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
107 changes: 107 additions & 0 deletions frontend/e2e/clients/kubernetes-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,14 @@ export default class KubernetesClient {
}
}

async createSecret(namespace: string, body: k8s.V1Secret): Promise<unknown> {
return this.k8sApi.createNamespacedSecret({ namespace, body });
}

async getSecret(name: string, namespace: string): Promise<k8s.V1Secret> {
return this.k8sApi.readNamespacedSecret({ name, namespace });
}

async patchSecret(name: string, namespace: string, patch: object[]): Promise<void> {
await this.k8sApi.patchNamespacedSecret({ name, namespace, body: patch });
}
Expand Down Expand Up @@ -452,6 +460,105 @@ export default class KubernetesClient {
return response;
}

async createClusterCustomResource(
group: string,
version: string,
plural: string,
body: Record<string, unknown>,
): Promise<unknown> {
return this.coApi.createClusterCustomObject({ body, group, plural, version });
}

async deleteClusterCustomResource(
group: string,
version: string,
plural: string,
name: string,
): Promise<void> {
try {
await this.coApi.deleteClusterCustomObject({ group, name, plural, version });
} catch (err) {
if (!isNotFound(err)) {
throw err;
}
}
}

async getClusterCustomResource(
group: string,
version: string,
plural: string,
name: string,
): Promise<unknown> {
return this.coApi.getClusterCustomObject({ group, name, plural, version });
}

async patchClusterCustomResource(
group: string,
version: string,
plural: string,
name: string,
patch: object,
): Promise<unknown> {
return this.coApi.patchClusterCustomObject({
body: patch,
group,
name,
plural,
version,
contentType: k8s.PatchStrategy.MergePatch,
} as any);
}

async createPVC(namespace: string, body: k8s.V1PersistentVolumeClaim): Promise<unknown> {
return this.k8sApi.createNamespacedPersistentVolumeClaim({ namespace, body });
}

async deletePVC(name: string, namespace: string): Promise<void> {
try {
await this.k8sApi.deleteNamespacedPersistentVolumeClaim({ name, namespace });
} catch (err) {
if (!isNotFound(err)) {
throw err;
}
}
}

async getPVC(name: string, namespace: string): Promise<k8s.V1PersistentVolumeClaim> {
return this.k8sApi.readNamespacedPersistentVolumeClaim({ name, namespace });
}

async createDeployment(namespace: string, body: k8s.V1Deployment): Promise<unknown> {
return this.appsApi.createNamespacedDeployment({ namespace, body });
}

async getDeployment(name: string, namespace: string): Promise<k8s.V1Deployment> {
return this.appsApi.readNamespacedDeployment({ name, namespace });
}

async deleteDeployment(name: string, namespace: string): Promise<void> {
try {
await this.appsApi.deleteNamespacedDeployment({ name, namespace });
} catch (err) {
if (!isNotFound(err)) {
throw err;
}
}
}

async patchDeployment(
name: string,
namespace: string,
patch: object,
): Promise<unknown> {
return this.appsApi.patchNamespacedDeployment({
name,
namespace,
body: patch,
contentType: k8s.PatchStrategy.JsonPatch,
} as any);
}

async listCustomResources(
group: string,
version: string,
Expand Down
1 change: 1 addition & 0 deletions frontend/e2e/fixtures/secrets/asciisecret.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is ascii
Binary file added frontend/e2e/fixtures/secrets/binarysecret.bin
Binary file not shown.
1 change: 1 addition & 0 deletions frontend/e2e/fixtures/secrets/unicodesecret.utf8
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ȹȻɣʄɣɱɰ
223 changes: 223 additions & 0 deletions frontend/e2e/mocks/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
export const testerDeployment = {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: 'busybox-deployment',
labels: { app: 'busybox' },
},
spec: {
replicas: 1,
strategy: { type: 'RollingUpdate' },
selector: { matchLabels: { app: 'busybox' } },
template: {
metadata: { labels: { app: 'busybox' } },
spec: {
volumes: [
{
name: 'testpvc',
persistentVolumeClaim: { claimName: 'testpvc' },
},
],
containers: [
{
name: 'busybox',
image: 'busybox',
imagePullPolicy: 'IfNotPresent',
volumeDevices: [{ name: 'testpvc', devicePath: '/data' }],
command: ['sh', '-c', 'echo Container 1 is Running ; sleep 3600'],
},
],
},
},
},
};
Comment on lines +1 to +33
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP 'testerDeployment|volumeMode|volumeDevices' --type=ts -C2

