Skip to content

Commit

Permalink
Enhance status checking in with notebooks deployed in internal regist…
Browse files Browse the repository at this point in the history
…ries
  • Loading branch information
lucferbux authored and andrewballantyne committed May 31, 2024
1 parent 28efbe4 commit 09aab22
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 70 deletions.
6 changes: 4 additions & 2 deletions frontend/src/__mocks__/mockImageStreamK8sResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ type MockResourceConfigType = {
name?: string;
namespace?: string;
displayName?: string;
imageTag?: string;
opts?: RecursivePartial<ImageStreamKind>;
};

export const mockImageStreamK8sResource = ({
name = 'test-imagestream',
namespace = 'test-project',
displayName = 'Test Image',
imageTag = 'quay.io/opendatahub/notebooks@sha256:a138838e1c9acd7708462e420bf939e03296b97e9cf6c0aa0fd9a5d20361ab75',
opts = {},
}: MockResourceConfigType): ImageStreamKind =>
_.mergeWith(
Expand Down Expand Up @@ -56,7 +58,7 @@ export const mockImageStreamK8sResource = ({
},
from: {
kind: 'DockerImage',
name: 'quay.io/opendatahub/notebooks@sha256:a138838e1c9acd7708462e420bf939e03296b97e9cf6c0aa0fd9a5d20361ab75',
name: imageTag,
},
},
],
Expand All @@ -72,7 +74,7 @@ export const mockImageStreamK8sResource = ({
created: '2023-06-30T15:07:36Z',
dockerImageReference:
'quay.io/opendatahub/notebooks@sha256:a138838e1c9acd7708462e420bf939e03296b97e9cf6c0aa0fd9a5d20361ab75',
image: 'sha256:a138838e1c9acd7708462e420bf939e03296b97e9cf6c0aa0fd9a5d20361ab75',
image: imageTag,
generation: 2,
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ import { projectDetails } from '~/__tests__/cypress/cypress/pages/projects';
type HandlersProps = {
isEmpty?: boolean;
imageStreamName?: string;
imageStreamTag?: string;
disableKServeConfig?: boolean;
disableKServeMetrics?: boolean;
disableModelConfig?: boolean;
isEnabled?: string;
isUnknown?: boolean;
};

const initIntercepts = ({
isEmpty = false,
imageStreamName = 'test-image',
imageStreamTag = 'latest',
isEnabled = 'true',
isUnknown = false,
}: HandlersProps) => {
Expand Down Expand Up @@ -99,14 +104,14 @@ const initIntercepts = ({
spec: {
tags: [
{
name: 'latest',
name: imageStreamTag,
},
],
},
status: {
tags: [
{
tag: 'latest',
tag: imageStreamTag,
},
],
},
Expand Down Expand Up @@ -185,7 +190,7 @@ describe('Project Details', () => {
});

it('Notebook with deleted image', () => {
initIntercepts({ imageStreamName: 'test' });
initIntercepts({ imageStreamName: 'test', imageStreamTag: 'failing-tag' });
projectDetails.visit('test-project');
const notebookRow = projectDetails.getNotebookRow('test-notebook');
notebookRow.shouldHaveNotebookImageName('Test image');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { mockNotebookK8sResource } from '~/__mocks__';
import { mockImageStreamK8sResource } from '~/__mocks__/mockImageStreamK8sResource';
import { getNotebookImageData } from '~/pages/projects/screens/detail/notebooks/useNotebookImageData';
import { NotebookImageAvailability } from '~/pages/projects/screens/detail/notebooks/const';

describe('getNotebookImageData', () => {
it('should return image data when image stream exists and image version exists', () => {
const notebook = mockNotebookK8sResource({
image:
'quay.io/opendatahub/notebooks@sha256:a138838e1c9acd7708462e420bf939e03296b97e9cf6c0aa0fd9a5d20361ab75',
});
const images = [
mockImageStreamK8sResource({
imageTag:
'quay.io/opendatahub/notebooks@sha256:a138838e1c9acd7708462e420bf939e03296b97e9cf6c0aa0fd9a5d20361ab75',
}),
];
const result = getNotebookImageData(notebook, images);
expect(result?.imageAvailability).toBe(NotebookImageAvailability.ENABLED);
});

it('should return image data when image stream exists and image version does not exist', () => {
const notebook = mockNotebookK8sResource({
image:
'quay.io/opendatahub/notebooks@sha256:a138838e1c9acd7708462e420bf939e03296b97e9cf6c0aa0fd9a5d20361ab75',
});
const images = [
mockImageStreamK8sResource({
imageTag: 'quay.io/opendatahub/notebooks@sha256:invalid',
}),
];
const result = getNotebookImageData(notebook, images);
expect(result?.imageAvailability).toBe(NotebookImageAvailability.DELETED);
});
});
Original file line number Diff line number Diff line change
@@ -1,87 +1,91 @@
import * as React from 'react';
import { NotebookKind } from '~/k8sTypes';
import { ImageStreamKind, NotebookKind } from '~/k8sTypes';
import useNamespaces from '~/pages/notebookController/useNamespaces';
import useImageStreams from '~/pages/projects/screens/spawner/useImageStreams';
import { PodContainer } from '~/types';
import { getImageStreamDisplayName } from '~/pages/projects/screens/spawner/spawnerUtils';
import { NotebookImageAvailability } from './const';
import { NotebookImageData } from './types';

const useNotebookImageData = (notebook?: NotebookKind): NotebookImageData => {
const { dashboardNamespace } = useNamespaces();
const [images, loaded, loadError] = useImageStreams(dashboardNamespace, true);
export const getNotebookImageData = (
notebook: NotebookKind,
images: ImageStreamKind[],
): NotebookImageData[0] => {
const container: PodContainer | undefined = notebook.spec.template.spec.containers.find(
(currentContainer) => currentContainer.name === notebook.metadata.name,
);
const imageTag = container?.image.split('/').at(-1)?.split(':');

return React.useMemo(() => {
if (!loaded || !notebook) {
return [null, false, loadError];
}
// if image could not be parsed from the container, consider it deleted because the image tag is invalid
if (!imageTag || imageTag.length < 2 || !container) {
return {
imageAvailability: NotebookImageAvailability.DELETED,
};
}

const container: PodContainer | undefined = notebook.spec.template.spec.containers.find(
(currentContainer) => currentContainer.name === notebook.metadata.name,
);
const imageTag = container?.image.split('/').at(-1)?.split(':');
const [, versionName] = imageTag;
const imageStream = images.find((image) =>
image.spec.tags
? image.spec.tags.find(
(version) => version.name === versionName || version.from?.name === container.image,
)
: false,
);

// if image could not be parsed from the container, consider it deleted because the image tag is invalid
if (!imageTag || imageTag.length < 2 || !container) {
return [
{
imageAvailability: NotebookImageAvailability.DELETED,
},
true,
undefined,
];
}
// if the image stream is not found, consider it deleted
if (!imageStream) {
// Get the image display name from the notebook metadata if we can't find the image stream. (this is a fallback and could still be undefined)
const imageDisplayName = notebook.metadata.annotations?.['opendatahub.io/image-display-name'];

const [imageName, versionName] = imageTag;
const imageStream = images.find((image) => image.metadata.name === imageName);
return {
imageAvailability: NotebookImageAvailability.DELETED,
imageDisplayName,
};
}

// if the image stream is not found, consider it deleted
if (!imageStream) {
// Get the image display name from the notebook metadata if we can't find the image stream. (this is a fallback and could still be undefined)
const imageDisplayName = notebook.metadata.annotations?.['opendatahub.io/image-display-name'];
const versions = imageStream.spec.tags || [];
const imageVersion = versions.find(
(version) => version.name === versionName || version.from?.name === container.image,
);

return [
{
imageAvailability: NotebookImageAvailability.DELETED,
imageDisplayName,
},
true,
undefined,
];
}
// because the image stream was found, get its display name
const imageDisplayName = getImageStreamDisplayName(imageStream);

// if the image version is not found, consider the image stream deleted
if (!imageVersion) {
return {
imageAvailability: NotebookImageAvailability.DELETED,
imageDisplayName,
};
}

const versions = imageStream.spec.tags || [];
const imageVersion = versions.find((version) => version.name === versionName);
// if the image stream exists and the image version exists, return the image data
return {
imageStream,
imageVersion,
imageAvailability:
imageStream.metadata.labels?.['opendatahub.io/notebook-image'] === 'true'
? NotebookImageAvailability.ENABLED
: NotebookImageAvailability.DISABLED,
imageDisplayName,
};
};

const useNotebookImageData = (notebook?: NotebookKind): NotebookImageData => {
const { dashboardNamespace } = useNamespaces();
const [images, loaded, loadError] = useImageStreams(dashboardNamespace, true);

// because the image stream was found, get its display name
const imageDisplayName = getImageStreamDisplayName(imageStream);
return React.useMemo(() => {
if (!loaded || !notebook) {
return [null, false, loadError];
}
const data = getNotebookImageData(notebook, images);

// if the image version is not found, consider the image stream deleted
if (!imageVersion) {
return [
{
imageAvailability: NotebookImageAvailability.DELETED,
imageDisplayName,
},
true,
undefined,
];
if (data === null) {
return [null, false, loadError];
}

// if the image stream exists and the image version exists, return the image data
return [
{
imageStream,
imageVersion,
imageAvailability:
imageStream.metadata.labels?.['opendatahub.io/notebook-image'] === 'true'
? NotebookImageAvailability.ENABLED
: NotebookImageAvailability.DISABLED,
imageDisplayName,
},
true,
undefined,
];
return [data, true, undefined];
}, [images, notebook, loaded, loadError]);
};

Expand Down

0 comments on commit 09aab22

Please sign in to comment.