Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug 1906769: Fix for topology load for users without access to all resources #7511

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -7,8 +7,8 @@ import { usePrometheusRulesPoll } from '@console/internal/components/graphs/prom
import { getAlertsAndRules } from '@console/internal/components/monitoring/utils';
import { TopologyResourcesObject, TrafficData } from '../topology-types';
import { ModelContext, ExtensibleModel } from './ModelContext';
import { baseDataModelGetter } from './data-transformer';
import { getFilterById, SHOW_GROUPS_FILTER_ID, useDisplayFilters } from '../filters';
import { updateTopologyDataModel } from './updateTopologyDataModel';

type TopologyDataRetrieverProps = {
trafficData?: TrafficData;
Expand Down Expand Up @@ -54,43 +54,13 @@ const TopologyDataRetriever: React.FC<TopologyDataRetrieverProps> = ({ trafficDa
}, [namespace]);

React.useEffect(() => {
const { extensionsLoaded, watchedResources } = dataModelContext;
if (!extensionsLoaded) {
return;
}

if (!Object.keys(resources).every((key) => resources[key].loaded)) {
return;
}

const loadErrorKey = Object.keys(resources).find(
(key) => resources[key].loadError && !watchedResources[key].optional,
);
dataModelContext.loadError = loadErrorKey && resources[loadErrorKey].loadError;
if (loadErrorKey) {
return;
}

// Get Workload objects from extensions
const workloadResources = dataModelContext.getWorkloadResources(resources);

// Get model from each extension
const depicters = dataModelContext.dataModelDepicters;
dataModelContext
.getExtensionModels(resources)
.then((extensionsModel) => {
const fullModel = baseDataModelGetter(
extensionsModel,
dataModelContext.namespace,
resources,
workloadResources,
showGroups ? depicters : [],
trafficData,
monitoringAlerts,
);
dataModelContext.reconcileModel(fullModel, resources);
dataModelContext.loaded = true;
dataModelContext.model = fullModel;
updateTopologyDataModel(dataModelContext, resources, showGroups, trafficData, monitoringAlerts)
.then((res) => {
dataModelContext.loadError = res.loadError;
if (res.loaded) {
dataModelContext.loaded = true;
dataModelContext.model = res.model;
}
})
.catch(() => {});
}, [resources, trafficData, dataModelContext, monitoringAlerts, showGroups]);
Expand Down
@@ -0,0 +1,124 @@
import * as _ from 'lodash';
import { updateTopologyDataModel } from '../updateTopologyDataModel';
import { ExtensibleModel } from '../ModelContext';

const namespace = 'test-project';

const MockWatchedResources = {
deploymentConfigs: { isList: true, kind: 'DeploymentConfig', namespace, optional: true },
deployments: { isList: true, kind: 'Deployment', namespace, optional: true },
jobs: { isList: true, kind: 'Job', namespace, optional: true },
pods: { isList: true, kind: 'Pod', namespace, optional: true },
secrets: { isList: true, kind: 'Secret', namespace, optional: true },
statefulSets: { isList: true, kind: 'StatefulSet', namespace, optional: true },
};

const mockNotReadyResources = {
deploymentConfigs: { data: [], loaded: true, loadError: '' },
deployments: { data: [], loaded: true, loadError: '' },
jobs: { data: [], loaded: true, loadError: '' },
pods: { data: [], loaded: false, loadError: '' },
secrets: { data: [], loaded: true, loadError: '' },
statefulSets: { data: [], loaded: true, loadError: '' },
};

const mockReadyResources = {
deploymentConfigs: { data: [], loaded: true, loadError: '' },
deployments: { data: [], loaded: true, loadError: '' },
jobs: { data: [], loaded: true, loadError: '' },
pods: { data: [], loaded: true, loadError: '' },
secrets: { data: [], loaded: true, loadError: '' },
statefulSets: { data: [], loaded: true, loadError: '' },
};

const mockErrorResources = {
deploymentConfigs: { data: [], loaded: true, loadError: '' },
deployments: { data: [], loaded: false, loadError: 'Deployments Error' },
jobs: { data: [], loaded: true, loadError: 'Jobs Error' },
pods: { data: [], loaded: true, loadError: '' },
secrets: { data: [], loaded: true, loadError: '' },
statefulSets: { data: [], loaded: true, loadError: '' },
};

const errorResourceKeys = ['deployments', 'jobs'];

describe('TopologyDataRetriever ', () => {
let mockWatchedResources;
let mockExtensibleModel;

beforeEach(() => {
mockWatchedResources = _.cloneDeep(MockWatchedResources);
mockExtensibleModel = new ExtensibleModel(namespace);
mockExtensibleModel.extensionsLoaded = true;
mockExtensibleModel.watchedResources = mockWatchedResources;
});

it('should proceed when all data is loaded', async () => {
const results = await updateTopologyDataModel(
mockExtensibleModel,
mockReadyResources,
true,
null,
null,
);
expect(results.loaded).toBeTruthy();
expect(results.loadError).toBeFalsy();
});

it('should wait for extensions to load', async () => {
mockExtensibleModel.extensionsLoaded = false;
const results = await updateTopologyDataModel(
mockExtensibleModel,
mockReadyResources,
true,
null,
null,
);
expect(results.loaded).toBeFalsy();
expect(results.loadError).toBeFalsy();
});

it('should wait for resources to be defined', async () => {
const results = await updateTopologyDataModel(mockExtensibleModel, undefined, true, null, null);
expect(results.loaded).toBeFalsy();
expect(results.loadError).toBeFalsy();
});

it('should wait for data to load', async () => {
const results = await updateTopologyDataModel(
mockExtensibleModel,
mockNotReadyResources,
true,
null,
null,
);
expect(results.loaded).toBeFalsy();
expect(results.loadError).toBeFalsy();
});

it('should proceed when optional data failed to load', async () => {
const results = await updateTopologyDataModel(
mockExtensibleModel,
mockErrorResources,
true,
null,
null,
);
expect(results.loaded).toBeTruthy();
expect(results.loadError).toBeFalsy();
});

it('should set an error when required data failed to load', async () => {
errorResourceKeys.forEach((key) => (mockWatchedResources[key].optional = false));
mockExtensibleModel.watchedResources = mockWatchedResources;
const results = await updateTopologyDataModel(
mockExtensibleModel,
mockErrorResources,
true,
null,
null,
);
expect(results.loaded).toBeFalsy();
expect(results.loadError).toBeTruthy();
});
});
@@ -0,0 +1,62 @@
import { ExtensibleModel } from './ModelContext';
import { Model } from '@patternfly/react-topology';
import { WatchK8sResults } from '@console/internal/components/utils/k8s-watch-hook';
import { Alerts } from '@console/internal/components/monitoring/types';
import { TopologyResourcesObject, TrafficData } from '../topology-types';
import { baseDataModelGetter } from './data-transformer';

export const updateTopologyDataModel = (
dataModelContext: ExtensibleModel,
resources: WatchK8sResults<TopologyResourcesObject>,
showGroups: boolean,
trafficData: TrafficData,
monitoringAlerts: Alerts,
): Promise<{ loaded: boolean; loadError: string; model: Model }> => {
const { extensionsLoaded, watchedResources } = dataModelContext;
if (!extensionsLoaded || !resources) {
return Promise.resolve({ loaded: false, loadError: '', model: null });
}

const getLoadError = (key) => {
if (resources[key].loadError && !watchedResources[key].optional) {
return resources[key].loadError;
}
return '';
};

const isLoaded = (key) => {
return resources[key].loaded || (resources[key].loadError && watchedResources[key].optional);
};

const loadErrorKey = Object.keys(resources).find((key) => getLoadError(key));
if (loadErrorKey) {
return Promise.resolve({
loaded: false,
loadError: resources[loadErrorKey].loadError,
model: null,
});
}

if (!Object.keys(resources).every((key) => isLoaded(key))) {
return Promise.resolve({ loaded: false, loadError: '', model: null });
}

// Get Workload objects from extensions
const workloadResources = dataModelContext.getWorkloadResources(resources);

// Get model from each extension
const depicters = dataModelContext.dataModelDepicters;
return dataModelContext.getExtensionModels(resources).then((extensionsModel) => {
const fullModel = baseDataModelGetter(
extensionsModel,
dataModelContext.namespace,
resources,
workloadResources,
showGroups ? depicters : [],
trafficData,
monitoringAlerts,
);
dataModelContext.reconcileModel(fullModel, resources);
return Promise.resolve({ loaded: true, loadError: '', model: fullModel });
});
};