Skip to content

Commit

Permalink
Merge pull request #2001 from christianvogt/global-model-serving
Browse files Browse the repository at this point in the history
handle kserve in global model serving page
  • Loading branch information
openshift-ci[bot] authored Oct 25, 2023
2 parents c5c3596 + 5b9e0e8 commit 7ec208c
Show file tree
Hide file tree
Showing 15 changed files with 238 additions and 50 deletions.
2 changes: 1 addition & 1 deletion frontend/src/const.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { KnownLabels } from '~/k8sTypes';

export const LABEL_SELECTOR_DASHBOARD_RESOURCE = `${KnownLabels.DASHBOARD_RESOURCE}=true`;
export const LABEL_SELECTOR_MODEL_SERVING_PROJECT = `${KnownLabels.MODEL_SERVING_PROJECT}=true`;
export const LABEL_SELECTOR_MODEL_SERVING_PROJECT = KnownLabels.MODEL_SERVING_PROJECT;
export const LABEL_SELECTOR_DATA_CONNECTION_AWS = `${KnownLabels.DATA_CONNECTION_AWS}=true`;
export const LABEL_SELECTOR_PROJECT_SHARING = `${KnownLabels.PROJECT_SHARING}=true`;
5 changes: 5 additions & 0 deletions frontend/src/k8sTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export type ServingRuntimeAnnotations = Partial<{
'opendatahub.io/accelerator-name': string;
'enable-route': string;
'enable-auth': string;
'modelmesh-enabled': 'true' | 'false';
}>;

