Skip to content

Commit

Permalink
Merge pull request #2002 from dpanshug/accelerator-section
Browse files Browse the repository at this point in the history
Add Accelerator section in Admin Panel and feature flag
  • Loading branch information
openshift-ci[bot] authored Oct 31, 2023
2 parents c8595db + 7f8e230 commit d15f8aa
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 46 deletions.
11 changes: 5 additions & 6 deletions backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type DashboardConfig = K8sResourceCommon & {
disableCustomServingRuntimes: boolean;
modelMetricsNamespace: string;
disablePipelines: boolean;
disableAcceleratorProfiles: boolean;
};
groupsConfig?: {
adminGroups: string;
Expand Down Expand Up @@ -254,7 +255,6 @@ export type KubeDecorator = KubeStatus & {
customObjectsApi: k8s.CustomObjectsApi;
rbac: k8s.RbacAuthorizationV1Api;
currentToken: string;

};

export type KubeFastifyInstance = FastifyInstance & {
Expand Down Expand Up @@ -759,10 +759,10 @@ export type GPUInfo = {

export type AcceleratorInfo = {
configured: boolean;
available: {[key: string]: number};
total: {[key: string]: number};
allocated: {[key: string]: number};
}
available: { [key: string]: number };
total: { [key: string]: number };
allocated: { [key: string]: number };
};

export type EnvironmentVariable = EitherNotBoth<
{ value: string | number },
Expand Down Expand Up @@ -882,7 +882,6 @@ export type SupportedModelFormats = {
autoSelect?: boolean;
};


export enum ContainerResourceAttributes {
CPU = 'cpu',
MEMORY = 'memory',
Expand Down
1 change: 1 addition & 0 deletions backend/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const blankDashboardCR: DashboardConfig = {
disableCustomServingRuntimes: false,
modelMetricsNamespace: '',
disablePipelines: false,
disableAcceleratorProfiles: false,
},
notebookController: {
enabled: true,
Expand Down
50 changes: 26 additions & 24 deletions docs/dashboard-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

# Dashboard Config

By default the ODH Dashboard comes with a set of core features enabled that are design to work for most scenarios. The dashboard can be configured from its OdhDashboard CR, `odh-dashboard-config`.
By default the ODH Dashboard comes with a set of core features enabled that are design to work for most scenarios. The dashboard can be configured from its OdhDashboard CR, `odh-dashboard-config`.

## Features

The following are a list of features that are supported, along with there default settings.

| Feature | Default | Description |
|------------------------------|---------|------------------------------------------------------------------------------------------------------|
| ---------------------------- | ------- | ---------------------------------------------------------------------------------------------------- |
| enablement | true | Enables the ability to enable ISVs to the dashboard |
| disableInfo | false | Removes the information panel in Explore Application section |
| disableSupport | false | Disables components related to support. |
Expand All @@ -24,6 +24,7 @@ The following are a list of features that are supported, along with there defaul
| disableModelServing | false | Disables Model Serving from the dashboard and from Data Science Projects. |
| disableProjectSharing | false | Disables Project Sharing from Data Science Projects. |
| disableCustomServingRuntimes | false | Disables Custom Serving Runtimes from the Admin Panel. |
| disableAcceleratorProfiles | false | Disables Accelerator profiles from the Admin Panel. |
| modelMetricsNamespace | false | Enables the namespace in which the Model Serving Metrics' Prometheus Operator is installed. |

## Defaults
Expand All @@ -47,6 +48,7 @@ spec:
disableModelServing: false
disableProjectSharing: false
disableCustomServingRuntimes: false
disableAcceleratorProfiles: false
modelMetricsNamespace: ''
```
Expand All @@ -72,12 +74,12 @@ Note: These sizes must follow conventions such as requests smaller than limits

```yaml
notebookSizes:
- name: XSmall
resources:
requests:
- name: XSmall
resources:
requests:
memory: 0.5Gi
cpu: '0.1'
limits:
limits:
memory: 2Gi
cpu: '0.1'
```
Expand All @@ -103,11 +105,11 @@ We make use of the Notebook resource as a source of truth for what the user has

New annotations we created are:

| Annotation name | What it represents |
| --------------- | ------------------ |
| `opendatahub.io/username` | The untranslated username behind the notebook`*` |
| `notebooks.opendatahub.io/last-image-selection` | The last image the user selected (on create notebook) |
| `notebooks.opendatahub.io/last-size-selection` | The last notebook size the user selected (on create notebook) |
| Annotation name | What it represents |
| ----------------------------------------------- | ------------------------------------------------------------- |
| `opendatahub.io/username` | The untranslated username behind the notebook`*` |
| `notebooks.opendatahub.io/last-image-selection` | The last image the user selected (on create notebook) |
| `notebooks.opendatahub.io/last-size-selection` | The last notebook size the user selected (on create notebook) |

`*` - We need the original user's name (we translate their name to kube safe characters for notebook name and for the label) for some functionality. If this is omitted from the Notebook (or they don't have one yet) we try to make a validation against the current logged in user. This will work most of the time (and we assume logged in user when they don't have a Notebook), if this fails because you're an Admin and we don't have this state, we consider this an invalid state - should be rare though as it requires the subset of users that are Admins to have a bad-state Notebook they are trying to impersonate (to start or view that users Notebook information).

Expand Down Expand Up @@ -136,58 +138,59 @@ spec:
disableModelServing: true
disableProjectSharing: true
disableCustomServingRuntimes: false
disableAcceleratorProfiles: true
modelMetricsNamespace: ''
notebookController:
enabled: true
notebookSizes:
- name: Small
resources:
limits:
cpu: "2"
cpu: '2'
memory: 2Gi
requests:
cpu: "1"
cpu: '1'
memory: 1Gi
- name: Medium
resources:
limits:
cpu: "4"
cpu: '4'
memory: 4Gi
requests:
cpu: "2"
cpu: '2'
memory: 2Gi
- name: Large
resources:
limits:
cpu: "8"
cpu: '8'
memory: 8Gi
requests:
cpu: "4"
cpu: '4'
memory: 4Gi
modelServerSizes:
- name: Small
resources:
limits:
cpu: "2"
cpu: '2'
memory: 8Gi
requests:
cpu: "1"
cpu: '1'
memory: 4Gi
- name: Medium
resources:
limits:
cpu: "8"
cpu: '8'
memory: 10Gi
requests:
cpu: "4"
cpu: '4'
memory: 8Gi
- name: Large
resources:
limits:
cpu: "10"
cpu: '10'
memory: 20Gi
requests:
cpu: "6"
cpu: '6'
memory: 16Gi
groupsConfig:
adminGroups: 'odh-admins'
Expand All @@ -196,5 +199,4 @@ spec:
- 'ovms'
templateDisablement:
- 'ovms'
```
22 changes: 22 additions & 0 deletions frontend/src/__mocks__/mockAcceleratorProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { AcceleratorKind } from '~/k8sTypes';

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',
},
],
},
});
3 changes: 3 additions & 0 deletions frontend/src/__mocks__/mockDashboardConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type MockDashboardConfigType = {
disableProjects?: boolean;
disableModelServing?: boolean;
disableCustomServingRuntimes?: boolean;
disableAcceleratorProfiles?: boolean;
};

