Skip to content

Commit

Permalink
Update cluster storage modal
Browse files Browse the repository at this point in the history
  • Loading branch information
pnaik1 committed Nov 29, 2024
1 parent 09ccd93 commit fdbd40a
Show file tree
Hide file tree
Showing 17 changed files with 721 additions and 271 deletions.
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 _ 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
35 changes: 28 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,35 @@ 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');
}

findWorkbenchRow(row: number) {
return this.findWorkbenchTable().find('tbody').eq(row).find('tr');
}

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

selectCustomPathFormat(row: number) {
this.findWorkbenchRow(row).find(`[data-label="Path format"]`).find('button').click();
this.findWorkbenchTable()
.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.findWorkbenchRow(row).find(`[data-label="Mount path"]`).find('input');
}

findMountFieldHelperText() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ const initInterceptors = ({ isEmpty = false, storageClassName }: HandlersProps)
name: 'test-dupe-pvc-path',
},
],
additionalVolumes: [
{
name: 'test-dupe-pvc-path',
persistentVolumeClaim: {
claimName: 'test-dupe-pvc-path',
},
},
],
}),
]),
);
Expand Down Expand Up @@ -174,38 +182,22 @@ describe('ClusterStorage', () => {
addClusterStorageModal.selectPVSize('MiB');

//connect workbench
addClusterStorageModal.findWorkbenchConnectionSelect().click();
addClusterStorageModal.find().findByText('Test Notebook').click();

// don't allow duplicate path
addClusterStorageModal.findMountField().clear();
addClusterStorageModal.findMountField().fill('test-dupe');
addClusterStorageModal
.findMountFieldHelperText()
.should('have.text', 'Mount folder is already in use for this workbench.');

// don't allow number in the path
addClusterStorageModal.findMountField().clear();
addClusterStorageModal.findMountField().fill('test2');
addClusterStorageModal
.findMountFieldHelperText()
.should('have.text', 'Must only consist of lowercase letters and dashes.');
addClusterStorageModal.findAddWorkbenchButton().click();
addClusterStorageModal.findWorkbenchTable().should('exist');
addClusterStorageModal.selectWorkbenchName(0, 'test-notebook');

// Allow trailing slash
addClusterStorageModal.findMountField().clear();
addClusterStorageModal.findMountField().fill('test/');
//don't allow duplicate path
addClusterStorageModal.findMountPathField(0).fill('test-dupe');
addClusterStorageModal
.findMountFieldHelperText()
.should('have.text', 'Must consist of lowercase letters and dashes.');
.should('contain.text', 'This path is already connected to this workbench');

addClusterStorageModal.findMountField().clear();
addClusterStorageModal.findMountPathField(0).clear();
addClusterStorageModal.selectCustomPathFormat(0);
addClusterStorageModal
.findMountFieldHelperText()
.should(
'have.text',
'Enter a path to a model or folder. This path cannot point to a root folder.',
);
addClusterStorageModal.findMountField().fill('data');
.should('contain.text', 'Enter a path to a model or folder.');
addClusterStorageModal.findMountPathField(0).fill('data');
addClusterStorageModal.findWorkbenchRestartAlert().should('exist');

cy.interceptK8s('PATCH', NotebookModel, mockNotebookK8sResource({})).as('addClusterStorage');
Expand All @@ -220,7 +212,7 @@ describe('ClusterStorage', () => {
{
op: 'add',
path: '/spec/template/spec/containers/0/volumeMounts/-',
value: { mountPath: '/opt/app-root/src/data' },
value: { mountPath: 'data' },
},
]);
});
Expand Down Expand Up @@ -248,7 +240,7 @@ describe('ClusterStorage', () => {
],
containers: [
{
volumeMounts: [{ name: 'existing-pvc', mountPath: '/' }],
volumeMounts: [{ name: 'existing-pvc', mountPath: '/opt/app-root/src' }],
},
],
},
Expand Down Expand Up @@ -276,12 +268,9 @@ describe('ClusterStorage', () => {
clusterStorage.visit('test-project');
const clusterStorageRow = clusterStorage.getClusterStorageRow('Existing PVC');
clusterStorageRow.findKebabAction('Edit storage').click();

// Connect to 'Another Notebook'
updateClusterStorageModal.findWorkbenchConnectionSelect().click();
updateClusterStorageModal.find().findByText('Another Notebook').click();

updateClusterStorageModal.findMountField().fill('new-data');
updateClusterStorageModal.findAddWorkbenchButton().click();
updateClusterStorageModal.selectWorkbenchName(1, 'another-notebook');
updateClusterStorageModal.findMountPathField(1).fill('new-data');

cy.interceptK8s('PATCH', NotebookModel, anotherNotebook).as('updateClusterStorage');

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/api/k8s/__tests__/notebooks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ describe('attachNotebookPVC', () => {
const namespace = 'test-project';
const notebookName = 'test-notebook';
const pvcName = 'test-pvc';
const mountSuffix = 'data';
const mountSuffix = '/opt/app-root/src/data';
const mockNotebook = mockNotebookK8sResource({ uid });

k8sPatchResourceMock.mockResolvedValue(mockNotebook);
Expand Down Expand Up @@ -728,7 +728,7 @@ describe('attachNotebookPVC', () => {
const namespace = 'test-project';
const notebookName = 'test-notebook';
const pvcName = 'test-pvc';
const mountSuffix = 'data';
const mountSuffix = '/opt/app-root/src/data';

k8sPatchResourceMock.mockRejectedValue(new Error('error1'));
await expect(attachNotebookPVC(notebookName, namespace, pvcName, mountSuffix)).rejects.toThrow(
Expand Down
54 changes: 52 additions & 2 deletions frontend/src/api/k8s/notebooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ export const attachNotebookPVC = (
notebookName: string,
namespace: string,
pvcName: string,
mountSuffix: string,
mountPath: string,
opts?: K8sAPIOptions,
): Promise<NotebookKind> => {
const patches: Patch[] = [
Expand All @@ -422,7 +422,7 @@ export const attachNotebookPVC = (
op: 'add',
// TODO: can we assume first container?
path: '/spec/template/spec/containers/0/volumeMounts/-',
value: { mountPath: `${ROOT_MOUNT_PATH}/${mountSuffix}`, name: pvcName },
value: { mountPath, name: pvcName },
},
];

Expand All @@ -438,6 +438,56 @@ export const attachNotebookPVC = (
);
};

export const UpdateNotebookPVC = (
notebookName: string,
namespace: string,
mountPath: string,
pvcName: string,
opts?: K8sAPIOptions,

Check warning on line 446 in frontend/src/api/k8s/notebooks.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/api/k8s/notebooks.ts#L446

Added line #L446 was not covered by tests
): Promise<NotebookKind> =>
new Promise((resolve, reject) => {
getNotebook(notebookName, namespace)
.then((notebook) => {
const volumes = notebook.spec.template.spec.volumes || [];
const volumeMounts = notebook.spec.template.spec.containers[0].volumeMounts || [];

Check warning on line 452 in frontend/src/api/k8s/notebooks.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/api/k8s/notebooks.ts#L448-L452

Added lines #L448 - L452 were not covered by tests

const filteredVolumeMounts = volumeMounts.map((volumeMount) => {
if (volumeMount.name === pvcName) {
return { ...volumeMount, mountPath };

Check warning on line 456 in frontend/src/api/k8s/notebooks.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/api/k8s/notebooks.ts#L454-L456

Added lines #L454 - L456 were not covered by tests
}
return volumeMount;

Check warning on line 458 in frontend/src/api/k8s/notebooks.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/api/k8s/notebooks.ts#L458

Added line #L458 was not covered by tests
});

const patches: Patch[] = [

Check warning on line 461 in frontend/src/api/k8s/notebooks.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/api/k8s/notebooks.ts#L461

Added line #L461 was not covered by tests
{
op: 'replace',
path: '/spec/template/spec/volumes',
value: volumes,
},
{
op: 'replace',
// TODO: can we assume first container?
path: '/spec/template/spec/containers/0/volumeMounts',
value: filteredVolumeMounts,
},
];

k8sPatchResource<NotebookKind>(

Check warning on line 475 in frontend/src/api/k8s/notebooks.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/api/k8s/notebooks.ts#L475

Added line #L475 was not covered by tests
applyK8sAPIOptions(
{
model: NotebookModel,
queryOptions: { name: notebookName, ns: namespace },
patches,
},
opts,
),
)
.then(resolve)
.catch(reject);
})
.catch(reject);
});

export const removeNotebookPVC = (
notebookName: string,
namespace: string,
Expand Down

This file was deleted.

4 changes: 1 addition & 3 deletions frontend/src/pages/projects/notebook/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { EventKind, NotebookKind } from '~/k8sTypes';
import { EventStatus, NotebookSize, NotebookStatus } from '~/types';
import { ROOT_MOUNT_PATH } from '~/pages/projects/pvc/const';
import { fireFormTrackingEvent } from '~/concepts/analyticsTracking/segmentIOUtils';
import { TrackingOutcome } from '~/concepts/analyticsTracking/trackingProperties';
import { AcceleratorProfileState } from '~/utilities/useReadAcceleratorState';
Expand All @@ -24,8 +23,7 @@ export const getNotebookPVCVolumeNames = (notebook: NotebookKind): { [name: stri
};
}, {});

const relativeMountPath = (mountPath: string): string =>
mountPath.slice(ROOT_MOUNT_PATH.length) || '/';
const relativeMountPath = (mountPath: string): string => mountPath || '/';

export const getNotebookPVCMountPathMap = (
notebook?: NotebookKind,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type BaseStorageModalProps = {
submitLabel?: string;
title?: string;
description?: string;
children: React.ReactNode;
children: ((opts: string) => React.ReactNode) | React.ReactNode;
isValid: boolean;
onSubmit: (data: CreateStorageObjectData) => Promise<void>;
hasDuplicateName?: boolean;
Expand Down Expand Up @@ -122,7 +122,7 @@ const BaseStorageModal: React.FC<BaseStorageModalProps> = ({
disableStorageClassSelect={!!existingPvc}
/>
</StackItem>
{children}
{typeof children === 'function' ? children(createData.name) : children}
</Stack>
</Form>
</Modal>
Expand Down
Loading

0 comments on commit fdbd40a

Please sign in to comment.