Skip to content

Commit

Permalink
Convert templates to use websocket
Browse files Browse the repository at this point in the history
  • Loading branch information
ppadti committed May 29, 2024
1 parent 23428a2 commit 6b9b882
Show file tree
Hide file tree
Showing 20 changed files with 211 additions and 259 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { mockK8sResourceList } from '~/__mocks__/mockK8sResourceList';
import { mockServingRuntimeTemplateK8sResource } from '~/__mocks__/mockServingRuntimeTemplateK8sResource';
import { servingRuntimes } from '~/__tests__/cypress/cypress/pages/servingRuntimes';
import { ServingRuntimeAPIProtocol, ServingRuntimePlatform } from '~/types';
import { deleteModal } from '~/__tests__/cypress/cypress/pages/components/DeleteModal';
import { mockServingRuntimeK8sResource } from '~/__mocks__/mockServingRuntimeK8sResource';
import { asProductAdminUser, asProjectAdminUser } from '~/__tests__/cypress/cypress/utils/users';
import { pageNotfound } from '~/__tests__/cypress/cypress/pages/pageNotFound';
import {
customServingRuntimesInitialMock,
customServingRuntimesIntercept,
} from '~/__tests__/cypress/cypress/e2e/customServingRuntimes/customServingRuntimesUtils';
import { customServingRuntimesIntercept } from '~/__tests__/cypress/cypress/e2e/customServingRuntimes/customServingRuntimesUtils';
import { TemplateModel } from '~/__tests__/cypress/cypress/utils/models';

const addfilePath = '../../__mocks__/mock-custom-serving-runtime-add.yaml';
const editfilePath = '../../__mocks__/mock-custom-serving-runtime-edit.yaml';
Expand Down Expand Up @@ -81,37 +78,20 @@ describe('Custom serving runtimes', () => {

servingRuntimes.findSubmitButton().should('be.enabled');
servingRuntimes.findSubmitButton().click();
cy.wait('@createTemplate');

cy.wait('@createSingleModelServingRuntime').then((interception) => {
expect(interception.request.url).to.include('?dryRun=All');
expect(interception.request.body).to.containSubset({
metadata: {
name: 'template-new',
annotations: { 'openshift.io/display-name': 'New OVMS Server' },
namespace: 'opendatahub',
},
});
});
cy.wsK8s(
'ADDED',
TemplateModel,
mockServingRuntimeTemplateK8sResource({
name: 'template-new',
displayName: 'New OVMS Server',
platforms: [ServingRuntimePlatform.SINGLE],
apiProtocol: ServingRuntimeAPIProtocol.REST,
}),
);

cy.wait('@createTemplate').then((interception) => {
expect(interception.request.body).to.containSubset({
metadata: {
annotations: {
'opendatahub.io/modelServingSupport': '["single"]',
'opendatahub.io/apiProtocol': 'REST',
},
},
objects: [
{
metadata: {
name: 'template-new',
annotations: { 'openshift.io/display-name': 'New OVMS Server' },
labels: { 'opendatahub.io/dashboard': 'true' },
},
},
],
});
});
servingRuntimes.getRowById('template-new').shouldBeSingleModel(true);
});