Repository: openshift/console

Length of output: 28032


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the PVC fixture in the referenced mock file
nl -ba frontend/e2e/mocks/storage.ts | sed -n '1,120p'

# Check whether other e2e storage mocks/tests set Block volumeMode
rg -n "volumeMode:\s*['\"]Block['\"]|volumeMode:\s*Block|spec:\s*\{[^}]*volumeMode" frontend/e2e -S

Repository: openshift/console

Length of output: 104


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== frontend/e2e/mocks/storage.ts (first 120 lines) =="
cat -n frontend/e2e/mocks/storage.ts | sed -n '1,120p'

echo
echo "== volumeMode occurrences in e2e mocks/tests (storage) =="
rg -n "volumeMode\s*:\s*['\"]?Block['\"]?|volumeMode\s*:\s*['\"]?Filesystem['\"]?|volumeDevices|devicePath" frontend/e2e -S

Repository: openshift/console

Length of output: 4874


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== volumeDevices usage in e2e storage tests =="
rg -n "volumeDevices|devicePath" frontend/e2e/tests/console/storage -S

echo
echo "== volumeMode usage in e2e storage tests =="
rg -n "volumeMode\s*:\s*['\"]?(Block|Filesystem)['\"]?" frontend/e2e/tests/console/storage -S

echo
echo "== volumeMode usage in e2e mocks (broader, but limited to e2e folder) =="
rg -n "volumeMode\s*:\s*['\"]?(Block|Filesystem)['\"]?" frontend/e2e/mocks -S

Repository: openshift/console

Length of output: 108


🏁 Script executed:

#!/bin/bash
set -uo pipefail

echo "== volumeDevices usage in e2e storage tests =="
rg -n "volumeDevices|devicePath" frontend/e2e/tests/console/storage -S || true

echo
echo "== volumeMode usage in e2e storage tests =="
rg -n "volumeMode\s*:\s*['\"]?(Block|Filesystem)['\"]?" frontend/e2e/tests/console/storage -S || true

echo
echo "== volumeMode usage in e2e mocks (limited to e2e) =="
rg -n "volumeMode\s*:\s*['\"]?(Block|Filesystem)['\"]?" frontend/e2e/mocks -S || true

Repository: openshift/console

Length of output: 207


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show where testerDeployment/PVC are used in the storage e2e tests
rg -n "testerDeployment|createDeployment\(|createPVC\(|\bPVC\b" frontend/e2e/tests/console/storage -S

echo
echo "== snapshot.spec.ts (relevant sections) =="
sed -n '1,260p' frontend/e2e/tests/console/storage/snapshot.spec.ts

echo
echo "== clone.spec.ts (relevant sections) =="
sed -n '1,220p' frontend/e2e/tests/console/storage/clone.spec.ts

Repository: openshift/console

Length of output: 18799


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== testerDeployment usage across e2e =="
rg -n "testerDeployment" frontend/e2e -S

Repository: openshift/console

Length of output: 1402


