Skip to content

Commit

Permalink
Added accelerator edit page
Browse files Browse the repository at this point in the history
clean up

separated components

fixed js key error

fix tests

tooltips to popover and direction of popup

wording fixes

fixes

removed popover for toleration table
  • Loading branch information
Gkrumbach07 committed Nov 10, 2023
1 parent 0b7492e commit 79d8b86
Show file tree
Hide file tree
Showing 31 changed files with 1,565 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { KubeFastifyInstance, AcceleratorKind } from '../../../types';
import { FastifyRequest } from 'fastify';
import createError from 'http-errors';
import { translateDisplayNameForK8s } from '../../../utils/resourceUtils';

export const getAcceleratorProfiles = async (
fastify: KubeFastifyInstance,
): Promise<AcceleratorKind[]> => {
const customObjectsApi = fastify.kube.customObjectsApi;
const namespace = fastify.kube.namespace;
return await customObjectsApi
.listNamespacedCustomObject('dashboard.opendatahub.io', 'v1', namespace, 'acceleratorprofiles')
.then((res) => {
const list = (
res?.body as {
items: AcceleratorKind[];
}
).items;
return list;
})
.catch((e) => {
fastify.log.error(e);
return [];
});
};

export const postAcceleratorProfile = async (
fastify: KubeFastifyInstance,
request: FastifyRequest,
): Promise<{ success: boolean; error: string }> => {
const customObjectsApi = fastify.kube.customObjectsApi;
const namespace = fastify.kube.namespace;
const body = request.body as AcceleratorKind['spec'];

const newResourceName = translateDisplayNameForK8s(body.displayName);
const acceleratorProfiles = (await getAcceleratorProfiles(fastify)) as AcceleratorKind[];
const validName = acceleratorProfiles.filter((ap) => ap.metadata.name === newResourceName);

if (validName.length > 0) {
fastify.log.error('Duplicate name unable to add accelerator profile');
return { success: false, error: 'Unable to add accelerator profile: ' + newResourceName };
}

const payload: AcceleratorKind = {
apiVersion: 'dashboard.opendatahub.io/v1',
kind: 'AcceleratorProfile',
metadata: {
name: newResourceName,
namespace: namespace,
annotations: {
'opendatahub.io/modified-date': new Date().toISOString(),
},
},
spec: body,
};

try {
await customObjectsApi
.createNamespacedCustomObject(
'dashboard.opendatahub.io',
'v1',
namespace,
'acceleratorprofiles',
payload,
)
.catch((e) => {
throw createError(e.statusCode, e?.body?.message);
});
return { success: true, error: null };
} catch (e) {
if (e.response?.statusCode !== 404) {
fastify.log.error(e, 'Unable to add accelerator profile.');
return { success: false, error: 'Unable to add accelerator profile: ' + e.message };
}
}
};

export const deleteAcceleratorProfile = async (
fastify: KubeFastifyInstance,
request: FastifyRequest,
): Promise<{ success: boolean; error: string }> => {
const customObjectsApi = fastify.kube.customObjectsApi;
const namespace = fastify.kube.namespace;
const params = request.params as { acceleratorProfileName: string };

try {
await customObjectsApi
.deleteNamespacedCustomObject(
'dashboard.opendatahub.io',
'v1',
namespace,
'acceleratorprofiles',
params.acceleratorProfileName,
)
.catch((e) => {
throw createError(e.statusCode, e?.body?.message);
});
return { success: true, error: null };
} catch (e) {
fastify.log.error(e, 'Unable to delete accelerator profile.');
return { success: false, error: 'Unable to delete accelerator profile: ' + e.message };
}
};

