Skip to content

Commit

Permalink
Merge branch 'opendatahub-io:main' into supportPipelineGroups
Browse files Browse the repository at this point in the history
  • Loading branch information
jenny-s51 authored Apr 30, 2024
2 parents 7dfd49a + 3aa9f3b commit 84ad10e
Show file tree
Hide file tree
Showing 36 changed files with 721 additions and 260 deletions.
46 changes: 22 additions & 24 deletions backend/src/routes/api/service/modelregistry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,29 @@ import { DEV_MODE } from '../../../../utils/constants';
import { getParam, setParam } from '../../../../utils/proxy';

export default async (fastify: KubeFastifyInstance): Promise<void> => {
if (DEV_MODE) {
fastify.register(httpProxy, {
upstream: '',
prefix: '/:name',
rewritePrefix: '',
replyOptions: {
// preHandler must set the `upstream` param
getUpstream: (request) => getParam(request, 'upstream'),
},
preHandler: (request, _, done) => {
const name = getParam(request, 'name');
fastify.register(httpProxy, {
upstream: '',
prefix: '/:name',
rewritePrefix: '',
replyOptions: {
// preHandler must set the `upstream` param
getUpstream: (request) => getParam(request, 'upstream'),
},
preHandler: (request, _, done) => {
const name = getParam(request, 'name');

const upstream = DEV_MODE
? // Use port forwarding for local development:
// kubectl port-forward -n <namespace> svc/<service-name> <local.port>:<service.port>
`http://${process.env.MODEL_REGISTRY_SERVICE_HOST}:${process.env.MODEL_REGISTRY_SERVICE_PORT}`
: // Construct service URL
`http://${name}.odh-model-registries.svc.cluster.local:8080`;
const upstream = DEV_MODE
? // Use port forwarding for local development:
// kubectl port-forward -n <namespace> svc/<service-name> <local.port>:<service.port>
`http://${process.env.MODEL_REGISTRY_SERVICE_HOST}:${process.env.MODEL_REGISTRY_SERVICE_PORT}`
: // Construct service URL
`http://${name}.odh-model-registries.svc.cluster.local:8080`;

// assign the `upstream` param so we can dynamically set the upstream URL for http-proxy
setParam(request, 'upstream', upstream);
// assign the `upstream` param so we can dynamically set the upstream URL for http-proxy
setParam(request, 'upstream', upstream);

fastify.log.info(`Proxy ${request.method} request ${request.url} to ${upstream}`);
done();
},
});
}
fastify.log.info(`Proxy ${request.method} request ${request.url} to ${upstream}`);
done();
},
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { AcceleratorProfileModel } from '~/__tests__/cypress/cypress/utils/model
import { mockK8sResourceList } from '~/__mocks__';
import { be } from '~/__tests__/cypress/cypress/utils/should';
import { asProductAdminUser } from '~/__tests__/cypress/cypress/utils/users';
import { testPagination } from '~/__tests__/cypress/cypress/utils/pagination';

type HandlersProps = {
isEmpty?: boolean;
Expand Down Expand Up @@ -45,15 +46,35 @@ describe('Accelerator Profile', () => {
acceleratorProfile.findAddButton().should('be.enabled');
});

it('list accelerator profiles and Table filtering, sorting, searching', () => {
initIntercepts({});
it('list accelerator profiles and Table filtering, sorting, searching and pagination', () => {
const totalItems = 50;
cy.interceptK8sList(
{ model: AcceleratorProfileModel, ns: 'opendatahub' },
mockK8sResourceList(
Array.from({ length: totalItems }, (_, i) =>
mockAcceleratorProfile({
displayName: `Test Accelerator - ${i}`,
identifier: 'tensor.com/gpu',
description: `accelerator profile ${i}`,
}),
),
),
);
acceleratorProfile.visit();
const tableRow = acceleratorProfile.getRow('TensorRT');
tableRow
.findDescription()
.contains('Lorem, ipsum dolor sit amet consectetur adipisicing elit. Saepe, quis');
const tableRow = acceleratorProfile.getRow('Test Accelerator - 0');
tableRow.findDescription().contains('accelerator profile 0');
tableRow.shouldHaveIdentifier('tensor.com/gpu');

// top pagination
testPagination({ totalItems, firstElement: 'Test Accelerator - 0', paginationVariant: 'top' });

// bottom pagination
testPagination({
totalItems,
firstElement: 'Test Accelerator - 0',
paginationVariant: 'bottom',
});

//sort by Name
acceleratorProfile.findTableHeaderButton('Name').click();
acceleratorProfile.findTableHeaderButton('Name').should(be.sortDescending);
Expand Down Expand Up @@ -83,7 +104,7 @@ describe('Accelerator Profile', () => {
const acceleratorTableToolbar = acceleratorProfile.getTableToolbar();
acceleratorTableToolbar.findFilterMenuOption('filter-dropdown-select', 'Name').click();
acceleratorTableToolbar.findSearchInput().fill('Test');
acceleratorProfile.getRow('Test Accelerator').shouldHaveIdentifier('nvidia.com/gpu');
acceleratorProfile.getRow('Test Accelerator - 0').shouldHaveIdentifier('tensor.com/gpu');

acceleratorTableToolbar.findFilterMenuOption('filter-dropdown-select', 'Identifier').click();
acceleratorTableToolbar.findResetButton().click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
} from '~/__tests__/cypress/cypress/pages/administration';
import { be } from '~/__tests__/cypress/cypress/utils/should';
import { asProductAdminUser, asProjectEditUser } from '~/__tests__/cypress/cypress/utils/users';
import { AllowedUser } from '~/pages/notebookController/screens/admin/types';
import { testPagination } from '~/__tests__/cypress/cypress/utils/pagination';

const groupSubjects: RoleBindingSubject[] = [
{
Expand All @@ -18,11 +20,14 @@ const groupSubjects: RoleBindingSubject[] = [
},
];

const initIntercepts = () => {
cy.interceptOdh('GET /api/status/openshift-ai-notebooks/allowedUsers', [
mockAllowedUsers({}),
mockAllowedUsers({ username: 'regularuser1' }),
]);
type HandlersProps = {
allowedUser?: AllowedUser[];
};

const initIntercepts = ({
allowedUser = [mockAllowedUsers({}), mockAllowedUsers({ username: 'regularuser1' })],
}: HandlersProps) => {
cy.interceptOdh('GET /api/status/openshift-ai-notebooks/allowedUsers', allowedUser);
cy.interceptOdh(
'GET /api/rolebindings/opendatahub/openshift-ai-notebooks-image-pullers',
mockK8sResourceList([
Expand All @@ -36,8 +41,8 @@ const initIntercepts = () => {
cy.interceptOdh('GET /api/images/:type', { path: { type: 'jupyter' } }, mockNotebookImageInfo());
};

it('Administartion tab should not be accessible for non-project admins', () => {
initIntercepts();
it('Administration tab should not be accessible for non-project admins', () => {
initIntercepts({});
asProjectEditUser();
notebookController.visit();
notebookController.findAdministrationTab().should('not.exist');
Expand All @@ -47,13 +52,13 @@ it('Administartion tab should not be accessible for non-project admins', () => {

describe('Administration Tab', () => {
beforeEach(() => {
initIntercepts();
asProductAdminUser();
notebookController.visit();
notebookController.findAdministrationTab().click();
});

it('Check table with users details', () => {
initIntercepts({});
notebookController.visit();
notebookController.findAdministrationTab().click();
administration.shouldHaveManageUsersAlert();
administration.findStopAllServersButton().should('be.disabled');
const userRow = administration.getRow('test-user');
Expand All @@ -63,6 +68,9 @@ describe('Administration Tab', () => {
});

it('Users table sorting', () => {
initIntercepts({});
notebookController.visit();
notebookController.findAdministrationTab().click();
// By user
administration.findTableHeaderButton('User').click();
administration.findTableHeaderButton('User').should(be.sortDescending);
Expand All @@ -88,7 +96,27 @@ describe('Administration Tab', () => {
administration.findTableHeaderButton('Server status').should(be.sortDescending);
});

it('Validate pagination', () => {
const totalItems = 50;
const mockAllowedUser: AllowedUser[] = Array.from({ length: totalItems }, (_, i) =>
mockAllowedUsers({
username: `Test user-${i}`,
}),
);
initIntercepts({ allowedUser: mockAllowedUser });
notebookController.visit();
notebookController.findAdministrationTab().click();
// top pagination
testPagination({ totalItems, firstElement: 'Test user-0', paginationVariant: 'top' });

// bottom pagination
testPagination({ totalItems, firstElement: 'Test user-0', paginationVariant: 'bottom' });
});

it('Validate that clicking on "Start server" button will open a form in administartion tab and "Start your server" button will navigate to notebook server tab', () => {
initIntercepts({});
notebookController.visit();
notebookController.findAdministrationTab().click();
let userRow = administration.getRow('regularuser1');

// open a form in administartion tab with impersonate alert
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { RoleBindingSubject } from '~/types';
import { mockComponents } from '~/__mocks__/mockComponents';
import { enabledPage } from '~/__tests__/cypress/cypress/pages/enabled';
import { jupyterCard } from '~/__tests__/cypress/cypress/pages/components/JupyterCard';
import { mockDashboardConfig, mockK8sResourceList } from '~/__mocks__';
import { mockRoleBindingK8sResource } from '~/__mocks__/mockRoleBindingK8sResource';

describe('Enabled Page', () => {
beforeEach(() => {
Expand All @@ -20,4 +23,34 @@ describe('Enabled Page', () => {
'A multi-user version of the notebook designed for companies, classrooms and research labs.',
);
});
it('should navigate to the notebook controller spawner page', () => {
const groupSubjects: RoleBindingSubject[] = [
{
kind: 'Group',
apiGroup: 'rbac.authorization.k8s.io',
name: 'group-1',
},
];
cy.interceptOdh(
'GET /api/rolebindings/opendatahub/openshift-ai-notebooks-image-pullers',
mockK8sResourceList([
mockRoleBindingK8sResource({
name: 'group-1',
subjects: groupSubjects,
roleRefName: 'edit',
}),
]),
);

enabledPage.visit();
jupyterCard.findApplicationLink().click();
cy.findByTestId('app-page-title').should('have.text', 'Start a notebook server');

// Now validate with the home page feature flag enabled
cy.interceptOdh('GET /api/config', mockDashboardConfig({ disableHome: false }));

enabledPage.visit(true);
jupyterCard.findApplicationLink().click();
cy.findByTestId('app-page-title').should('have.text', 'Start a notebook server');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('Model Registry', () => {

modelRegistry.visit();
modelRegistry.navigate();
modelRegistry.shouldtableToolbarExist();
modelRegistry.shouldModelRegistrySelectorExist();
modelRegistry.shouldregisteredModelsEmpty();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { InferenceServiceKind, ServingRuntimeKind } from '~/k8sTypes';
import { ServingRuntimePlatform } from '~/types';
import { be } from '~/__tests__/cypress/cypress/utils/should';
import { asClusterAdminUser } from '~/__tests__/cypress/cypress/utils/users';
import { testPagination } from '~/__tests__/cypress/cypress/utils/pagination';

type HandlersProps = {
disableKServeConfig?: boolean;
Expand Down Expand Up @@ -472,7 +473,7 @@ describe('Model Serving Global', () => {
cy.findByText('Error creating model server').should('not.exist');
});

describe('Table filter', () => {
describe('Table filter and pagination', () => {
it('filter by name', () => {
initIntercepts({});
modelServingGlobal.visit('test-project');
Expand Down Expand Up @@ -526,5 +527,32 @@ describe('Model Serving Global', () => {
// Verify no results were found
modelServingGlobal.findEmptyResults().should('exist');
});

it('Validate pagination', () => {
const totalItems = 50;
const mockInferenceService: InferenceServiceKind[] = Array.from(
{ length: totalItems },
(_, i) =>
mockInferenceServiceK8sResource({
displayName: `Test Inference Service-${i}`,
}),
);
initIntercepts({ inferenceServices: mockInferenceService });
modelServingGlobal.visit('test-project');

// top pagination
testPagination({
totalItems,
firstElement: 'Test Inference Service-0',
paginationVariant: 'top',
});

// bottom pagination
testPagination({
totalItems,
firstElement: 'Test Inference Service-0',
paginationVariant: 'bottom',
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { mockByon } from '~/__mocks__/mockByon';
import { deleteModal } from '~/__tests__/cypress/cypress/pages/components/DeleteModal';
import { tablePagination } from '~/__tests__/cypress/cypress/pages/components/Pagination';
import {
importNotebookImageModal,
notebookImageDeleteModal,
Expand All @@ -11,6 +10,7 @@ import { pageNotfound } from '~/__tests__/cypress/cypress/pages/pageNotFound';
import { projectListPage } from '~/__tests__/cypress/cypress/pages/projects';
import { be } from '~/__tests__/cypress/cypress/utils/should';
import { asProductAdminUser, asProjectAdminUser } from '~/__tests__/cypress/cypress/utils/users';
import { testPagination } from '~/__tests__/cypress/cypress/utils/pagination';

it('Notebook image settings should not be available for non product admins', () => {
asProjectAdminUser();
Expand All @@ -24,11 +24,12 @@ describe('Notebook image settings', () => {
asProductAdminUser();
});

it('Table filtering, sorting, searching', () => {
it('Table filtering, sorting, searching and pagination', () => {
const totalItems = 1000;
cy.interceptOdh(
'GET /api/images/byon',
Array.from(
{ length: 1000 },
{ length: totalItems },
(_, i) =>
mockByon([
{
Expand Down Expand Up @@ -73,25 +74,11 @@ describe('Notebook image settings', () => {
notebookImageSettings.findTableHeaderButton('Enable').should(be.sortDescending);
notebookImageSettings.findTableHeaderButton('Name').click();

// test pagination
// test next page
tablePagination.top.findNextButton().click();
tablePagination.top.findNextButton().click();
tablePagination.top.findNextButton().click();
tablePagination.top.findNextButton().click();
notebookImageSettings.getRow('image-136').find().should('exist');

// test type page
tablePagination.top.findInput().clear();
tablePagination.top.findInput().type('50{enter}');
notebookImageSettings.getRow('image-542').find().should('exist');

// test last and first page
tablePagination.top.findLastButton().click();
notebookImageSettings.getRow('image-999').find().should('exist');

tablePagination.top.findFirstButton().click();
notebookImageSettings.getRow('image-0').find().should('exist');
// top pagination
testPagination({ totalItems, firstElement: 'image-0', paginationVariant: 'top' });

// bottom pagination
testPagination({ totalItems, firstElement: 'image-0', paginationVariant: 'bottom' });

// test filtering
// by name
Expand Down
Loading

0 comments on commit 84ad10e

Please sign in to comment.