Align PVC spec.volumeMode with testerDeployment’s volumeDevices usage.
In frontend/e2e/mocks/storage.ts, testerDeployment mounts testpvc via volumeDevices (block-mode), but the PVC fixture omits spec.volumeMode (defaults to Filesystem). This mismatch can prevent the Deployment pod from scheduling/attaching even though the clone/snapshot tests only wait for the PVC to become Bound. Set PVC.spec.volumeMode: 'Block' for this test (or switch testerDeployment to volumeMounts if the intent is filesystem).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/e2e/mocks/storage.ts` around lines 1 - 33, The testerDeployment uses
volumeDevices (block device) but the PVC fixture lacks spec.volumeMode and
defaults to Filesystem; update the PVC fixture to include spec.volumeMode:
'Block' so it matches testerDeployment, or alternatively change testerDeployment
to use volumeMounts if a filesystem volume was intended; locate the PVC fixture
used by the tests and add spec.volumeMode: 'Block' (or switch volumeDevices ->
volumeMounts in testerDeployment) to keep volumeMode and mount type consistent.


export const PVC = {
apiVersion: 'v1',
kind: 'PersistentVolumeClaim',
metadata: { name: 'testpvc' },
spec: {
storageClassName: 'gp2-csi',
accessModes: ['ReadWriteOnce'],
resources: { requests: { storage: '1Gi' } },
},
};

export const PVCGP3 = {
apiVersion: 'v1',
kind: 'PersistentVolumeClaim',
metadata: { name: 'testpvcgp3' },
spec: {
storageClassName: 'gp3-csi',
accessModes: ['ReadWriteOnce'],
resources: { requests: { storage: '1Gi' } },
},
};

export const SnapshotClass = {
apiVersion: 'snapshot.storage.k8s.io/v1',
kind: 'VolumeSnapshotClass',
metadata: { name: 'csi-hostpath-snapclass' },
driver: 'ebs.csi.aws.com',
deletionPolicy: 'Delete',
};

export const patchForVolume = {
op: 'add' as const,
path: '/spec/template/spec/volumes/-',
value: {
name: 'testpvc-snapshot-restore',
persistentVolumeClaim: { claimName: 'testpvc-snapshot-restore' },
},
};

// --- Storage Class provisioner fixtures ---

export type Parameter = {
name: string;
id?: string;
values?: string | string[];
hintText?: string;
nestedParameter?: Parameter;
};

export const provisionersMap: Record<string, Parameter[]> = {
'kubernetes.io/aws-ebs': [
{ name: 'Type', id: 'type', values: ['io1', 'gp2', 'sc1', 'st1'] },
{ name: 'IOPS per GiB', id: 'iopsPerGB', values: '10' },
{ name: 'Filesystem type', id: 'fsType', values: 'ext4' },
{
name: 'Encrypted',
id: 'encrypted',
nestedParameter: { name: 'KMS key ID', values: 'sample-kms-id' },
},
],
'kubernetes.io/cinder': [
{ name: 'Volume type', id: 'type', values: 'ext4' },
{ name: 'Availability zone', id: 'availability', values: 'lalitpur' },
],
'kubernetes.io/azure-file': [
{ name: 'SKU name', id: 'skuName', hintText: 'Azure storage account SKU tier', values: 'sample-name' },
{ name: 'Location', id: 'location', hintText: 'Azure storage account name', values: 'bhaktapur' },
{
name: 'Azure storage account name',
id: 'storageAccount',
hintText: 'Azure storage account name',
values: 'test-account',
},
],
'kubernetes.io/azure-disk': [
{ name: 'Storage account type', id: 'storageaccounttype', hintText: 'Storage account type', values: 'tester' },
{ name: 'Account kind', id: 'kind', values: ['shared', 'dedicated', 'managed'] },
],
'kubernetes.io/quobyte': [
{ name: 'Quobyte API server', id: 'quobyteAPIServer', values: 'test.xyzab' },
{ name: 'Registry address(es)', id: 'registry', values: 'xyz.abc' },
{ name: 'Admin secret name', id: 'adminSecretName', values: 'secret-admin' },
{ name: 'Admin secret namespace', id: 'adminSecretNamespace', values: 'secret-ns' },
{ name: 'User', id: 'user', values: 'admin' },
{ name: 'Quobyte configuration', id: 'quobyteConfig', values: 'config' },
{ name: 'Quobyte tenant', id: 'quobyteTenant', values: 'tester' },
],
'kubernetes.io/vsphere-volume': [
{ name: 'Disk format', id: 'diskformat', values: ['thin', 'zeroed thick', 'eager zeroed thick'] },
{ name: 'Datastore', id: 'datastore', values: 'store-thin' },
],
'kubernetes.io/portworx-volume': [
{ name: 'Filesystem', id: 'fs', values: ['none', 'xfs', 'ext4'] },
{ name: 'Block size', id: 'block_size', values: '1024' },
{
name: 'Number of synchronous replicas to be provided in the form of replication factor',
id: 'repl',
values: '2',
},
{ name: 'I/O priority', id: 'io_priority', values: ['high', 'medium', 'low'] },
{ name: 'Snapshot interval', id: 'snap_interval', values: '5' },
{ name: 'Aggregation level', id: 'aggregation_level', values: '2024' },
{ name: 'Ephemeral', id: 'ephemeral' },
],
'kubernetes.io/scaleio': [
{ name: 'API gateway', id: 'gateway', values: 'abc.xyz' },
{ name: 'System name', id: 'system', values: 'test-sys' },
{ name: 'Protection domain', id: 'protectionDomain', values: 'local' },
{ name: 'Storage pool', id: 'storagePool', values: 'simple-pool' },
{ name: 'Storage mode', id: 'storageMode', values: ['ThinProvisioned', 'ThickProvisioned'] },
{ name: 'Secret reference', id: 'secretRef', values: 'simpleSecret' },
{ name: 'Read Only', id: 'readOnly' },
{ name: 'Filesystem Type', id: 'fsType', values: 'ext2' },
],
'kubernetes.io/storageos': [
{ name: 'Pool', id: 'pool', values: 'test-pool' },
{ name: 'Description', id: 'description', values: 'storage drive' },
{ name: 'Filesystem type', id: 'fsType', values: 'ext3' },
{ name: 'Admin secret name', id: 'adminSecretName', values: 'admin-secret-name' },
{ name: 'Admin secret namespace', id: 'adminSecretNamespace', values: 'secret-ns' },
],
};

// --- VolumeAttributesClass fixtures ---

export const getVACFixtures = (suffix: string) => {
const names = {
TEST_VAC_LOW_IOPS: `test-vac-low-iops-${suffix}`,
TEST_VAC_HIGH_IOPS: `test-vac-high-iops-${suffix}`,
TEST_VAC_INVALID: `test-vac-invalid-${suffix}`,
TEST_STORAGECLASS: `test-storageclass-${suffix}`,
TEST_PVC: 'test-pvc',
TEST_DEPLOYMENT: 'test-deployment',
};

return {
...names,
VAC_LOW_IOPS: {
apiVersion: 'storage.k8s.io/v1',
kind: 'VolumeAttributesClass',
metadata: { name: names.TEST_VAC_LOW_IOPS },
driverName: 'ebs.csi.aws.com',
parameters: { iops: '3000', throughput: '125', type: 'gp3' },
},
VAC_HIGH_IOPS: {
apiVersion: 'storage.k8s.io/v1',
kind: 'VolumeAttributesClass',
metadata: { name: names.TEST_VAC_HIGH_IOPS },
driverName: 'ebs.csi.aws.com',
parameters: { iops: '3000', throughput: '125', type: 'gp3' },
},
Comment on lines +172 to +185
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

VAC_LOW_IOPS and VAC_HIGH_IOPS have identical parameters.

Both define iops: '3000', throughput: '125', type: 'gp3'. Any test that modifies a PVC from the low to the high VAC won't actually exercise a parameter change, so the assertion becomes a no-op. Differentiate the high-IOPS values.

Proposed fix
     VAC_HIGH_IOPS: {
       apiVersion: 'storage.k8s.io/v1',
       kind: 'VolumeAttributesClass',
       metadata: { name: names.TEST_VAC_HIGH_IOPS },
       driverName: 'ebs.csi.aws.com',
-      parameters: { iops: '3000', throughput: '125', type: 'gp3' },
+      parameters: { iops: '6000', throughput: '250', type: 'gp3' },
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
VAC_LOW_IOPS: {
apiVersion: 'storage.k8s.io/v1',
kind: 'VolumeAttributesClass',
metadata: { name: names.TEST_VAC_LOW_IOPS },
driverName: 'ebs.csi.aws.com',
parameters: { iops: '3000', throughput: '125', type: 'gp3' },
},
VAC_HIGH_IOPS: {
apiVersion: 'storage.k8s.io/v1',
kind: 'VolumeAttributesClass',
metadata: { name: names.TEST_VAC_HIGH_IOPS },
driverName: 'ebs.csi.aws.com',
parameters: { iops: '3000', throughput: '125', type: 'gp3' },
},
VAC_LOW_IOPS: {
apiVersion: 'storage.k8s.io/v1',
kind: 'VolumeAttributesClass',
metadata: { name: names.TEST_VAC_LOW_IOPS },
driverName: 'ebs.csi.aws.com',
parameters: { iops: '3000', throughput: '125', type: 'gp3' },
},
VAC_HIGH_IOPS: {
apiVersion: 'storage.k8s.io/v1',
kind: 'VolumeAttributesClass',
metadata: { name: names.TEST_VAC_HIGH_IOPS },
driverName: 'ebs.csi.aws.com',
parameters: { iops: '6000', throughput: '250', type: 'gp3' },
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/e2e/mocks/storage.ts` around lines 172 - 185, VAC_LOW_IOPS and
VAC_HIGH_IOPS in the storage mocks use identical parameters so tests that switch
a PVC between them don't exercise a parameter change; update the VAC_HIGH_IOPS
mock (symbol: VAC_HIGH_IOPS) to use distinctly higher IOPS/throughput values
than VAC_LOW_IOPS (e.g., increase the iops and throughput parameters and keep
type 'gp3') so assertions comparing before/after VAC changes actually detect a
difference.

