Skip to content

Commit 8f2902b

Browse files
cajiehclaude
andcommitted
CONSOLE-4913: Add E2E tests for VolumeAttributesClass UI
Adds comprehensive Cypress E2E tests for creating and managing VolumeAttributesClasses with PVCs. Tests cover: - Creating VACs via oc CLI - Selecting VAC from PVC creation form dropdown - Creating Deployment that uses PVC with VAC - Verifying VAC on PVC details page - Modifying VAC through modal UI - Verifying VAC details page Tests are platform-specific and only run on AWS and GCP where VAC is supported. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent e1d975d commit 8f2902b

4 files changed

Lines changed: 297 additions & 0 deletions

File tree

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import type { DeploymentKind } from '@console/internal/module/k8s';
2+
3+
// VolumeAttributesClass names and constants
4+
export const VAC_NAME_1 = 'aws-ebs-gp3-high-iops';
5+
export const VAC_NAME_2 = 'aws-ebs-gp3-low-iops';
6+
export const VAC_INVALID = 'invalid-vac-1';
7+
export const PVC_NAME = 'my-pvc-1';
8+
export const DEPLOYMENT_NAME = 'my-deployment-1';
9+
10+
// AWS EBS CSI driver VolumeAttributesClass for high IOPS
11+
export const VAC_1 = {
12+
apiVersion: 'storage.k8s.io/v1',
13+
kind: 'VolumeAttributesClass',
14+
metadata: {
15+
name: VAC_NAME_1,
16+
},
17+
driverName: 'ebs.csi.aws.com',
18+
parameters: {
19+
iops: '1000',
20+
throughput: '125',
21+
type: 'gp3',
22+
},
23+
};
24+
25+
// AWS EBS CSI driver VolumeAttributesClass for low IOPS
26+
export const VAC_2 = {
27+
apiVersion: 'storage.k8s.io/v1',
28+
kind: 'VolumeAttributesClass',
29+
metadata: {
30+
name: VAC_NAME_2,
31+
},
32+
driverName: 'ebs.csi.aws.com',
33+
parameters: {
34+
iops: '3000',
35+
throughput: '125',
36+
type: 'gp3',
37+
},
38+
};
39+
40+
// Invalid VAC with non-existent driver to trigger error state
41+
export const VAC_INVALID_OBJ = {
42+
apiVersion: 'storage.k8s.io/v1',
43+
kind: 'VolumeAttributesClass',
44+
metadata: {
45+
name: VAC_INVALID,
46+
},
47+
driverName: 'non.existent.driver',
48+
parameters: {
49+
iops: '1000',
50+
throughput: '125',
51+
type: 'gp3',
52+
},
53+
};
54+
55+
export const getDeployment = (namespace: string, pvcName: string): DeploymentKind => ({
56+
apiVersion: 'apps/v1',
57+
kind: 'Deployment',
58+
metadata: {
59+
name: DEPLOYMENT_NAME,
60+
namespace,
61+
labels: {
62+
app: 'my-app',
63+
},
64+
},
65+
spec: {
66+
replicas: 1,
67+
selector: {
68+
matchLabels: {
69+
app: 'my-app',
70+
},
71+
},
72+
template: {
73+
metadata: {
74+
labels: {
75+
app: 'my-app',
76+
},
77+
},
78+
spec: {
79+
containers: [
80+
{
81+
name: 'app-container',
82+
image: 'image-registry.openshift-image-registry.svc:5000/openshift/httpd:latest',
83+
ports: [{ containerPort: 80, protocol: 'TCP' }],
84+
volumeMounts: [
85+
{
86+
name: 'persistent-storage',
87+
mountPath: '/data',
88+
},
89+
],
90+
resources: {
91+
requests: {
92+
memory: '128Mi',
93+
cpu: '100m',
94+
},
95+
limits: {
96+
memory: '256Mi',
97+
cpu: '200m',
98+
},
99+
},
100+
},
101+
],
102+
volumes: [
103+
{
104+
name: 'persistent-storage',
105+
persistentVolumeClaim: {
106+
claimName: pvcName,
107+
},
108+
},
109+
],
110+
},
111+
},
112+
},
113+
});

frontend/packages/integration-tests-cypress/tests/crud/resource-crud.cy.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ describe('Kubernetes resource CRUD operations', () => {
7878
kind: 'snapshot.storage.k8s.io~v1~VolumeSnapshotContent',
7979
namespaced: false,
8080
})
81+
.set('storage.k8s.io~v1~VolumeAttributesClass', {
82+
kind: 'storage.k8s.io~v1~VolumeAttributesClass',
83+
namespaced: false,
84+
})
8185
: OrderedMap<string, TestDefinition>();
8286

