diff --git a/frontend/src/__mocks__/mockInferenceServiceK8sResource.ts b/frontend/src/__mocks__/mockInferenceServiceK8sResource.ts index 7e80939e8d..f878a01375 100644 --- a/frontend/src/__mocks__/mockInferenceServiceK8sResource.ts +++ b/frontend/src/__mocks__/mockInferenceServiceK8sResource.ts @@ -19,7 +19,9 @@ type MockResourceConfigType = { maxReplicas?: number; lastFailureInfoMessage?: string; resources?: ContainerResources; + kserveInternalUrl?: string; statusPredictor?: Record; + kserveInternalLabel?: boolean; }; type InferenceServicek8sError = K8sStatus & { @@ -76,6 +78,8 @@ export const mockInferenceServiceK8sResource = ({ lastFailureInfoMessage = 'Waiting for runtime Pod to become available', resources, statusPredictor = undefined, + kserveInternalUrl = '', + kserveInternalLabel = false, }: MockResourceConfigType): InferenceServiceKind => ({ apiVersion: 'serving.kserve.io/v1beta1', kind: 'InferenceService', @@ -96,6 +100,7 @@ export const mockInferenceServiceK8sResource = ({ labels: { name, [KnownLabels.DASHBOARD_RESOURCE]: 'true', + ...(kserveInternalLabel && { 'networking.knative.dev/visibility': 'cluster-local' }), }, name, namespace, @@ -167,5 +172,10 @@ export const mockInferenceServiceK8sResource = ({ }, transitionStatus: '', }, + ...(kserveInternalUrl && { + address: { + url: kserveInternalUrl, + }, + }), }, }); diff --git a/frontend/src/__tests__/cypress/cypress/pages/modelServing.ts b/frontend/src/__tests__/cypress/cypress/pages/modelServing.ts index 66fb6c0450..18bd61e95a 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/modelServing.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/modelServing.ts @@ -228,6 +228,14 @@ class ModelServingRow extends TableRow { .should(enabled ? 'not.exist' : 'exist'); return this; } + + findInternalServiceButton() { + return this.find().findByTestId('internal-service-button'); + } + + findInternalServicePopover() { + return cy.findByTestId('internal-service-popover'); + } } class ModelMeshRow extends ModelServingRow { diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/servingRuntimeList.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/servingRuntimeList.cy.ts index fcdf80219c..84a8a6aad8 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/servingRuntimeList.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/servingRuntimeList.cy.ts @@ -1894,7 +1894,7 @@ describe('Serving Runtime List', () => { }); describe('Internal service', () => { - it('Check internal service is rendered when the model is loaded', () => { + it('Check internal service is rendered when the model is loaded in Modelmesh', () => { initIntercepts({ projectEnableModelMesh: true, disableKServeConfig: false, @@ -1948,5 +1948,57 @@ describe('Serving Runtime List', () => { .findByText('Could not find any internal service enabled') .should('exist'); }); + + it('Check internal service is rendered when the model is loaded in Kserve', () => { + initIntercepts({ + projectEnableModelMesh: false, + disableKServeConfig: true, + disableModelMeshConfig: false, + servingRuntimes: [ + mockServingRuntimeK8sResource({ + name: 'test-model', + auth: true, + route: false, + }), + mockServingRuntimeK8sResource({ + name: 'test-model-not-loaded', + auth: true, + route: false, + }), + ], + inferenceServices: [ + mockInferenceServiceK8sResource({ + name: 'model-loaded', + modelName: 'test-model', + displayName: 'Loaded model', + isModelMesh: false, + kserveInternalUrl: 'http://test.kserve.svc.cluster.local', + kserveInternalLabel: true, + }), + mockInferenceServiceK8sResource({ + name: 'model-not-loaded', + modelName: 'est-model-not-loaded', + displayName: 'Model Not loaded', + isModelMesh: false, + kserveInternalLabel: true, + }), + ], + }); + + projectDetails.visitSection('test-project', 'model-server'); + + // Get modal of inference service when is loaded + const kserveRowModelLoaded = modelServingSection.getKServeRow('Loaded model'); + kserveRowModelLoaded.findInternalServiceButton().click(); + kserveRowModelLoaded.findInternalServicePopover().findByText('url').should('exist'); + + // Get modal of inference service when is not loaded + const kserveRowModelNotLoaded = modelServingSection.getKServeRow('Model Not loaded'); + kserveRowModelNotLoaded.findInternalServiceButton().click(); + kserveRowModelLoaded + .findInternalServicePopover() + .findByText('Could not find any internal service enabled') + .should('exist'); + }); }); }); diff --git a/frontend/src/api/k8s/__tests__/inferenceServices.spec.ts b/frontend/src/api/k8s/__tests__/inferenceServices.spec.ts index 5b4da94a1e..34c7efc9b8 100644 --- a/frontend/src/api/k8s/__tests__/inferenceServices.spec.ts +++ b/frontend/src/api/k8s/__tests__/inferenceServices.spec.ts @@ -107,6 +107,37 @@ describe('assembleInferenceService', () => { ); }); + it('should have the right labels when creating for Kserve with public route', async () => { + const inferenceService = assembleInferenceService( + mockInferenceServiceModalData({ externalRoute: false }), + ); + + expect(inferenceService.metadata.labels?.['networking.knative.dev/visibility']).toBe( + 'cluster-local', + ); + + const missingExternalRoute = assembleInferenceService( + mockInferenceServiceModalData({ externalRoute: true }), + ); + + expect(missingExternalRoute.metadata.labels?.['networking.knative.dev/visibility']).toBe( + undefined, + ); + }); + + it('should have the right labels when creating for Modelmesh with public route', async () => { + const missingExternalRoute = assembleInferenceService( + mockInferenceServiceModalData({ externalRoute: true }), + undefined, + undefined, + true, + ); + + expect(missingExternalRoute.metadata.labels?.['networking.knative.dev/visibility']).toBe( + undefined, + ); + }); + it('should handle name and display name', async () => { const displayName = 'Llama model'; diff --git a/frontend/src/api/k8s/inferenceServices.ts b/frontend/src/api/k8s/inferenceServices.ts index c870136286..0e64b7656a 100644 --- a/frontend/src/api/k8s/inferenceServices.ts +++ b/frontend/src/api/k8s/inferenceServices.ts @@ -34,6 +34,7 @@ export const assembleInferenceService = ( maxReplicas, minReplicas, tokenAuth, + externalRoute, } = data; const name = editName || translateDisplayNameForK8s(data.name); const { path, dataConnection } = storage; @@ -55,6 +56,11 @@ export const assembleInferenceService = ( ...(tokenAuth && { 'security.opendatahub.io/enable-auth': 'true' }), }), }, + labels: { + ...inferenceService.metadata.labels, + ...(!isModelMesh && + !externalRoute && { 'networking.knative.dev/visibility': 'cluster-local' }), + }, }, spec: { predictor: { @@ -82,6 +88,8 @@ export const assembleInferenceService = ( namespace: project, labels: { [KnownLabels.DASHBOARD_RESOURCE]: 'true', + ...(!isModelMesh && + !externalRoute && { 'networking.knative.dev/visibility': 'cluster-local' }), }, annotations: { 'openshift.io/display-name': data.name.trim(), @@ -118,6 +126,10 @@ export const assembleInferenceService = ( delete updateInferenceService.metadata.annotations['serving.knative.openshift.io/token-auth']; } + if (externalRoute && updateInferenceService.metadata.labels) { + delete updateInferenceService.metadata.labels['networking.knative.dev/visibility']; + } + // Resource and Accelerator support for KServe if (!isModelMesh) { const resourceSettings: ContainerResources = { diff --git a/frontend/src/k8sTypes.ts b/frontend/src/k8sTypes.ts index 8abe8081e4..4b806c18c1 100644 --- a/frontend/src/k8sTypes.ts +++ b/frontend/src/k8sTypes.ts @@ -424,6 +424,10 @@ export type InferenceServiceAnnotations = Partial<{ 'security.opendatahub.io/enable-auth': string; }>; +export type InferenceServiceLabels = Partial<{ + 'networking.knative.dev/visibility': string; +}>; + export type InferenceServiceKind = K8sResourceCommon & { metadata: { name: string; @@ -495,6 +499,12 @@ export type InferenceServiceKind = K8sResourceCommon & { transitionStatus: string; }; url: string; + address?: { + CACerts?: string; + audience?: string; + name?: string; + url?: string; + }; }; }; diff --git a/frontend/src/pages/modelServing/screens/global/InferenceServiceEndpoint.tsx b/frontend/src/pages/modelServing/screens/global/InferenceServiceEndpoint.tsx index 4acbf367e4..1ab73f040a 100644 --- a/frontend/src/pages/modelServing/screens/global/InferenceServiceEndpoint.tsx +++ b/frontend/src/pages/modelServing/screens/global/InferenceServiceEndpoint.tsx @@ -8,7 +8,10 @@ import { Skeleton, } from '@patternfly/react-core'; import { InferenceServiceKind, ServingRuntimeKind } from '~/k8sTypes'; -import { isServingRuntimeRouteEnabled } from '~/pages/modelServing/screens/projects/utils'; +import { + isServingRuntimeRouteEnabled, + isInferenceServiceRouteEnabled, +} from '~/pages/modelServing/screens/projects/utils'; import useRouteForInferenceService from './useRouteForInferenceService'; import InternalServicePopoverContent from './InternalServicePopoverContent'; @@ -23,8 +26,9 @@ const InferenceServiceEndpoint: React.FC = ({ servingRuntime, isKserve, }) => { - const isRouteEnabled = - servingRuntime !== undefined && isServingRuntimeRouteEnabled(servingRuntime); + const isRouteEnabled = !isKserve + ? servingRuntime !== undefined && isServingRuntimeRouteEnabled(servingRuntime) + : isInferenceServiceRouteEnabled(inferenceService); const [routeLink, loaded, loadError] = useRouteForInferenceService( inferenceService, @@ -32,13 +36,15 @@ const InferenceServiceEndpoint: React.FC = ({ isKserve, ); - if (!isKserve && !isRouteEnabled) { + if (!isRouteEnabled) { return ( } + bodyContent={ + + } >