VAC_INVALID: {
apiVersion: 'storage.k8s.io/v1',
kind: 'VolumeAttributesClass',
metadata: { name: names.TEST_VAC_INVALID },
driverName: 'ebs.csi.aws.com',
parameters: { iops: '999999', throughput: '999999', type: 'gp3' },
},
STORAGE_CLASS: {
apiVersion: 'storage.k8s.io/v1',
kind: 'StorageClass',
metadata: { name: names.TEST_STORAGECLASS },
provisioner: 'ebs.csi.aws.com',
allowVolumeExpansion: true,
},
getDeployment: (namespace: string, pvcName: string) => ({
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: { name: names.TEST_DEPLOYMENT, namespace },
spec: {
replicas: 1,
selector: { matchLabels: { app: 'test-app' } },
template: {
metadata: { labels: { app: 'test-app' } },
spec: {
containers: [
{
name: 'container',
image: 'image-registry.openshift-image-registry.svc:5000/openshift/httpd:latest',
volumeMounts: [{ name: 'storage', mountPath: '/data' }],
},
],
volumes: [{ name: 'storage', persistentVolumeClaim: { claimName: pvcName } }],
},
},
},
}),
};
};
12 changes: 12 additions & 0 deletions frontend/e2e/pages/base-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ export default abstract class BasePage {
protected async goTo(url: string): Promise<void> {
await this.page.goto(url, { timeout: 90_000 });
await this.waitForLoadingComplete();
await this.dismissTourIfPresent();
}

private async dismissTourIfPresent(): Promise<void> {
const skipButton = this.page.getByRole('button', { name: 'Skip tour' });
try {
if ((await skipButton.count()) > 0) {
await skipButton.click({ timeout: 3_000 });
}
} catch {
// Tour not present or already dismissed
}
}

protected locator(
Expand Down
Loading