it('should add a new multi model serving runtime', () => {
Expand Down Expand Up @@ -143,35 +123,17 @@ describe('Custom serving runtimes', () => {
servingRuntimes.findSubmitButton().should('be.enabled');
servingRuntimes.findSubmitButton().click();

cy.wait('@createMultiModelServingRuntime').then((interception) => {
expect(interception.request.url).to.include('?dryRun=All');
expect(interception.request.body.metadata).to.eql({
cy.wsK8s(
'ADDED',
TemplateModel,
mockServingRuntimeTemplateK8sResource({
name: 'template-new',
annotations: { 'openshift.io/display-name': 'New OVMS Server' },
labels: { 'opendatahub.io/dashboard': 'true' },
namespace: 'opendatahub',
});
});
displayName: 'New OVMS Server',
platforms: [ServingRuntimePlatform.MULTI],
}),
);

cy.wait('@createTemplate').then((interception) => {
expect(interception.request.body).to.containSubset({
metadata: {
annotations: {
'opendatahub.io/modelServingSupport': '["multi"]',
'opendatahub.io/apiProtocol': 'REST',
},
},
objects: [
{
metadata: {
name: 'template-new',
annotations: { 'openshift.io/display-name': 'New OVMS Server' },
labels: { 'opendatahub.io/dashboard': 'true' },
},
},
],
});
});
servingRuntimes.getRowById('template-new').shouldBeMultiModel(true);
});

it('should duplicate a serving runtime', () => {
Expand All @@ -185,19 +147,6 @@ describe('Custom serving runtimes', () => {
'duplicateTemplate',
);

const ServingRuntimeTemplateMock = mockServingRuntimeTemplateK8sResource({
name: 'serving-runtime-template-1',
displayName: 'Multi platform',
platforms: [ServingRuntimePlatform.SINGLE],
apiProtocol: ServingRuntimeAPIProtocol.GRPC,
});

cy.interceptOdh(
'GET /api/templates/:namespace',
{ path: { namespace: 'opendatahub' } },
mockK8sResourceList([...customServingRuntimesInitialMock, ServingRuntimeTemplateMock]),
).as('refreshServingRuntime');

servingRuntimes.getRowById('template-1').find().findKebabAction('Duplicate').click();
servingRuntimes.findAppTitle().should('have.text', 'Duplicate serving runtime');
cy.url().should('include', '/addServingRuntime');
Expand All @@ -210,36 +159,19 @@ describe('Custom serving runtimes', () => {
servingRuntimes.findSubmitButton().should('be.enabled');
servingRuntimes.findSubmitButton().click();

cy.wait('@duplicateServingRuntime').then((interception) => {
expect(interception.request.body.metadata).to.containSubset({
cy.wsK8s(
'ADDED',
TemplateModel,
mockServingRuntimeTemplateK8sResource({
name: 'template-1-copy',
annotations: { 'openshift.io/display-name': 'Copy of Multi Platform' },
namespace: 'opendatahub',
});
});

cy.wait('@duplicateTemplate').then((interception) => {
expect(interception.request.body).to.containSubset({
metadata: {
annotations: {
'opendatahub.io/modelServingSupport': '["single"]',
'opendatahub.io/apiProtocol': 'gRPC',
},
},
objects: [
{
metadata: {
name: 'template-1-copy',
annotations: { 'openshift.io/display-name': 'Copy of Multi Platform' },
},
},
],
});
});
cy.wait('@refreshServingRuntime');
displayName: 'Copy of Multi platform',
platforms: [ServingRuntimePlatform.SINGLE],
apiProtocol: ServingRuntimeAPIProtocol.GRPC,
}),
);

servingRuntimes
.getRowById('serving-runtime-template-1')
.getRowById('template-1-copy')
.shouldHaveAPIProtocol(ServingRuntimeAPIProtocol.GRPC);
});

Expand Down Expand Up @@ -310,5 +242,16 @@ describe('Custom serving runtimes', () => {
deleteModal.findSubmitButton().should('be.enabled').click();

cy.wait('@deleteServingRuntime');
cy.wsK8s(
'DELETED',
TemplateModel,
mockServingRuntimeTemplateK8sResource({
name: 'template-1',
displayName: 'Multi platform',
platforms: [ServingRuntimePlatform.SINGLE],
apiProtocol: ServingRuntimeAPIProtocol.REST,
}),
);
servingRuntimes.getRowById('template-1').find().should('not.exist');
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { mockK8sResourceList } from '~/__mocks__/mockK8sResourceList';
import { mockServingRuntimeTemplateK8sResource } from '~/__mocks__/mockServingRuntimeTemplateK8sResource';
import { ServingRuntimeAPIProtocol, ServingRuntimePlatform } from '~/types';
import { ProjectModel } from '~/__tests__/cypress/cypress/utils/models';
import { ProjectModel, TemplateModel } from '~/__tests__/cypress/cypress/utils/models';
import { mockProjectK8sResource } from '~/__mocks__';

export const customServingRuntimesInitialMock = [
Expand All @@ -28,10 +28,11 @@ export const customServingRuntimesInitialMock = [
];

export const customServingRuntimesIntercept = (): void => {
cy.interceptK8sList(TemplateModel, mockK8sResourceList(customServingRuntimesInitialMock));
cy.interceptK8sList(ProjectModel, mockK8sResourceList([mockProjectK8sResource({})]));
cy.interceptOdh(
'GET /api/templates/:namespace',
{ path: { namespace: 'opendatahub' } },
mockK8sResourceList(customServingRuntimesInitialMock),
);
cy.interceptK8sList(ProjectModel, mockK8sResourceList([mockProjectK8sResource({})]));
};
112 changes: 81 additions & 31 deletions frontend/src/api/k8s/__tests__/templates.spec.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,50 @@
import { K8sStatus, k8sDeleteResource, k8sListResource } from '@openshift/dynamic-plugin-sdk-utils';
import { mockK8sResourceList } from '~/__mocks__/mockK8sResourceList';
import {
K8sStatus,
k8sDeleteResource,
useK8sWatchResource,
} from '@openshift/dynamic-plugin-sdk-utils';
import { mock200Status, mock404Error } from '~/__mocks__/mockK8sStatus';

import { mockServingRuntimeTemplateK8sResource } from '~/__mocks__/mockServingRuntimeTemplateK8sResource';
import { assembleServingRuntimeTemplate, deleteTemplate, listTemplates } from '~/api';
import {
assembleServingRuntimeTemplate,
deleteTemplate,
groupVersionKind,
useTemplates,
} from '~/api';
import { TemplateModel } from '~/api/models';
import { K8sDSGResource, TemplateKind } from '~/k8sTypes';
import useCustomServingRuntimesEnabled from '~/pages/modelServing/customServingRuntimes/useCustomServingRuntimesEnabled';
import useModelServingEnabled from '~/pages/modelServing/useModelServingEnabled';
import { ServingRuntimeAPIProtocol, ServingRuntimePlatform } from '~/types';
import { genRandomChars } from '~/utilities/string';
import { NotReadyError } from '~/utilities/useFetchState';

jest.mock('@openshift/dynamic-plugin-sdk-utils', () => ({
k8sListResource: jest.fn(),
k8sDeleteResource: jest.fn(),
useK8sWatchResource: jest.fn(),
}));

jest.mock('~/utilities/string', () => ({
genRandomChars: jest.fn(),
}));

const k8sListResourceMock = jest.mocked(k8sListResource<TemplateKind>);
jest.mock('~/pages/modelServing/useModelServingEnabled', () => ({
__esModule: true,
default: jest.fn(),
}));

jest.mock('~/pages/modelServing/customServingRuntimes/useCustomServingRuntimesEnabled', () => ({
__esModule: true,
default: jest.fn(),
}));

const useModelServingEnabledMock = jest.mocked(useModelServingEnabled);
const useCustomServingRuntimesEnabledMock = jest.mocked(useCustomServingRuntimesEnabled);
const genRandomCharsMock = jest.mocked(genRandomChars);
const k8sDeleteResourceMock = jest.mocked(k8sDeleteResource<TemplateKind, K8sStatus>);
const useK8sWatchResourceMock = jest.mocked(useK8sWatchResource<TemplateKind[]>);

const templateMock = mockServingRuntimeTemplateK8sResource({});
const { namespace } = templateMock.metadata;
Expand Down Expand Up @@ -75,37 +99,63 @@ describe('assembleServingRuntimeTemplate', () => {
});
});

describe('listTemplates', () => {
it('should list templates without namespace and label selector', async () => {
k8sListResourceMock.mockResolvedValue(mockK8sResourceList([templateMock]));
const result = await listTemplates();
expect(k8sListResourceMock).toHaveBeenCalledWith({
model: TemplateModel,
queryOptions: {},
});
expect(k8sListResourceMock).toHaveBeenCalledTimes(1);
expect(result).toStrictEqual([templateMock]);
describe('useTemplates', () => {
it('should wrap useK8sWatchResource to watch templates', () => {
useModelServingEnabledMock.mockReturnValue(true);
useCustomServingRuntimesEnabledMock.mockReturnValue(true);
const mockReturnValue: ReturnType<typeof useK8sWatchResourceMock> = [[], false, undefined];
useK8sWatchResourceMock.mockReturnValue(mockReturnValue);
expect(useTemplates('opendatahub')).toStrictEqual(mockReturnValue);
expect(useK8sWatchResourceMock).toHaveBeenCalledWith(
{
isList: true,
groupVersionKind: groupVersionKind(TemplateModel),
namespace,
},
TemplateModel,
);
});

it('should list templates with namespace and label selector', async () => {
k8sListResourceMock.mockResolvedValue(mockK8sResourceList([templateMock]));
const result = await listTemplates(namespace, 'labelSelector');
expect(k8sListResourceMock).toHaveBeenCalledWith({
model: TemplateModel,
queryOptions: { ns: namespace, queryParams: { labelSelector: 'labelSelector' } },
});
expect(k8sListResourceMock).toHaveBeenCalledTimes(1);
expect(result).toStrictEqual([templateMock]);
it('should throw error when namespace is not provided', () => {
useModelServingEnabledMock.mockReturnValue(true);
useCustomServingRuntimesEnabledMock.mockReturnValue(true);
const mockReturnValue: ReturnType<typeof useK8sWatchResourceMock> = [[], false, undefined];
useK8sWatchResourceMock.mockReturnValue(mockReturnValue);
expect(() => useTemplates()).toThrow(new NotReadyError('No namespace provided'));
});

it('should handle errors and rethrow', async () => {
k8sListResourceMock.mockRejectedValue(new Error('error1'));
await expect(listTemplates()).rejects.toThrow('error1');
expect(k8sListResourceMock).toHaveBeenCalledTimes(1);
expect(k8sListResourceMock).toHaveBeenCalledWith({
model: TemplateModel,
queryOptions: {},
});
it('should throw error when model serving is not enabled', () => {
useModelServingEnabledMock.mockReturnValue(false);
useCustomServingRuntimesEnabledMock.mockReturnValue(true);
const mockReturnValue: ReturnType<typeof useK8sWatchResourceMock> = [[], false, undefined];
useK8sWatchResourceMock.mockReturnValue(mockReturnValue);
expect(() => useTemplates('opendatahub')).toThrow(
new NotReadyError('Model serving is not enabled'),
);
});

it('should filter templates when custom serving runtime is not enabled', () => {
const templatesMock = {
...templateMock,
metadata: { ...templateMock.metadata, labels: { 'opendatahub.io/ootb': 'true' } },
};
useModelServingEnabledMock.mockReturnValue(true);
useCustomServingRuntimesEnabledMock.mockReturnValue(false);
const mockReturnValue: ReturnType<typeof useK8sWatchResourceMock> = [
[templatesMock],
false,
undefined,
];
useK8sWatchResourceMock.mockReturnValue(mockReturnValue);
expect(useTemplates('opendatahub')).toStrictEqual(mockReturnValue);
expect(useK8sWatchResourceMock).toHaveBeenCalledWith(
{
isList: true,
groupVersionKind: groupVersionKind(TemplateModel),
namespace,
},
TemplateModel,
);
});
});

Expand Down
Loading

0 comments on commit 6b9b882

Please sign in to comment.