Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions frontend/packages/integration-tests-cypress/mocks/clone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { PVC } from './snapshot';

export const PVC_NAME = 'testpvc';
Comment on lines +1 to +3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { PVC } from './snapshot';
export const PVC_NAME = 'testpvc';
import { PVC, PVC_NAME } from './snapshot';

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intentionally scoped the const to this test to avoid cross-importing mocks, but a shared mock file would probably be cleaner.

export const CLONE_NAME = `${PVC_NAME}-clone`;
export const CLONE_SIZE = '2';

/** PVC with different storage class (gp3) for testing cross-storage-class cloning */
export const PVCGP3 = {
apiVersion: PVC.apiVersion,
kind: PVC.kind,
metadata: {
name: 'testpvcgp3',
},
spec: {
storageClassName: 'gp3-csi',
accessModes: PVC.spec.accessModes,
resources: {
requests: {
storage: PVC.spec.resources.requests.storage,
},
},
},
};
49 changes: 31 additions & 18 deletions frontend/packages/integration-tests-cypress/mocks/snapshot.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type { Patch } from '@console/internal/module/k8s';

export const PVC_NAME = 'testpvc';
export const SNAPSHOT_NAME = `${PVC_NAME}-snapshot`;
export const DEPLOYMENT_NAME = 'busybox-deployment';

export const testerDeployment = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If testerDeployment is no longer used, it can be removed

apiVersion: 'apps/v1',
kind: 'Deployment',
Expand Down Expand Up @@ -56,6 +60,32 @@ export const testerDeployment = {
},
};

/** Deployment with volumeMounts (for clone tests) */
export const testerDeploymentWithMounts = {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: { name: 'busybox-deployment', labels: { app: 'busybox' } },
spec: {
replicas: 1,
selector: { matchLabels: { app: 'busybox' } },
template: {
metadata: { labels: { app: 'busybox' } },
spec: {
volumes: [{ name: 'testpvc', persistentVolumeClaim: { claimName: 'testpvc' } }],
containers: [
{
name: 'busybox',
image: 'busybox',
imagePullPolicy: 'IfNotPresent',
volumeMounts: [{ name: 'testpvc', mountPath: '/data' }],
command: ['sh', '-c', 'echo Container 1 is Running ; sleep 3600'],
},
],
},
},
},
};

export const PVC = {
apiVersion: 'v1',
kind: 'PersistentVolumeClaim',
Expand All @@ -73,23 +103,6 @@ export const PVC = {
},
};

export const PVCGP3 = {
apiVersion: PVC.apiVersion,
kind: PVC.kind,
metadata: {
name: 'testpvcgp3',
},
spec: {
storageClassName: 'gp3-csi',
accessModes: PVC.spec.accessModes,
resources: {
requests: {
storage: PVC.spec.resources.requests.storage,
},
},
},
};