export type BuildConfigKind = K8sResourceCommon & {
Expand Down Expand Up @@ -357,6 +358,10 @@ export type InferenceServiceKind = K8sResourceCommon & {
metadata: {
name: string;
namespace: string;
annotations?: DisplayNameAnnotations &
Partial<{
'serving.kserve.io/deploymentMode': 'ModelMesh';
}>;
};
spec: {
predictor: {
Expand Down
37 changes: 37 additions & 0 deletions frontend/src/pages/modelServing/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import {
checkPlatformAvailability,
resourcesArePositive,
} from '~/pages/modelServing/utils';
import {
mockServingRuntimeK8sResource,
mockServingRuntimeK8sResourceLegacy,
} from '~/__mocks__/mockServingRuntimeK8sResource';
import { ServingRuntimeKind } from '~/k8sTypes';
import { getDisplayNameFromServingRuntimeTemplate } from '~/pages/modelServing/customServingRuntimes/utils';
import { ContainerResources } from '~/types';

describe('resourcesArePositive', () => {
Expand Down Expand Up @@ -156,3 +162,34 @@ describe('servingPlatformsInstallaed', () => {
expect(checkModelMeshFailureStatus(mockedDataScienceStatusKserve)).toEqual(errorMessage);
});
});

describe('getDisplayNameFromServingRuntimeTemplate', () => {
it('should provide default name if not found', () => {
const servingRuntime = getDisplayNameFromServingRuntimeTemplate({
metadata: {},
spec: {},
} as ServingRuntimeKind);
expect(servingRuntime).toBe('Unknown Serving Runtime');
});

it('should prioritize name from annotation "opendatahub.io/template-display-name"', () => {
const servingRuntime = getDisplayNameFromServingRuntimeTemplate(
mockServingRuntimeK8sResource({}),
);
expect(servingRuntime).toBe('OpenVINO Serving Runtime (Supports GPUs)');
});

it('should fallback first to name from annotation "opendatahub.io/template-name"', () => {
const mockServingRuntime = mockServingRuntimeK8sResource({});
delete mockServingRuntime.metadata.annotations?.['opendatahub.io/template-display-name'];
const servingRuntime = getDisplayNameFromServingRuntimeTemplate(mockServingRuntime);
expect(servingRuntime).toBe('ovms');
});

it('should fallback to ovms serverType', () => {
const servingRuntime = getDisplayNameFromServingRuntimeTemplate(
mockServingRuntimeK8sResourceLegacy({}),
);
expect(servingRuntime).toBe('OpenVINO Model Server');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import { getInferenceServiceDisplayName } from './utils';
type DeleteInferenceServiceModalProps = {
inferenceService?: InferenceServiceKind;
onClose: (deleted: boolean) => void;
isOpen?: boolean;
};

const DeleteInferenceServiceModal: React.FC<DeleteInferenceServiceModalProps> = ({
inferenceService,
onClose,
isOpen = false,
}) => {
const [isDeleting, setIsDeleting] = React.useState(false);
const [error, setError] = React.useState<Error | undefined>();
Expand All @@ -29,7 +31,7 @@ const DeleteInferenceServiceModal: React.FC<DeleteInferenceServiceModalProps> =
return (
<DeleteModal
title="Delete deployed model?"
isOpen={!!inferenceService}
isOpen={isOpen}
onClose={() => onBeforeClose(false)}
submitButtonLabel="Delete deployed model"
onDelete={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const InferenceServiceEndpoint: React.FC<InferenceServiceEndpointProps> = ({
);
}

if (!routeLink || !loaded) {
if (!loaded) {
return <Skeleton />;
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { HelperText, HelperTextItem, Skeleton } from '@patternfly/react-core';
import { HelperText, HelperTextItem, Label, Skeleton } from '@patternfly/react-core';
import { InferenceServiceKind } from '~/k8sTypes';
import { getProjectDisplayName } from '~/pages/projects/utils';
import { byName, ProjectsContext } from '~/concepts/projects/ProjectsContext';
Expand Down Expand Up @@ -27,7 +27,22 @@ const InferenceServiceProject: React.FC<InferenceServiceProjectProps> = ({ infer

const project = modelServingProjects.find(byName(inferenceService.metadata.namespace));

return <>{project ? getProjectDisplayName(project) : 'Unknown'}</>;
return (
<>
{project ? (
<>
{getProjectDisplayName(project)}{' '}
<Label>
{project.metadata.labels?.['modelmesh-enabled'] === 'true'
? 'Multi-model serving enabled'
: 'Single model serving enabled'}
</Label>
</>
) : (
'Unknown'
)}
</>
);
};

export default InferenceServiceProject;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';
import { ServingRuntimeKind } from '~/k8sTypes';
import { getDisplayNameFromServingRuntimeTemplate } from '~/pages/modelServing/customServingRuntimes/utils';

type Props = {
servingRuntime?: ServingRuntimeKind;
};

const InferenceServiceServingRuntime: React.FC<Props> = ({ servingRuntime }) => (
<>{servingRuntime ? getDisplayNameFromServingRuntimeTemplate(servingRuntime) : 'Unknown'}</>
);

export default InferenceServiceServingRuntime;
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const InferenceServiceTable: React.FC<InferenceServiceTableProps> = ({
)}
/>
<DeleteInferenceServiceModal
isOpen={!!deleteInferenceService}
inferenceService={deleteInferenceService}
onClose={(deleted) => {
if (deleted) {
Expand All @@ -72,7 +73,7 @@ const InferenceServiceTable: React.FC<InferenceServiceTableProps> = ({
}}
/>
<ManageInferenceServiceModal
isOpen={editInferenceService !== undefined}
isOpen={!!editInferenceService}
editInfo={editInferenceService}
onClose={(edited) => {
if (edited) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { DropdownDirection } from '@patternfly/react-core';
import { ActionsColumn, Td, Tr } from '@patternfly/react-table';
import { Link } from 'react-router-dom';
import ResourceNameTooltip from '~/components/ResourceNameTooltip';
import { isModelMesh } from '~/pages/modelServing/utils';
import useModelMetricsEnabled from '~/pages/modelServing/useModelMetricsEnabled';
import { InferenceServiceKind, ServingRuntimeKind } from '~/k8sTypes';
import { getInferenceServiceDisplayName } from './utils';
import InferenceServiceEndpoint from './InferenceServiceEndpoint';
import InferenceServiceProject from './InferenceServiceProject';
import InferenceServiceModel from './InferenceServiceModel';
import InferenceServiceStatus from './InferenceServiceStatus';
import InferenceServiceServingRuntime from './InferenceServiceServingRuntime';

type InferenceServiceTableRowProps = {
obj: InferenceServiceKind;
Expand Down Expand Up @@ -53,8 +54,8 @@ const InferenceServiceTableRow: React.FC<InferenceServiceTableRowProps> = ({
</Td>
)}
{isGlobal && (
<Td dataLabel="Model server">
<InferenceServiceModel inferenceService={inferenceService} />
<Td dataLabel="Serving Runtime">
<InferenceServiceServingRuntime servingRuntime={servingRuntime} />
</Td>
)}
<Td dataLabel="Inference endpoint">
Expand All @@ -71,6 +72,8 @@ const InferenceServiceTableRow: React.FC<InferenceServiceTableRowProps> = ({
dropdownDirection={isGlobal ? DropdownDirection.down : DropdownDirection.up}
items={[
{
// TODO re-enable edit when supported
isDisabled: !isModelMesh(inferenceService),
title: 'Edit',
onClick: () => {
onEditInferenceService(inferenceService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const ModelServingGlobal: React.FC = () => {

return (
<ApplicationsPage
title="Model serving"
title="Deployed models"
description="Manage and view the health and performance of your deployed models."
loaded
empty={servingRuntimes.length === 0 || inferenceServices.length === 0}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { mockInferenceServiceK8sResource } from '~/__mocks__/mockInferenceServiceK8sResource';
import InferenceServiceProject from '~/pages/modelServing/screens/global/InferenceServiceProject';
import { ProjectsContext } from '~/concepts/projects/ProjectsContext';
import { mockProjectK8sResource } from '~/__mocks__/mockProjectK8sResource';
import { ProjectKind } from '~/k8sTypes';

describe('InferenceServiceProject', () => {
it('should render error if loading fails', () => {
const result = render(
<InferenceServiceProject inferenceService={mockInferenceServiceK8sResource({})} />,
{
wrapper: ({ children }) => (
<ProjectsContext.Provider
value={
{
loaded: true,
loadError: new Error('test loading error'),
} as React.ComponentProps<typeof ProjectsContext.Provider>['value']
}
>
{children}
</ProjectsContext.Provider>
),
},
);

expect(result.queryByText(/test loading error/)).toBeInTheDocument();
});

it('should render modelmesh project', () => {
const result = render(
<InferenceServiceProject
inferenceService={mockInferenceServiceK8sResource({
namespace: 'my-project',
})}
/>,
{
wrapper: ({ children }) => (
<ProjectsContext.Provider
value={
{
loaded: true,
modelServingProjects: [
mockProjectK8sResource({
k8sName: 'my-project',
displayName: 'My Project',
enableModelMesh: true,
}),
],
} as React.ComponentProps<typeof ProjectsContext.Provider>['value']
}
>
{children}
</ProjectsContext.Provider>
),
},
);

expect(result.queryByText('My Project')).toBeInTheDocument();
expect(result.queryByText('Multi-model serving enabled')).toBeInTheDocument();
});

it('should render kserve project', () => {
const result = render(
<InferenceServiceProject
inferenceService={mockInferenceServiceK8sResource({
namespace: 'my-project',
})}
/>,
{
wrapper: ({ children }) => (
<ProjectsContext.Provider
value={
{
loaded: true,
modelServingProjects: [
mockProjectK8sResource({
k8sName: 'my-project',
displayName: 'My Project',
enableModelMesh: false,
}),
],
} as React.ComponentProps<typeof ProjectsContext.Provider>['value']
}
>
{children}
</ProjectsContext.Provider>
),
},
);

expect(result.queryByText('My Project')).toBeInTheDocument();
expect(result.queryByText('Single model serving enabled')).toBeInTheDocument();
});

it('should render kserve project', () => {
const result = render(
<InferenceServiceProject
inferenceService={mockInferenceServiceK8sResource({
namespace: 'my-project',
})}
/>,
{
wrapper: ({ children }) => (
<ProjectsContext.Provider
value={
{
loaded: true,
modelServingProjects: [] as ProjectKind[],
} as React.ComponentProps<typeof ProjectsContext.Provider>['value']
}
>
{children}
</ProjectsContext.Provider>
),
},
);

expect(result.queryByText('My Project')).not.toBeInTheDocument();
expect(result.queryByText('Unknown')).toBeInTheDocument();
expect(result.queryByText('Single model serving enabled')).not.toBeInTheDocument();
expect(result.queryByText('Multi-model serving enabled')).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import InferenceServiceServingRuntime from '~/pages/modelServing/screens/global/InferenceServiceServingRuntime';
import { mockServingRuntimeK8sResource } from '~/__mocks__/mockServingRuntimeK8sResource';

describe('InferenceServiceServingRuntime', () => {
it('should handle undefined serving runtime', () => {
const wrapper = render(<InferenceServiceServingRuntime />);
expect(wrapper.container.textContent).toBe('Unknown');
});

it('should display serving runtime name', () => {
const mockServingRuntime = mockServingRuntimeK8sResource({});
const wrapper = render(<InferenceServiceServingRuntime servingRuntime={mockServingRuntime} />);
expect(wrapper.container.textContent).toBe('OpenVINO Serving Runtime (Supports GPUs)');
});
});
Loading

0 comments on commit 7ec208c

Please sign in to comment.