Skip to content

Commit

Permalink
Merge branch 'cypress-RHOAIENG-15765' of https://github.com/antowaddl…
Browse files Browse the repository at this point in the history
…e/odh-dashboard into cypress-RHOAIENG-15765
  • Loading branch information
antowaddle committed Dec 12, 2024
2 parents 65b62d4 + 4ef29dd commit 9fbf4d3
Show file tree
Hide file tree
Showing 71 changed files with 2,507 additions and 569 deletions.
17 changes: 15 additions & 2 deletions backend/src/routes/api/status/adminAllowedUsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getUserInfo } from '../../../utils/userUtils';
import {
getAdminUserList,
getAllowedUserList,
getClusterAdminUserList,
isUserAdmin,
KUBE_SAFE_PREFIX,
} from '../../../utils/adminUtils';
Expand Down Expand Up @@ -77,22 +78,34 @@ export const getAllowedUsers = async (
return [];
}

const activityMap = await getUserActivityFromNotebook(fastify, namespace);

const withNotebookUsers = Object.keys(activityMap);
const adminUsers = await getAdminUserList(fastify);
const allowedUsers = await getAllowedUserList(fastify);
const activityMap = await getUserActivityFromNotebook(fastify, namespace);
// get cluster admins that have a notebook
const clusterAdminUsers = (await getClusterAdminUserList(fastify)).filter((user) =>
withNotebookUsers.includes(user),
);

const usersWithNotebooksMap: AllowedUserMap = convertUserListToMap(
Object.keys(activityMap),
withNotebookUsers,
'User',
activityMap,
);
const allowedUsersMap: AllowedUserMap = convertUserListToMap(allowedUsers, 'User', activityMap);
const adminUsersMap: AllowedUserMap = convertUserListToMap(adminUsers, 'Admin', activityMap);
const clusterAdminUsersMap: AllowedUserMap = convertUserListToMap(
clusterAdminUsers,
'Admin',
activityMap,
);

const returnUsers: AllowedUserMap = {
...usersWithNotebooksMap,
...allowedUsersMap,
...adminUsersMap,
...clusterAdminUsersMap,
};
return Object.values(returnUsers);
};
7 changes: 7 additions & 0 deletions backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ export type DashboardConfig = K8sResourceCommon & {
disableKServeRaw: boolean;
disableModelMesh: boolean;
disableAcceleratorProfiles: boolean;
disableHardwareProfiles: boolean;
disableDistributedWorkloads: boolean;
disableModelRegistry: boolean;
disableModelRegistrySecureDB: boolean;
disableServingRuntimeParams: boolean;
disableConnectionTypes: boolean;
disableStorageClasses: boolean;
Expand Down Expand Up @@ -1254,3 +1256,8 @@ export type NIMAccountKind = K8sResourceCommon & {
conditions?: K8sCondition[];
};
};

export type ResourceAccessReviewResponse = {
groups?: string[];
users?: string[];
};
36 changes: 33 additions & 3 deletions backend/src/utils/adminUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import {
V1ClusterRoleBinding,
V1ClusterRoleBindingList,
} from '@kubernetes/client-node';
import { KubeFastifyInstance } from '../types';
import { KubeFastifyInstance, ResourceAccessReviewResponse } from '../types';
import { getAdminGroups, getAllGroupsByUser, getAllowedGroups, getGroup } from './groupsUtils';
import { flatten, uniq } from 'lodash';
import { getNamespaces } from '../utils/notebookUtils';

const SYSTEM_AUTHENTICATED = 'system:authenticated';
/** Usernames with invalid characters can start with `b64:` to keep their unwanted characters */
Expand All @@ -14,10 +15,11 @@ export const KUBE_SAFE_PREFIX = 'b64:';
const getGroupUserList = async (
fastify: KubeFastifyInstance,
groupListNames: string[],
additionalUsers: string[] = [],
): Promise<string[]> => {
const customObjectApi = fastify.kube.customObjectsApi;
return Promise.all(groupListNames.map((group) => getGroup(customObjectApi, group))).then(
(usersPerGroup: string[][]) => uniq(flatten(usersPerGroup)),
(usersPerGroup: string[][]) => uniq([...flatten(usersPerGroup), ...additionalUsers]),
);
};

