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

handle kserve in global model serving page #2001

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)}{' '}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could give a little bit of padding, but that can be a follow up enhancement and we can discuss it later with UX

<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
Loading