export const mockDashboardConfig = ({
Expand All @@ -27,6 +28,7 @@ export const mockDashboardConfig = ({
disableProjects = false,
disableModelServing = false,
disableCustomServingRuntimes = false,
disableAcceleratorProfiles = false,
}: MockDashboardConfigType): DashboardConfig => ({
apiVersion: 'opendatahub.io/v1alpha',
kind: 'OdhDashboardConfig',
Expand Down Expand Up @@ -54,6 +56,7 @@ export const mockDashboardConfig = ({
modelMetricsNamespace: 'test-project',
disablePipelines: false,
disableProjectSharing: false,
disableAcceleratorProfiles,
},
notebookController: {
enabled: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { test, expect } from '@playwright/test';
import { navigateToStory } from '~/__tests__/integration/utils';

test('Empty State no Accelerator profile', async ({ page }) => {
await page.goto(
navigateToStory(
'pages-acceleratorprofiles-acceleratorprofiles',
'empty-state-no-accelerator-profile',
),
);

// wait for page to load
await page.waitForSelector('text=No available accelerator profiles yet');

// Test that the button is enabled
await expect(page.getByRole('button', { name: 'Add new accelerator profile' })).toBeEnabled();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { StoryFn, Meta, StoryObj } from '@storybook/react';
import { rest } from 'msw';
import { mockAcceleratorProfile } from '~/__mocks__/mockAcceleratorProfile';
import { mockProjectK8sResource } from '~/__mocks__/mockProjectK8sResource';
import { mockK8sResourceList } from '~/__mocks__/mockK8sResourceList';
import useDetectUser from '~/utilities/useDetectUser';
import { mockStatus } from '~/__mocks__/mockStatus';

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

export default {
component: AcceleratorProfiles,
parameters: {
msw: {
handlers: {
status: [
rest.get('/api/k8s/apis/project.openshift.io/v1/projects', (req, res, ctx) =>
res(ctx.json(mockK8sResourceList([mockProjectK8sResource({})]))),
),
rest.get('/api/status', (req, res, ctx) => res(ctx.json(mockStatus()))),
],
accelerators: rest.get(
'/api/k8s/apis/dashboard.opendatahub.io/v1/namespaces/opendatahub/acceleratorprofiles',
(req, res, ctx) => res(ctx.json(mockK8sResourceList([mockAcceleratorProfile()]))),
),
},
},
},
} as Meta<typeof AcceleratorProfiles>;

const Template: StoryFn<typeof AcceleratorProfiles> = (args) => {
useDetectUser();
return <AcceleratorProfiles {...args} />;
};

export const EmptyStateNoAcceleratorProfile: StoryObj = {
render: Template,

parameters: {
msw: {
handlers: {
accelerators: rest.get(
'/api/k8s/apis/dashboard.opendatahub.io/v1/namespaces/opendatahub/acceleratorprofiles',
(req, res, ctx) => res(ctx.json(mockK8sResourceList([]))),
),
},
},
},
};
5 changes: 5 additions & 0 deletions frontend/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ const DependencyMissingPage = React.lazy(
() => import('../pages/dependencies/DependencyMissingPage'),
);

const AcceleratorProfiles = React.lazy(
() => import('../pages/acceleratorProfiles/AcceleratorProfiles'),
);

const AppRoutes: React.FC = () => {
const { isAdmin, isAllowed } = useUser();

Expand Down Expand Up @@ -76,6 +80,7 @@ const AppRoutes: React.FC = () => {
<>
<Route path="/notebookImages" element={<BYONImagesPage />} />
<Route path="/clusterSettings" element={<ClusterSettingsPage />} />
<Route path="/acceleratorProfiles" element={<AcceleratorProfiles />} />
<Route path="/servingRuntimes/*" element={<CustomServingRuntimeRoutes />} />
<Route path="/groupSettings" element={<GroupSettingsPage />} />
</>
Expand Down
70 changes: 70 additions & 0 deletions frontend/src/pages/acceleratorProfiles/AcceleratorProfiles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as React from 'react';
import {
Button,
ButtonVariant,
Flex,
FlexItem,
EmptyState,
EmptyStateIcon,
EmptyStateVariant,
EmptyStateBody,
PageSection,
PageSectionVariants,
Title,
} from '@patternfly/react-core';
import { PlusCircleIcon } from '@patternfly/react-icons';
import ApplicationsPage from '~/pages/ApplicationsPage';
import useAccelerators from '~/pages/notebookController/screens/server/useAccelerators';
import { useDashboardNamespace } from '~/redux/selectors';

const description = `Manage accelerator profile settings for users in your organization`;

const AcceleratorProfiles: React.FC = () => {
const { dashboardNamespace } = useDashboardNamespace();
const [accelerators, loaded, loadError] = useAccelerators(dashboardNamespace);

const isEmpty = !accelerators || accelerators.length === 0;

const noAcceleratorProfilePageSection = (
<PageSection isFilled>
<EmptyState variant={EmptyStateVariant.full} data-id="empty-empty-state">
<EmptyStateIcon icon={PlusCircleIcon} />
<Title headingLevel="h5" size="lg">
No available accelerator profiles yet
</Title>
<EmptyStateBody>
You don&apos;t have any accelerator profiles yet. To get started, please ask your cluster
administrator about the accelerator availability in your cluster and create corresponding
profiles in Openshift Data Science.
</EmptyStateBody>
<Button
data-id="display-accelerator-modal-button"
variant={ButtonVariant.primary}
onClick={() => undefined}
>
Add new accelerator profile
</Button>
</EmptyState>
</PageSection>
);

return (
<ApplicationsPage
title="Accelerator profiles"
description={description}
loaded={loaded}
empty={isEmpty}
loadError={loadError}
errorMessage="Unable to load accelerator profiles."
emptyStatePage={noAcceleratorProfilePageSection}
>
<PageSection variant={PageSectionVariants.light} padding={{ default: 'noPadding' }}>
<Flex direction={{ default: 'column' }}>
<FlexItem>{/* Todo: Create accelerator table */}</FlexItem>
</Flex>
</PageSection>
</ApplicationsPage>
);
};

export default AcceleratorProfiles;
Loading

0 comments on commit d15f8aa

Please sign in to comment.