Skip to content

Commit

Permalink
Add check for dsc status and utility types to check serving platform …
Browse files Browse the repository at this point in the history
…availablity
  • Loading branch information
lucferbux committed Oct 20, 2023
1 parent 5c15171 commit 5da4e14
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 1 deletion.
129 changes: 129 additions & 0 deletions frontend/src/__mocks__/mockDSCStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { DataScienceClusterKindStatus, K8sCondition } from '~/k8sTypes';

export type MockDataScienceClusterKindStatus = {
conditions?: K8sCondition[];
phase?: string;
codeFlareEnabled?: boolean;
dataSciencePipelineOperatorEnabled?: boolean;
kserveEnabled?: boolean;
modelMeshEnabled?: boolean;
odhDashboardEnabled?: boolean;
rayEnabled?: boolean;
workbenchesEnabled?: boolean;
};

export const mockDataScienceStatus = ({
conditions = [],
phase = 'Ready',
codeFlareEnabled = true,
dataSciencePipelineOperatorEnabled = true,
kserveEnabled = true,
modelMeshEnabled = true,
odhDashboardEnabled = true,
rayEnabled = true,
workbenchesEnabled = true,
}: MockDataScienceClusterKindStatus): DataScienceClusterKindStatus => ({
conditions: [
...[
{
lastHeartbeatTime: '2023-10-20T11:44:48Z',
lastTransitionTime: '2023-10-15T19:04:21Z',
message: 'DataScienceCluster resource reconciled successfully',
reason: 'ReconcileCompleted',
status: 'True',
type: 'ReconcileComplete',
},
{
lastHeartbeatTime: '2023-10-20T11:44:48Z',
lastTransitionTime: '2023-10-15T19:04:21Z',
message: 'DataScienceCluster resource reconciled successfully',
reason: 'ReconcileCompleted',
status: 'True',
type: 'Available',
},
{
lastHeartbeatTime: '2023-10-20T11:44:48Z',
lastTransitionTime: '2023-10-15T19:04:21Z',
message: 'DataScienceCluster resource reconciled successfully',
reason: 'ReconcileCompleted',
status: 'False',
type: 'Progressing',
},
{
lastHeartbeatTime: '2023-10-20T11:44:48Z',
lastTransitionTime: '2023-10-15T19:04:10Z',
message: 'DataScienceCluster resource reconciled successfully',
reason: 'ReconcileCompleted',
status: 'False',
type: 'Degraded',
},
{
lastHeartbeatTime: '2023-10-20T11:44:48Z',
lastTransitionTime: '2023-10-15T19:04:21Z',
message: 'DataScienceCluster resource reconciled successfully',
reason: 'ReconcileCompleted',
status: 'True',
type: 'Upgradeable',
},
{
lastHeartbeatTime: '2023-10-20T11:44:59Z',
lastTransitionTime: '2023-10-20T11:44:59Z',
message: 'Component reconciled successfully',
reason: 'ReconcileCompleted',
status: 'True',
type: 'odh-dashboardReady',
},
{
lastHeartbeatTime: '2023-10-20T11:44:59Z',
lastTransitionTime: '2023-10-20T11:44:59Z',
message: 'Component reconciled successfully',
reason: 'ReconcileCompleted',
status: 'True',
type: 'data-science-pipelines-operatorReady',
},
{
lastHeartbeatTime: '2023-10-20T11:45:01Z',
lastTransitionTime: '2023-10-20T11:45:01Z',
message: 'Component reconciled successfully',
reason: 'ReconcileCompleted',
status: 'True',
type: 'workbenchesReady',
},
{
lastHeartbeatTime: '2023-10-20T11:45:04Z',
lastTransitionTime: '2023-10-20T11:45:04Z',
message: 'Component reconciled successfully',
reason: 'ReconcileCompleted',
status: 'True',
type: 'kserveReady',
},
{
lastHeartbeatTime: '2023-10-20T11:45:04Z',
lastTransitionTime: '2023-10-20T11:45:04Z',
message: 'Component reconciled successfully',
reason: 'ReconcileCompleted',
status: 'True',
type: 'model-meshReady',
},
{
lastHeartbeatTime: '2023-10-20T11:45:06Z',
lastTransitionTime: '2023-10-20T11:45:06Z',
message: 'Component is disabled',
reason: 'ReconcileInit',
status: 'Unknown',
type: 'rayReady',
},
],
...conditions,
],
installedComponents: {
codeflare: codeFlareEnabled,
'data-science-pipelines-operator': dataSciencePipelineOperatorEnabled,
kserve: kserveEnabled,
'model-mesh': modelMeshEnabled,
'odh-dashboard': odhDashboardEnabled,
ray: rayEnabled,
workbenches: workbenchesEnabled,
},
phase,
});
1 change: 1 addition & 0 deletions frontend/src/k8sTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export type K8sCondition = {
reason?: string;
message?: string;
lastTransitionTime?: string;
lastHeartbeatTime?: string;
};