Expand All @@ -26,10 +28,38 @@ export const getAdminUserList = async (fastify: KubeFastifyInstance): Promise<st
const adminGroupsList = adminGroups
.split(',')
.filter((groupName) => groupName && !groupName.startsWith('system:')); // Handle edge-cases and ignore k8s defaults
adminGroupsList.push('cluster-admins');

return getGroupUserList(fastify, adminGroupsList);
};

export const getClusterAdminUserList = async (fastify: KubeFastifyInstance): Promise<string[]> => {
// fetch all the users and groups who have cluster-admin role and put them into the admin user list
const { notebookNamespace } = getNamespaces(fastify);
const clusterAdminUsersAndGroups = await fastify.kube.customObjectsApi
// This is not actually fetching all the groups who have admin access to the notebook resources
// But only the cluster admins
// The "*" in the verb field is more like a placeholder
.createClusterCustomObject('authorization.openshift.io', 'v1', 'resourceaccessreviews', {
resource: 'notebooks',
resourceAPIGroup: 'kubeflow.org',
resourceAPIVersion: 'v1',
verb: '*',
namespace: notebookNamespace,
})
.then((rar) => rar.body as ResourceAccessReviewResponse)
.catch((e) => {
fastify.log.error(`Failure to fetch cluster admin users and groups: ${e.response.body}`);
return { users: [], groups: [] };
});
const clusterAdminUsers = clusterAdminUsersAndGroups.users || [];
const clusterAdminGroups = clusterAdminUsersAndGroups.groups || [];
const filteredClusterAdminGroups = clusterAdminGroups.filter(
(group) => !group.startsWith('system:'),
);
const filteredClusterAdminUsers = clusterAdminUsers.filter((user) => !user.startsWith('system:'));
return getGroupUserList(fastify, filteredClusterAdminGroups, filteredClusterAdminUsers);
};