8387
const k8sObjsWithSnapshots = k8sObjs.merge(snapshotObjs);
@@ -146,6 +150,7 @@ describe('Kubernetes resource CRUD operations', () => {
146150
'snapshot.storage.k8s.io~v1~VolumeSnapshotClass',
147151
'snapshot.storage.k8s.io~v1~VolumeSnapshotContent',
148152
'StatefulSet',
153+
'storage.k8s.io~v1~VolumeAttributesClass',
149154
'StorageClass',
150155
'user.openshift.io~v1~Group',
151156
]);
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import {
2+
VAC_1,
3+
VAC_2,
4+
VAC_INVALID_OBJ,
5+
VAC_NAME_1,
6+
VAC_NAME_2,
7+
VAC_INVALID,
8+
PVC_NAME,
9+
DEPLOYMENT_NAME,
10+
getDeployment,
11+
} from '../../mocks/volume-attributes-class';
12+
import { testName, checkErrors } from '../../support';
13+
import { resourceStatusShouldContain } from '../../views/common';
14+
import { detailsPage } from '../../views/details-page';
15+
import { volumeAttributesClass, pvcWithVAC } from '../../views/storage/volume-attributes-class';
16+
17+
// These tests are meant to be run on AWS as only AWS supports CSI drivers with modifyVolume (EBS CSI)
18+
// Normalize env check: CI env vars are strings, so "false" would be truthy without explicit comparison.
19+
const isAws = String(Cypress.env('BRIDGE_AWS')).toLowerCase() === 'true'; // Extract into reusable logic
20+
21+
(isAws ? describe : describe.skip)('VolumeAttributesClass integration with PVC', () => {
22+
before(() => {
23+
cy.login();
24+
cy.createProjectWithCLI(testName);
25+
26+
// Create VACs using apply
27+
cy.exec(`echo '${JSON.stringify(VAC_1)}' | oc apply -f -`);
28+
cy.exec(`echo '${JSON.stringify(VAC_2)}' | oc apply -f -`);
29+
cy.exec(`echo '${JSON.stringify(VAC_INVALID_OBJ)}' | oc apply -f -`);
30+
});
31+
32+
afterEach(() => {
33+
checkErrors();
34+
});
35+
36+
after(() => {
37+
// Cleanup test resources in proper order with waits to ensure complete deletion
38+
39+
// Step 1: Delete Deployment (releases pod references to PVC)
40+
cy.exec(
41+
`oc delete deployment ${DEPLOYMENT_NAME} -n ${testName} --ignore-not-found=true --wait=true`,
42+
{
43+
failOnNonZeroExit: false,
44+
timeout: 120000,
45+
},
46+
);
47+
48+
// Step 2: Delete PVC (releases PVC reference to VAC) and wait for it
49+
cy.exec(`oc delete pvc ${PVC_NAME} -n ${testName} --ignore-not-found=true --wait=true`, {
50+
failOnNonZeroExit: false,
51+
timeout: 120000,
52+
});
53+
54+
// Step 3: Remove finalizers from all VACs
55+
[VAC_NAME_1, VAC_NAME_2, VAC_INVALID].forEach((vacName) => {
56+
cy.exec(
57+
`oc patch volumeattributesclass ${vacName} -p '{"metadata":{"finalizers":[]}}' --type=merge`,
58+
{ failOnNonZeroExit: false, timeout: 30000 },
59+
);
60+
});
61+
62+
// Step 4: Initiate VAC deletion without waiting (cluster resources can be slow to delete)
63+
cy.exec(
64+
`oc delete volumeattributesclass ${VAC_NAME_1} ${VAC_NAME_2} ${VAC_INVALID} --ignore-not-found=true --wait=false`,
65+
{
66+
failOnNonZeroExit: false,
67+
timeout: 30000,
68+
},
69+
);
70+
71+
cy.deleteProjectWithCLI(testName);
72+
});
73+
74+
it('creates a PVC with VolumeAttributesClass selected from the form dropdown', () => {
75+
pvcWithVAC.createPVCWithVAC(testName, PVC_NAME, VAC_NAME_1);
76+
detailsPage.titleShouldContain(PVC_NAME);
77+
pvcWithVAC.verifyVACOnDetailsPage(VAC_NAME_1);
78+
79+
// Create Deployment to bind the PVC
80+
cy.exec(`echo '${JSON.stringify(getDeployment(testName, PVC_NAME))}' | oc create -f -`);
81+
82+
// Wait for Deployment to be ready
83+
cy.exec(`oc rollout status deployment/${DEPLOYMENT_NAME} -n ${testName} --timeout=120s`, {
84+
failOnNonZeroExit: false,
85+
timeout: 130000,
86+
});
87+
88+
// Wait for PVC to be bound (required for VAC modification to be enabled)
89+
pvcWithVAC.navigateToPVCDetails(testName, PVC_NAME);
90+
resourceStatusShouldContain('Bound', { timeout: 45000 });
91+
92+
// pvcWithVAC.verifyVACOnDetailsPage(VAC_NAME_1);
93+
});
94+
95+
it('modifies VolumeAttributesClass on PVC using the modal', () => {
96+
pvcWithVAC.modifyVACOnPVC(testName, PVC_NAME, VAC_NAME_2);
97+
pvcWithVAC.verifyVACOnDetailsPage(VAC_NAME_2);
98+
});
99+
100+
it('handles invalid VAC gracefully by displaying requested VAC on details page', () => {
101+
pvcWithVAC.modifyVACOnPVC(testName, PVC_NAME, VAC_INVALID);
102+
pvcWithVAC.verifyVACOnDetailsPage(VAC_INVALID);
103+
104+
// Navigate to VAC list page before cleanup to avoid 404 when PVC is deleted
105+
volumeAttributesClass.navigateToVACList();
106+
});
107+
});
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { detailsPage } from '../details-page';
2+
import { listPage } from '../list-page';
3+
import { modal } from '../modal';
4+
5+
export const volumeAttributesClass = {
6+
navigateToVACList: () => {
7+
cy.visit('/k8s/cluster/storage.k8s.io~v1~VolumeAttributesClass');
8+
listPage.dvRows.shouldBeLoaded();
9+
},
10+
};
11+
12+
export const pvcWithVAC = {
13+
createPVCWithVAC: (namespace: string, pvcName: string, vacName: string) => {
14+
cy.visit(`/k8s/ns/${namespace}/persistentvolumeclaims/~new/form`);
15+
16+
// Wait for form to be fully loaded
17+
cy.byTestID('pvc-name', { timeout: 30000 }).should('be.visible');
18+
19+
// Wait for VAC dropdown to load and select VAC
20+
cy.byTestID('volumeattributesclass-dropdown', { timeout: 30000 }).should('be.visible').click();
21+
cy.byTestID('console-select-item').contains(vacName).click();
22+
23+
// Fill PVC name
24+
cy.byTestID('pvc-name').clear().type(pvcName);
25+
26+
// Set size
27+
cy.byTestID('pvc-size').clear().type('1');
28+
29+
// Create PVC
30+
cy.byTestID('create-pvc').click();
31+
32+
// Wait for navigation to details page
33+
cy.location('pathname', { timeout: 30000 }).should(
34+
'include',
35+
`persistentvolumeclaims/${pvcName}`,
36+
);
37+
detailsPage.isLoaded();
38+
},
39+
40+
navigateToPVCDetails: (namespace: string, pvcName: string) => {
41+
cy.visit(`/k8s/ns/${namespace}/persistentvolumeclaims/${pvcName}`);
42+
detailsPage.isLoaded();
43+
},
44+
45+
modifyVACOnPVC: (namespace: string, pvcName: string, newVACName: string) => {
46+
pvcWithVAC.navigateToPVCDetails(namespace, pvcName);
47+
48+
// Open actions menu and wait for Modify VAC action to be enabled (not disabled)
49+
cy.byLegacyTestID('actions-menu-button').click();
50+
cy.byTestActionID('Modify VolumeAttributesClass')
51+
.should('be.visible')
52+
.and('not.have.attr', 'aria-disabled', 'true')
53+
.click();
54+
55+
modal.shouldBeOpened();
56+
modal.modalTitleShouldContain('Modify VolumeAttributesClass');
57+
58+
// Wait for and click the VAC dropdown in the modal
59+
cy.byTestID('modify-vac-dropdown', { timeout: 15000 }).should('be.visible').click();
60+
cy.byTestID('console-select-item').contains(newVACName).click();
61+
62+
modal.submit();
63+
modal.shouldBeClosed();
64+
},
65+
66+
verifyVACOnDetailsPage: (vacName: string) => {
67+
cy.get('[data-test-section-heading="PersistentVolumeClaim details"]')
68+
.parent()
69+
.contains(vacName)
70+
.should('be.visible');
71+
},
72+
};

0 commit comments

Comments
 (0)