export const updateAcceleratorProfile = async (
fastify: KubeFastifyInstance,
request: FastifyRequest,
): Promise<{ success: boolean; error: string }> => {
const customObjectsApi = fastify.kube.customObjectsApi;
const namespace = fastify.kube.namespace;
const params = request.params as { acceleratorProfileName: string };
const body = request.body as Partial<AcceleratorKind['spec']>;

try {
const currentProfile = await customObjectsApi
.getNamespacedCustomObject(
'dashboard.opendatahub.io',
'v1',
namespace,
'acceleratorprofiles',
params.acceleratorProfileName,
)
.then((r) => r.body as AcceleratorKind)
.catch((e) => {
throw createError(e.statusCode, e?.body?.message);
});

if (body.displayName !== undefined) {
currentProfile.spec.displayName = body.displayName;
}
if (body.enabled !== undefined) {
currentProfile.spec.enabled = body.enabled;
}
if (body.identifier !== undefined) {
currentProfile.spec.identifier = body.identifier;
}
if (body.description !== undefined) {
currentProfile.spec.description = body.description;
}
if (body.tolerations !== undefined) {
currentProfile.spec.tolerations = body.tolerations;
}

// Update the modified date annotation
currentProfile.metadata.annotations['opendatahub.io/modified-date'] = new Date().toISOString();

await customObjectsApi
.patchNamespacedCustomObject(
'dashboard.opendatahub.io',
'v1',
namespace,
'acceleratorprofiles',
params.acceleratorProfileName,
currentProfile,
undefined,
undefined,
undefined,
{
headers: { 'Content-Type': 'application/merge-patch+json' },
},
)
.catch((e) => {
throw createError(e.statusCode, e?.body?.message);
});
return { success: true, error: null };
} catch (e) {
fastify.log.error(e, 'Unable to update accelerator profile.');
return { success: false, error: 'Unable to update accelerator profile: ' + e.message };
}
};
48 changes: 48 additions & 0 deletions backend/src/routes/api/accelerator-profiles/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import { secureAdminRoute } from '../../../utils/route-security';
import {
deleteAcceleratorProfile,
postAcceleratorProfile,
updateAcceleratorProfile,
} from './acceleratorProfilesUtils';

export default async (fastify: FastifyInstance): Promise<void> => {
fastify.delete(
'/:acceleratorProfileName',
secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => {
return deleteAcceleratorProfile(fastify, request)
.then((res) => {
return res;
})
.catch((res) => {
reply.send(res);
});
}),
);

fastify.put(
'/:acceleratorProfileName',
secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => {
return updateAcceleratorProfile(fastify, request)
.then((res) => {
return res;
})
.catch((res) => {
reply.send(res);
});
}),
);