export type ServingRuntimeAnnotations = Partial<{
Expand Down
108 changes: 107 additions & 1 deletion frontend/src/pages/modelServing/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { resourcesArePositive } from '~/pages/modelServing/utils';
import { mockDataScienceStatus } from '~/__mocks__/mockDSCStatus';
import {
checkKserveFailureStatus,
checkModelMeshFailureStatus,
checkPlatformAvailability,
resourcesArePositive,
} from '~/pages/modelServing/utils';
import { ContainerResources } from '~/types';

describe('resourcesArePositive', () => {
Expand Down Expand Up @@ -50,3 +56,103 @@ describe('resourcesArePositive', () => {
expect(resourcesArePositive(resources)).toBe(true);
});
});

describe('servingPlatformsInstallaed', () => {
it('should return true for kserve but false for model-mesh', () => {
const mockedDataScienceStatusKserve = mockDataScienceStatus({
kserveEnabled: true,
modelMeshEnabled: false,
});

expect(checkPlatformAvailability(mockedDataScienceStatusKserve)).toEqual({
kServeAvailable: true,
modelMeshAvailable: false,
});
});

it('should return false for kserve but true for model-mesh', () => {
const mockedDataScienceStatusKserve = mockDataScienceStatus({
kserveEnabled: false,
modelMeshEnabled: true,
});

expect(checkPlatformAvailability(mockedDataScienceStatusKserve)).toEqual({
kServeAvailable: false,
modelMeshAvailable: true,
});
});

it('should return false for both kserve and model-mesh', () => {
const mockedDataScienceStatusKserve = mockDataScienceStatus({
kserveEnabled: false,
modelMeshEnabled: false,
});

expect(checkPlatformAvailability(mockedDataScienceStatusKserve)).toEqual({
kServeAvailable: false,
modelMeshAvailable: false,
});
});

it('should not find any status issue for kserve', () => {
const mockedDataScienceStatusKserve = mockDataScienceStatus({});

expect(checkKserveFailureStatus(mockedDataScienceStatusKserve)).toEqual('');
});

it('should find an issue with kserve', () => {
const errorMessage =
'Component reconciliation failed: operator servicemeshoperator not found. Please install the operator before enabling kserve component';
const mockedDataScienceStatusKserve = mockDataScienceStatus({
conditions: [
{
lastHeartbeatTime: '2023-10-20T11:31:24Z',
lastTransitionTime: '2023-10-15T19:04:21Z',
message:
'DataScienceCluster resource reconciled with component errors: 1 error occurred:\n\t* operator servicemeshoperator not found. Please install the operator before enabling kserve component',
reason: 'ReconcileCompletedWithComponentErrors',
status: 'True',
type: 'ReconcileComplete',
},
{
lastHeartbeatTime: '2023-10-20T11:31:19Z',
lastTransitionTime: '2023-10-20T11:31:19Z',
message: errorMessage,
reason: 'ReconcileFailed',
status: 'False',
type: 'kserveReady',
},
],
});

expect(checkKserveFailureStatus(mockedDataScienceStatusKserve)).toEqual(errorMessage);
});

it('should find an issue with modelMesh', () => {
const errorMessage =
'Component reconciliation failed: CustomResourceDefinition.apiextensions.k8s.io "inferenceservices.serving.kserve.io" is invalid: [spec.conversion.strategy: Required value, spec.conversion.webhookClientConfig: Forbidden: should not be set when strategy is not set to Webhook]';
const mockedDataScienceStatusKserve = mockDataScienceStatus({
conditions: [
{
lastHeartbeatTime: '2023-10-20T11:31:24Z',
lastTransitionTime: '2023-10-15T19:04:21Z',
message:
'DataScienceCluster resource reconciled with component errors: 1 error occurred:\n\t* CustomResourceDefinition.apiextensions.k8s.io "inferenceservices.serving.kserve.io" is invalid: [spec.conversion.strategy: Required value, spec.conversion.webhookClientConfig: Forbidden: should not be set when strategy is not set to Webhook]',
reason: 'ReconcileCompletedWithComponentErrors',
status: 'True',
type: 'ReconcileComplete',
},
{
lastHeartbeatTime: '2023-10-20T11:31:19Z',
lastTransitionTime: '2023-10-20T11:31:19Z',
message: errorMessage,
reason: 'ReconcileFailed',
status: 'False',
type: 'model-meshReady',
},
],
});

expect(checkModelMeshFailureStatus(mockedDataScienceStatusKserve)).toEqual(errorMessage);
});
});
18 changes: 18 additions & 0 deletions frontend/src/pages/modelServing/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
K8sAPIOptions,
RoleBindingKind,
ServingRuntimeKind,
DataScienceClusterKindStatus,
} from '~/k8sTypes';
import { ContainerResources } from '~/types';
import { getDisplayNameFromK8sResource, translateDisplayNameForK8s } from '~/pages/projects/utils';
Expand Down Expand Up @@ -211,3 +212,20 @@ export const isModelServerEditInfoChanged = (
createData.tokens.map((token) => token.name).sort(),
))
: true;

export const checkPlatformAvailability = (
status: DataScienceClusterKindStatus,
): { kServeAvailable: boolean; modelMeshAvailable: boolean } => ({
kServeAvailable: status.installedComponents.kserve,
modelMeshAvailable: status.installedComponents['model-mesh'],
});

export const checkKserveFailureStatus = (status: DataScienceClusterKindStatus): string =>
status.conditions.find(
(condition) => condition.type === 'kserveReady' && condition.status === 'False',
)?.message || '';

export const checkModelMeshFailureStatus = (status: DataScienceClusterKindStatus): string =>
status.conditions.find(
(condition) => condition.type === 'model-meshReady' && condition.status === 'False',
)?.message || '';
12 changes: 12 additions & 0 deletions frontend/src/services/getClusterStatusService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import axios from 'axios';
import { DataScienceClusterKindStatus } from '~/k8sTypes';

export const fetchClusterStatus = (): Promise<DataScienceClusterKindStatus> => {
const url = '/api/dsc/status';
return axios
.get(url)
.then((response) => response.data)
.catch((e) => {
throw new Error(e.response.data.message);
});
};

0 comments on commit 5da4e14

Please sign in to comment.