export const getAllowedUserList = async (fastify: KubeFastifyInstance): Promise<string[]> => {
const allowedGroups = getAllowedGroups();
const allowedGroupList = allowedGroups
Expand Down
2 changes: 2 additions & 0 deletions backend/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ export const blankDashboardCR: DashboardConfig = {
disableKServeRaw: true,
disableModelMesh: false,
disableAcceleratorProfiles: false,
disableHardwareProfiles: true,
disableDistributedWorkloads: false,
disableModelRegistry: false,
disableModelRegistrySecureDB: true,
disableServingRuntimeParams: false,
disableConnectionTypes: false,
disableStorageClasses: false,
Expand Down
4 changes: 4 additions & 0 deletions docs/dashboard-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ The following are a list of features that are supported, along with there defaul
| disableKServeRaw | true | Disables the option to deploy in raw instead of serverless. |
| disableModelMesh | false | Disables the ability to select ModelMesh as a Serving Platform. |
| disableAcceleratorProfiles | false | Disables Accelerator profiles from the Admin Panel. |
| disableHardwareProfiles | true | Disables Hardware profiles from the Admin Panel. |
| disableTrustyBiasMetrics | false | Disables Model Bias tab from Model Serving metrics. |
| disablePerformanceMetrics | false | Disables Endpoint Performance tab from Model Serving metrics. |
| disableDistributedWorkloads | false | Disables Distributed Workload Metrics from the dashboard. |
| disableModelRegistry | false | Disables Model Registry from the dashboard. |
| disableModelRegistrySecureDB | true | Disables Model Registry Secure DB from the dashboard. |
| disableServingRuntimeParams | false | Disables Serving Runtime params from the dashboard. |
| disableStorageClasses | false | Disables storage classes settings nav item from the dashboard. |
| disableNIMModelServing | true | Disables components of NIM Model UI from the dashboard. |
Expand Down Expand Up @@ -62,6 +64,7 @@ spec:
disableProjectSharing: false
disableCustomServingRuntimes: false
disableAcceleratorProfiles: false
disableHardwareProfiles: true
disableKServeMetrics: false
disableTrustyBiasMetrics: false
disablePerformanceMetrics: false
Expand Down Expand Up @@ -158,6 +161,7 @@ spec:
disableProjectSharing: true
disableCustomServingRuntimes: false
disableAcceleratorProfiles: true
disableHardwareProfiles: true
disableKServeMetrics: true
disableTrustyBiasMetrics: false
disablePerformanceMetrics: false
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/__mocks__/mockDashboardConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ export type MockDashboardConfigType = {
disableKServeRaw?: boolean;
disableModelMesh?: boolean;
disableAcceleratorProfiles?: boolean;
disableHardwareProfiles?: boolean;
disablePerformanceMetrics?: boolean;
disableTrustyBiasMetrics?: boolean;
disableDistributedWorkloads?: boolean;
disableModelRegistry?: boolean;
disableModelRegistrySecureDB?: boolean;
disableServingRuntimeParams?: boolean;
disableConnectionTypes?: boolean;
disableStorageClasses?: boolean;
Expand Down Expand Up @@ -53,10 +55,12 @@ export const mockDashboardConfig = ({
disableKServeRaw = true,
disableModelMesh = false,
disableAcceleratorProfiles = false,
disableHardwareProfiles = false,
disablePerformanceMetrics = false,
disableTrustyBiasMetrics = false,
disableDistributedWorkloads = false,
disableModelRegistry = false,
disableModelRegistrySecureDB = true,
disableServingRuntimeParams = false,
disableConnectionTypes = true,
disableStorageClasses = false,
Expand Down Expand Up @@ -164,8 +168,10 @@ export const mockDashboardConfig = ({
disableKServeRaw,
disableModelMesh,
disableAcceleratorProfiles,
disableHardwareProfiles,
disableDistributedWorkloads,
disableModelRegistry,
disableModelRegistrySecureDB,
disableServingRuntimeParams,
disableConnectionTypes,
disableStorageClasses,
Expand Down
71 changes: 71 additions & 0 deletions frontend/src/__mocks__/mockHardwareProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { HardwareProfileKind } from '~/k8sTypes';
import {
Identifier,
NodeSelector,
Toleration,
TolerationEffect,
TolerationOperator,
} from '~/types';
import { genUID } from './mockUtils';

type MockResourceConfigType = {
name?: string;
namespace?: string;
uid?: string;
displayName?: string;
identifiers?: Identifier[];
description?: string;
enabled?: boolean;
nodeSelectors?: NodeSelector[];
tolerations?: Toleration[];
};

export const mockHardwareProfile = ({
name = 'migrated-gpu',
namespace = 'test-project',
uid = genUID('service'),
displayName = 'Nvidia GPU',
identifiers = [
{
displayName: 'Memory',
identifier: 'memory',
minCount: 5,
maxCount: 2,
defaultCount: 2,
},
],
description = '',
enabled = true,
tolerations = [
{
key: 'nvidia.com/gpu',
operator: TolerationOperator.EXISTS,
effect: TolerationEffect.NO_SCHEDULE,
},
],
nodeSelectors = [
{
key: 'test',
value: 'va;ue',
},
],
}: MockResourceConfigType): HardwareProfileKind => ({
apiVersion: 'dashboard.opendatahub.io/v1alpha1',
kind: 'HardwareProfile',
metadata: {
creationTimestamp: '2023-03-17T16:12:41Z',
generation: 1,
name,
namespace,
resourceVersion: '1309350',
uid,
},
spec: {
identifiers,
displayName,
enabled,
tolerations,
nodeSelectors,
description,
},
});
11 changes: 10 additions & 1 deletion frontend/src/__mocks__/mockNotebookK8sResource.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import * as _ from 'lodash-es';
import { KnownLabels, NotebookKind } from '~/k8sTypes';
import { DEFAULT_NOTEBOOK_SIZES } from '~/pages/projects/screens/spawner/const';
import { ContainerResources, TolerationEffect, TolerationOperator, VolumeMount } from '~/types';
import {
ContainerResources,
TolerationEffect,
TolerationOperator,
Volume,
VolumeMount,
} from '~/types';
import { genUID } from '~/__mocks__/mockUtils';
import { RecursivePartial } from '~/typeHelpers';
import { EnvironmentFromVariable } from '~/pages/projects/types';
Expand All @@ -19,6 +25,7 @@ type MockResourceConfigType = {
opts?: RecursivePartial<NotebookKind>;
uid?: string;
additionalVolumeMounts?: VolumeMount[];
additionalVolumes?: Volume[];
};

export const mockNotebookK8sResource = ({
Expand All @@ -41,6 +48,7 @@ export const mockNotebookK8sResource = ({
opts = {},
uid = genUID('notebook'),
additionalVolumeMounts = [],
additionalVolumes = [],
}: MockResourceConfigType): NotebookKind =>
_.merge(
{
Expand Down Expand Up @@ -257,6 +265,7 @@ export const mockNotebookK8sResource = ({
secretName: 'workbench-tls',
},
},
...additionalVolumes,
],
},
},
Expand Down
34 changes: 27 additions & 7 deletions frontend/src/__tests__/cypress/cypress/pages/clusterStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class ClusterStorageRow extends TableRow {
}

findConnectedWorkbenches() {
return this.find().find('[data-label="Connected workbenches"]');
return this.find().find('[data-label="Workbench connections"]');
}

toggleExpandableContent() {
Expand Down Expand Up @@ -55,14 +55,29 @@ class ClusterStorageModal extends Modal {
super(edit ? 'Update cluster storage' : 'Add cluster storage');
}

findWorkbenchConnectionSelect() {
return this.find()
.findByTestId('connect-existing-workbench-group')
.findByRole('button', { name: 'Typeahead menu toggle', hidden: true });
findAddWorkbenchButton() {
return cy.findByTestId('add-workbench-button');
}

findMountField() {
return this.find().findByTestId('mount-path-folder-value');
findWorkbenchTable() {
return cy.findByTestId('workbench-connection-table');
}

selectWorkbenchName(row: number, name: string) {
this.findWorkbenchTable().find(`[data-label=Name]`).eq(row).find('button').click();
cy.findByRole('option', { name, hidden: true }).click();
}

selectCustomPathFormat(row: number) {
this.findWorkbenchTable().find(`[data-label="Path format"]`).eq(row).find('button').click();
cy.findByRole('option', {
name: 'Custom Custom paths that do not begin with /opt/app-root/src/ are not visible in the JupyterLab file browser.',
hidden: true,
}).click();
}

findMountPathField(row: number) {
return this.findWorkbenchTable().find(`[data-label="Mount path"]`).eq(row).find('input');
}

findMountFieldHelperText() {
Expand Down Expand Up @@ -127,6 +142,11 @@ class ClusterStorageModal extends Modal {
return this.find().findByTestId('storage-classes-selector');
}

selectStorageClassSelectOption(name: string | RegExp) {
this.findStorageClassSelect().click();
cy.findByRole('option', { name, hidden: true }).click();
}

findStorageClassOption(name: string) {
return cy.get('#storage-classes-selector').findByText(name);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ModelVersionDeployModal extends Modal {

selectProjectByName(name: string) {
this.findProjectSelector().click();
this.find().findByRole('option', { name }).click();
this.find().findByRole('option', { name, timeout: 5000 }).click();
}
}

Expand Down
Loading

0 comments on commit 9fbf4d3

Please sign in to comment.