export const SnapshotClass = {
apiVersion: 'snapshot.storage.k8s.io/v1',
kind: 'VolumeSnapshotClass',
Expand All @@ -106,7 +119,7 @@ export const patchForVolume: Patch = {
value: {
name: 'testpvc-snapshot-restore',
persistentVolumeClaim: {
claimName: 'testpvc-snapshot-restore',
claimName: `${SNAPSHOT_NAME}-restore`,
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ module.exports = (on, config) => {
config.env.BRIDGE_HTPASSWD_USERNAME = process.env.BRIDGE_HTPASSWD_USERNAME;
config.env.BRIDGE_HTPASSWD_PASSWORD = process.env.BRIDGE_HTPASSWD_PASSWORD;
config.env.BRIDGE_KUBEADMIN_PASSWORD = process.env.BRIDGE_KUBEADMIN_PASSWORD;
config.env.BRIDGE_AWS = process.env.BRIDGE_AWS;
config.env.OAUTH_BASE_ADDRESS = process.env.OAUTH_BASE_ADDRESS;
config.env.OPENSHIFT_CI = process.env.OPENSHIFT_CI;
return config;
Expand Down
116 changes: 64 additions & 52 deletions frontend/packages/integration-tests-cypress/tests/storage/clone.cy.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,40 @@
import { PVC, PVCGP3, testerDeployment } from '../../mocks/snapshot';
import { PVC_NAME, CLONE_NAME, CLONE_SIZE, PVCGP3 } from '../../mocks/clone';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { PVC_NAME, CLONE_NAME, CLONE_SIZE, PVCGP3 } from '../../mocks/clone';
import { PVC_NAME, PVC, CLONE_NAME, CLONE_SIZE, PVCGP3 } from '../../mocks/clone';

import { PVC, testerDeploymentWithMounts } from '../../mocks/snapshot';
import { testName, checkErrors } from '../../support';
import { resourceStatusShouldContain } from '../../views/common';
import { detailsPage, DetailsPageSelector } from '../../views/details-page';
import { listPage } from '../../views/list-page';
import { modal } from '../../views/modal';
import { nav } from '../../views/nav';

const cloneName = `${PVC.metadata.name}-clone`;
const cloneSize = '2';
const deletePVCClone = (pvcName: string) => {
nav.sidenav.clickNavLink(['PersistentVolumeClaims']);
listPage.filter.byName(pvcName);
listPage.rows.clickKebabAction(pvcName, 'Delete PersistentVolumeClaim');
const deletePVCClone = (testNs: string, pvcName: string) => {
cy.visit(`/k8s/ns/${testNs}/core~v1~PersistentVolumeClaim`);
listPage.dvRows.shouldBeLoaded();
listPage.dvFilter.byName(pvcName);
listPage.dvRows.clickKebabAction(pvcName, 'Delete PersistentVolumeClaim');
modal.shouldBeOpened();
modal.submitShouldBeEnabled();
modal.submit();
modal.shouldBeClosed();
listPage.rows.shouldNotExist(pvcName);
listPage.dvRows.shouldNotExist(pvcName);
};

// Normalize env check: CI env vars are strings, so "false" would be truthy without explicit comparison.
// These tests require AWS platform with EBS CSI driver for clone support
const isAws = String(Cypress.env('BRIDGE_AWS')).toLowerCase() === 'true';

if (isAws) {
describe('Clone Tests', () => {
before(() => {
cy.login();
cy.createProjectWithCLI(testName);
cy.exec(`echo '${JSON.stringify(PVC)}' | oc apply -n ${testName} -f -`);
cy.exec(`echo '${JSON.stringify(PVCGP3)}' | oc apply -n ${testName} -f -`);
cy.exec(`echo '${JSON.stringify(testerDeployment)}' | oc apply -n ${testName} -f -`);
cy.exec(
`echo '${JSON.stringify(testerDeploymentWithMounts)}' | oc apply -n ${testName} -f -`,
);
nav.sidenav.clickNavLink(['Storage', 'PersistentVolumeClaims']);
listPage.filter.byName(PVC.metadata.name);
listPage.dvRows.shouldBeLoaded();
listPage.dvFilter.byName(PVC_NAME);
resourceStatusShouldContain('Bound');
});

Expand All @@ -39,75 +43,83 @@ if (isAws) {
});

after(() => {
cy.exec(`echo '${JSON.stringify(testerDeployment)}' | oc delete -n ${testName} -f -`);
cy.exec(`echo '${JSON.stringify(PVC)}' | oc delete -n ${testName} -f -`);
cy.exec(`echo '${JSON.stringify(PVCGP3)}' | oc delete -n ${testName} -f -`);
cy.exec(
`echo '${JSON.stringify(testerDeploymentWithMounts)}' | oc delete -n ${testName} -f -`,
{
failOnNonZeroExit: false,
},
);
cy.exec(`echo '${JSON.stringify(PVC)}' | oc delete -n ${testName} -f -`, {
failOnNonZeroExit: false,
});
cy.exec(`echo '${JSON.stringify(PVCGP3)}' | oc delete -n ${testName} -f -`, {
failOnNonZeroExit: false,
});
cy.deleteProjectWithCLI(testName);
});

it('Creates PVC Clone', () => {
listPage.rows.clickKebabAction(PVC.metadata.name, 'Clone PVC');
// Clean up any leftover clone from previous failed runs
cy.exec(`oc delete pvc ${CLONE_NAME} -n ${testName} --ignore-not-found`, {
failOnNonZeroExit: false,
});
// Navigate to PVC list
cy.visit(`/k8s/ns/${testName}/core~v1~PersistentVolumeClaim`);
listPage.dvRows.shouldBeLoaded();
listPage.dvFilter.byName(PVC_NAME);
listPage.dvRows.clickKebabAction(PVC_NAME, 'Clone PVC');
modal.shouldBeOpened();
modal.submitShouldBeEnabled();
cy.byTestID('input-request-size').clear().type(cloneSize);
cy.byTestID('input-request-size').clear().type(CLONE_SIZE);
modal.submit();
modal.shouldBeClosed();
cy.location('pathname').should(
'include',
`persistentvolumeclaims/${PVC.metadata.name}-clone`,
);
detailsPage.titleShouldContain(`${PVC.metadata.name}-clone`);
cy.exec(`oc get pvc ${PVC.metadata.name}-clone -n ${testName} -o json`)
.its('stdout')
.then((res) => {
const pvc = JSON.parse(res);
cy.get(DetailsPageSelector.name).contains(pvc.metadata.name);
cy.get(DetailsPageSelector.namespace).contains(pvc.metadata.namespace);
cy.byTestID('pvc-requested-capacity').contains(`${cloneSize} GiB`);
});
cy.location('pathname').should('include', `persistentvolumeclaims/${CLONE_NAME}`);
detailsPage.titleShouldContain(CLONE_NAME);
// Wait for PVC to be created and details page to load
cy.get(DetailsPageSelector.name, { timeout: 60000 }).should('contain.text', CLONE_NAME);
cy.get(DetailsPageSelector.namespace).should('contain.text', testName);
});

it('Lists Clone', () => {
nav.sidenav.clickNavLink(['PersistentVolumeClaims']);
listPage.rows.shouldBeLoaded();
listPage.rows.shouldExist(cloneName);
cy.visit(`/k8s/ns/${testName}/core~v1~PersistentVolumeClaim`);
listPage.dvRows.shouldBeLoaded();
listPage.dvRows.shouldExist(CLONE_NAME);
});

it('Deletes PVC Clone', () => {
deletePVCClone(cloneName);
deletePVCClone(testName, CLONE_NAME);
});

it('Creates PVC Clone with different storage cluster', () => {
listPage.filter.byName(PVC.metadata.name);
listPage.rows.clickKebabAction(PVC.metadata.name, 'Clone PVC');
it('Creates PVC Clone with different storage class', () => {
// Clean up any leftover clone from previous failed runs
cy.exec(`oc delete pvc ${CLONE_NAME} -n ${testName} --ignore-not-found`, {
failOnNonZeroExit: false,
});
// Navigate to PVC list and filter to find original PVC
cy.visit(`/k8s/ns/${testName}/core~v1~PersistentVolumeClaim`);
listPage.dvRows.shouldBeLoaded();
listPage.dvFilter.byName(PVC_NAME);
listPage.dvRows.clickKebabAction(PVC_NAME, 'Clone PVC');
modal.shouldBeOpened();
modal.submitShouldBeEnabled();
cy.byTestID('input-request-size').clear().type(cloneSize);
cy.byTestID('input-request-size').clear().type(CLONE_SIZE);
cy.byTestID('storage-class-dropdown').click();
cy.byTestID('console-select-item').contains('gp3-csi').click();
modal.submit();
modal.shouldBeClosed();
cy.location('pathname').should(
'include',
`persistentvolumeclaims/${PVC.metadata.name}-clone`,
);
detailsPage.titleShouldContain(`${PVC.metadata.name}-clone`);
cy.exec(`oc get pvc ${PVC.metadata.name}-clone -n ${testName} -o json`)
.its('stdout')
.then((res) => {
const pvc = JSON.parse(res);
cy.get(DetailsPageSelector.name).contains(pvc.metadata.name);
cy.get(DetailsPageSelector.namespace).contains(pvc.metadata.namespace);
cy.byTestID('pvc-requested-capacity').contains(`${cloneSize} GiB`);
});
cy.location('pathname').should('include', `persistentvolumeclaims/${CLONE_NAME}`);
detailsPage.titleShouldContain(CLONE_NAME);
// Wait for PVC to be created and details page to load
cy.get(DetailsPageSelector.name, { timeout: 60000 }).should('contain.text', CLONE_NAME);
cy.get(DetailsPageSelector.namespace).should('contain.text', testName);
});

it('Deletes PVC Clone', () => {
deletePVCClone(cloneName);
deletePVCClone(testName, CLONE_NAME);
});
});
} else {
describe('Skipping Clone Tests', () => {
it('No CSI based storage classes are available in this platform', () => {});
it('requires AWS platform with EBS CSI driver', () => {});
});
}
Loading