fastify.post(
'/',
secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => {
return postAcceleratorProfile(fastify, request)
.then((res) => {
return res;
})
.catch((res) => {
reply.send(res);
});
}),
);
};
8 changes: 1 addition & 7 deletions backend/src/routes/api/images/imageUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IMAGE_ANNOTATIONS } from '../../../utils/constants';
import { convertLabelsToString } from '../../../utils/componentUtils';
import { translateDisplayNameForK8s } from '../../../utils/resourceUtils';
import {
ImageStreamTag,
ImageTagInfo,
Expand All @@ -13,13 +14,6 @@ import {
import { FastifyRequest } from 'fastify';
import createError from 'http-errors';

const translateDisplayNameForK8s = (name: string): string =>
name
.trim()
.toLowerCase()
.replace(/\s/g, '-')
.replace(/[^A-Za-z0-9-]/g, '');

/**
* This function uses a regex to match the image location string
* The match result will return an array of 4 elements:
Expand Down
7 changes: 7 additions & 0 deletions backend/src/utils/resourceUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -952,3 +952,10 @@ export const migrateTemplateDisablement = async (

export const getServingRuntimeNameFromTemplate = (template: Template): string =>
template.objects[0].metadata.name;

export const translateDisplayNameForK8s = (name: string): string =>
name
.trim()
.toLowerCase()
.replace(/\s/g, '-')
.replace(/[^A-Za-z0-9-]/g, '');
50 changes: 31 additions & 19 deletions frontend/src/__mocks__/mockAcceleratorProfile.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
import _ from 'lodash';
import { AcceleratorKind } from '~/k8sTypes';
import { RecursivePartial } from '~/typeHelpers';
import { TolerationEffect, TolerationOperator } from '~/types';

export const mockAcceleratorProfile = (): AcceleratorKind => ({
apiVersion: 'dashboard.opendatahub.io/v1',
kind: 'AcceleratorProfile',
metadata: {
name: 'test-accelerator',
},
spec: {
displayName: 'test-accelerator',
enabled: true,
identifier: 'nvidia.com/gpu',
description: 'Test description',
tolerations: [
{
key: 'nvidia.com/gpu',
operator: 'Exists',
effect: 'NoSchedule',
export const mockAcceleratorProfile = (
data: RecursivePartial<AcceleratorKind> = {},
): AcceleratorKind =>
_.merge(
{
apiVersion: 'dashboard.opendatahub.io/v1',
kind: 'AcceleratorProfile',
metadata: {
name: 'test-accelerator',
annotations: {
'opendatahub.io/modified-date': '2023-10-31T21:16:11.721Z',
},
},
],
},
});
spec: {
displayName: 'Test Accelerator',
enabled: true,
identifier: 'nvidia.com/gpu',
description: 'Test description',
tolerations: [
{
key: 'nvidia.com/gpu',
operator: TolerationOperator.EXISTS,
effect: TolerationEffect.NO_SCHEDULE,
},
],
},
} as AcceleratorKind,
data,
);
32 changes: 4 additions & 28 deletions frontend/src/__mocks__/mockNotebookK8sResource.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { KnownLabels, NotebookKind } from '~/k8sTypes';
import { DEFAULT_NOTEBOOK_SIZES } from '~/pages/projects/screens/spawner/const';
import { ContainerResources } from '~/types';
import { ContainerResources, TolerationEffect, TolerationOperator } from '~/types';
import { genUID } from '~/__mocks__/mockUtils';

type MockResourceConfigType = {
Expand Down Expand Up @@ -135,23 +135,6 @@ export const mockNotebookK8sResource = ({
workingDir: '/opt/app-root/src',
},
{
args: [
'--provider=openshift',
'--https-address=:8443',
'--http-address=',
'--openshift-service-account=workbench',
'--cookie-secret-file=/etc/oauth/config/cookie_secret',
'--cookie-expire=24h0m0s',
'--tls-cert=/etc/tls/private/tls.crt',
'--tls-key=/etc/tls/private/tls.key',
'--upstream=http://localhost:8888',
'--upstream-ca=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt',
'--skip-auth-regex=^(?:/notebook/$(NAMESPACE)/workbench)?/api$',
'--email-domain=*',
'--skip-provider-button',
'--openshift-sar={"verb":"get","resource":"notebooks","resourceAPIGroup":"kubeflow.org","resourceName":"workbench","namespace":"$(NAMESPACE)"}',
'--logout-url=http://localhost:4010/projects/project?notebookLogout=workbench',
],
env: [
{
name: 'NAMESPACE',
Expand Down Expand Up @@ -220,12 +203,11 @@ export const mockNotebookK8sResource = ({
},
],
enableServiceLinks: false,
serviceAccountName: name,
tolerations: [
{
effect: 'NoSchedule',
effect: TolerationEffect.NO_SCHEDULE,
key: 'NotebooksOnlyChange',
operator: 'Exists',
operator: TolerationOperator.EXISTS,
},
],
volumes: [
Expand All @@ -238,14 +220,12 @@ export const mockNotebookK8sResource = ({
{
name: 'oauth-config',
secret: {
defaultMode: 420,
secretName: 'workbench-oauth-config',
},
},
{
name: 'tls-certificates',
secret: {
defaultMode: 420,
secretName: 'workbench-tls',
},
},
Expand Down Expand Up @@ -284,11 +264,7 @@ export const mockNotebookK8sResource = ({
type: 'Waiting',
},
],
containerState: {
running: {
startedAt: '2023-02-14T22:06:52Z',
},
},
containerState: {},
readyReplicas: 1,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { mockK8sResourceList } from '~/__mocks__/mockK8sResourceList';
import useDetectUser from '~/utilities/useDetectUser';
import { mockStatus } from '~/__mocks__/mockStatus';

import AcceleratorProfiles from '~/pages/acceleratorProfiles/AcceleratorProfiles';
import AcceleratorProfiles from '~/pages/acceleratorProfiles/screens/list/AcceleratorProfiles';

export default {
component: AcceleratorProfiles,
Expand Down
Loading

0 comments on commit 79d8b86

Please sign in to comment.