Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug 1832371: CNV-4046: Add integration test for vm environment tab #5280

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 26 additions & 5 deletions frontend/integration-tests/views/environment.view.ts
Expand Up @@ -26,13 +26,34 @@ export const addVariable = async (key: string, value: string) => {
await saveBtn.click();
};

export const addVariableFrom = async (resourceName: string, resourcePrefix: string) => {
export const addVariableFrom = async (
resourceName: string,
resourcePrefix?: string,
getExactResource?: boolean,
) => {
await isLoaded();
await dropDownBtn.first().click();
await dropDownBtn
.filter(async (elem) => {
const elemText = await elem.getText();
return elemText === 'Select a resource';
Copy link

Choose a reason for hiding this comment

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

I'm not sure if this is a good way to select a dropdown button, if I understand, this doesn't allow to change the selection in the dropdown for the second time, does it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it allows you to change the selection. this is why I need to select the dropdown which have no value.

})
.first()
.click();
await textFilter.sendKeys(resourceName);
await option.first().click();
await prefix.clear();
await prefix.sendKeys(resourcePrefix);
if (getExactResource) {
await option
.filter(async (elem) => {
return (await elem.$('.co-resource-item__resource-name').getText()) === resourceName;
})
.first()
.click();
} else {
await option.first().click();
}
if (resourcePrefix) {
await prefix.clear();
await prefix.sendKeys(resourcePrefix);
}
await browser.wait(until.elementToBeClickable(saveBtn), BROWSER_TIMEOUT);
await saveBtn.click();
};
Expand Down
Expand Up @@ -90,6 +90,11 @@ export class KubevirtUIResource extends UIResource {
await isLoaded();
}

async navigateToEnvironment() {
await this.navigateToTab(TAB.Environment);
await isLoaded();
}

async getAttachedDisks(): Promise<StorageResource[]> {
await this.navigateToTab(TAB.Disks);
const rows = await kubevirtDetailView.tableRows();
Expand Down
Expand Up @@ -79,6 +79,7 @@ export enum TAB {
Events = 'Events',
NetworkInterfaces = 'Network Interfaces',
Yaml = 'YAML',
Environment = 'Environment',
}

export enum VM_ACTION {
Expand Down
Expand Up @@ -14,6 +14,7 @@ import {
} from './consts';
import { getRandomMacAddress, getResourceObject, resolveStorageDataAttribute } from './utils';
import { Flavor, OperatingSystem, WorkloadProfile } from './constants/wizard';
import { ConfigMapKind, SecretKind, ServiceAccountKind } from '@console/internal/module/k8s/types';

export const multusNAD = {
apiVersion: 'k8s.cni.cncf.io/v1',
Expand Down Expand Up @@ -133,6 +134,49 @@ export const cloudInitCustomScriptConfig: CloudInitConfig = {
customScript: basicVMConfig.cloudInitScript,
};

export const getConfigMap = (namespace: string, name: string): ConfigMapKind => {
return {
apiVersion: 'v1',
kind: 'ConfigMap',
data: {
data1: 'value1',
data2: 'value2',
data3: 'value3',
},
metadata: {
name,
namespace,
},
};
};

export const getSecret = (namespace: string, name: string): SecretKind => {
return {
apiVersion: 'v1',
kind: 'Secret',
data: {
password: 'MWYyZDFlMmU2N2Rm',
username: 'YWRtaW4=',
},
metadata: {
name,
namespace,
},
type: 'Opaque',
};
};

export const getServiceAccount = (namespace: string, name: string): ServiceAccountKind => {
return {
apiVersion: 'v1',
kind: 'ServiceAccount',
metadata: {
name,
namespace,
},
};
};

function getMetadata(
provisionSource: 'URL' | 'PXE' | 'Container',
namespace: string,
Expand Down
@@ -0,0 +1,87 @@
#!/usr/bin/expect -f

set vm_name [lindex $argv 0]
set vm_namespace [lindex $argv 1]
set hostname vm-example
set username fedora
set password fedora

set login_prompt "$hostname login: "
set password_prompt "Password: "
set prompt "$"

set response_delay 3
set timeout 10
set send_human {.1 .3 1 .05 2}

set source_path1 "/home/fedora/source1"
set source_path2 "/home/fedora/source2"
set source_path3 "/home/fedora/source3"

spawn virtctl console $vm_name -n $vm_namespace

send -h "\n"

sleep 1

send -h \004

# Enter username
expect $login_prompt {
sleep $response_delay
send -h "$username\n"
}

# Enter Password
expect $password_prompt {
sleep $response_delay
send -h "$password\n"
}

# Run tests
expect $prompt {
sleep $response_delay

send -h "mkdir ${source_path1} \n"
send -h "mkdir ${source_path2} \n"
send -h "mkdir ${source_path3} \n"

# mount source 1
send -h "sudo mount /dev/vdc ${source_path1} \n"

# mount source 2
send -h "sudo mount /dev/vdd ${source_path2} \n"

# mount source 3
send -h "sudo mount /dev/vde ${source_path3} \n"

# Create readableFile func
send -h "function _rf() { test -r \$1 && echo SUCCESS || echo FAILED; };\n"

# Check if source1 files are readable
send -h "_rf ${source_path1}/data1 \n";
send -h "_rf ${source_path1}/data2 \n";
send -h "_rf ${source_path1}/data3 \n";

# Check if source2 files are readable
send -h "_rf ${source_path2}/ca.crt \n";
send -h "_rf ${source_path2}/namespace \n";
send -h "_rf ${source_path2}/service-ca.crt \n";
send -h "_rf ${source_path2}/token \n";

# Check if source3 files are readable
send -h "_rf ${source_path3}/ca.crt \n";
send -h "_rf ${source_path3}/namespace \n";
send -h "_rf ${source_path3}/service-ca.crt \n";
send -h "_rf ${source_path3}/token \n";

}

# exit to login prompt
expect $prompt {
send -h \004
expect -re $login_prompt
}

# exit console
send \003]
Expand Up @@ -43,6 +43,7 @@ export type CloudInitConfig = {
customScript?: string;
hostname?: string;
sshKeys?: string[];
password?: string;
};

export type NodePortService = {
Expand Down
Expand Up @@ -3,6 +3,7 @@ import { execSync } from 'child_process';
import * as _ from 'lodash';
import { $, $$, browser, by, ExpectedConditions as until } from 'protractor';
import { testName, appHost } from '@console/internal-integration-tests/protractor.conf';
import { safeLoad } from 'js-yaml';
import {
isLoaded,
createYAMLButton,
Expand All @@ -15,6 +16,7 @@ import { click } from '@console/shared/src/test-utils/utils';
import {
isLoaded as yamlPageIsLoaded,
saveButton,
getEditorContent,
} from '@console/internal-integration-tests/views/yaml.view';
import { STORAGE_CLASS, PAGE_LOAD_TIMEOUT_SECS, SEC } from './consts';
import { NodePortService, Status } from './types';
Expand Down Expand Up @@ -51,14 +53,23 @@ export async function createProject(name: string) {
}
}

export async function createExampleVMViaYAML() {
export async function createExampleVMViaYAML(getVMObj?: boolean) {
let vm = null;
await browser.get(`${appHost}/k8s/ns/${testName}/virtualization`);
await isLoaded();
await click(createItemButton);
await click(createYAMLLink);
await yamlPageIsLoaded();
if (getVMObj) {
try {
vm = safeLoad(await getEditorContent());
} catch {
return null;
}
}
await click(saveButton);
await browser.wait(until.presenceOf(resourceTitle));
return vm;
}

export async function getInputValue(elem: any) {
Expand Down
@@ -0,0 +1,133 @@
import { getSecret, getConfigMap, getServiceAccount } from './utils/mocks';
import { createResource, deleteResource, click } from '@console/shared/src/test-utils/utils';
import { browser, ExpectedConditions as until, Key, element, by } from 'protractor';
import { createExampleVMViaYAML } from './utils/utils';
import { testName } from '@console/internal-integration-tests/protractor.conf';
import { isLoaded } from '@console/internal-integration-tests/views/crud.view';
import * as vmEnv from '../views/vm.environment.view';
import { addVariableFrom } from '@console/internal-integration-tests/views/environment.view';
import {
PAGE_LOAD_TIMEOUT_SECS,
KUBEVIRT_SCRIPTS_PATH,
VM_BOOTUP_TIMEOUT_SECS,
VM_ACTION,
} from './utils/consts';
import { execSync } from 'child_process';
import { VirtualMachine } from './models/virtualMachine';

const expecScriptPath = `${KUBEVIRT_SCRIPTS_PATH}/expect-vm-env-readable.sh`;
const vmName = 'vm-example';
const configmapName = 'configmap-mock';
const secretName = 'secret-mock';
const serviceAccountName = 'service-account-mock';

describe('Test VM enviromnet tab', () => {
const secret = getSecret(testName, secretName);
const configmap = getConfigMap(testName, configmapName);
const serviceAccount = getServiceAccount(testName, serviceAccountName);
let vm: VirtualMachine;

beforeAll(async () => {
createResource(secret);
createResource(configmap);
createResource(serviceAccount);
const vmObj = await createExampleVMViaYAML(true);
vm = new VirtualMachine(vmObj.metadata);
});

afterAll(() => {
deleteResource(secret);
deleteResource(configmap);
deleteResource(serviceAccount);
});

beforeEach(async () => {
await vm.navigateToEnvironment();
});

it('Add configmap, secret and service account', async () => {
await vmEnv.addSource(configmapName);
await vmEnv.addSource(secretName);

// Add Service Account
await click(
element(by.buttonText('Add All From Config Map or Secret')),
PAGE_LOAD_TIMEOUT_SECS,
);
await addVariableFrom(serviceAccountName, null, true);
await browser.wait(until.presenceOf(vmEnv.successAlert));
expect(vmEnv.successAlert.isDisplayed()).toEqual(true);
});

it('Verify all the sources are present at the disks tab', async () => {
const disks = await vm.getAttachedDisks();
expect(!!disks.find((d) => d.name.includes(configmapName))).toBeTruthy();
expect(!!disks.find((d) => d.name.includes(secretName))).toBeTruthy();
expect(!!disks.find((d) => d.name.includes(serviceAccountName))).toBeTruthy();
});

it(
'Verify all sources are readable inside the VM',
async () => {
await vm.action(VM_ACTION.Start);
const out = execSync(`expect ${expecScriptPath} ${vmName} ${testName}`).toString();
const isFailedTest = out.split('\n').find((line) => line === 'FAILED');
expect(!isFailedTest).toBeTruthy();
},
VM_BOOTUP_TIMEOUT_SECS * 2, // VM boot time + test sources time
);

it('Error when resource has no serial', async () => {
await vmEnv.serialField.get(1).clear();
await vmEnv.serialField.get(1).sendKeys('i', Key.BACK_SPACE); // workaround: for some reason clear() is not enough
await browser.wait(until.elementToBeClickable(vmEnv.saveBtn), PAGE_LOAD_TIMEOUT_SECS);
await click(vmEnv.saveBtn, PAGE_LOAD_TIMEOUT_SECS);
await browser.wait(until.presenceOf(vmEnv.errorAlert), PAGE_LOAD_TIMEOUT_SECS);
const errorText = await vmEnv.errorAlert.getText();
expect(vmEnv.errorAlert.isDisplayed()).toEqual(true);
expect(errorText).toContain(vmEnv.noSerialError);
});

it('Error when two sources have the same serial', async () => {
const firstSerial = await vmEnv.serialField.get(0).getAttribute('value');
await vmEnv.serialField.get(1).clear();
await vmEnv.serialField.get(1).sendKeys(firstSerial);
await browser.wait(until.elementToBeClickable(vmEnv.saveBtn), PAGE_LOAD_TIMEOUT_SECS);
Copy link

Choose a reason for hiding this comment

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

again, you could use click as mentioned before

await click(vmEnv.saveBtn, PAGE_LOAD_TIMEOUT_SECS);
await browser.wait(until.presenceOf(vmEnv.errorAlert), PAGE_LOAD_TIMEOUT_SECS);
expect(vmEnv.errorAlert.isDisplayed()).toEqual(true);
const errorText = await vmEnv.errorAlert.getText();
expect(errorText).toContain(vmEnv.dupSerialsError);
});

it('Cannot use the same resource more than once', async () => {
await click(
element(by.buttonText('Add All From Config Map or Secret')),
PAGE_LOAD_TIMEOUT_SECS,
);
await isLoaded();
await vmEnv.dropDownBtn
.filter(async (elem) => {
const elemText = await elem.getText();
return elemText === 'Select a resource';
})
.first()
.click();
await vmEnv.textFilter.sendKeys(configmapName);
const optionSize = await vmEnv.option
.filter(async (elem) => {
return (await elem.$('.co-resource-item__resource-name').getText()) === configmapName;
})
.count();
expect(optionSize).toEqual(0);
});

it('Delete a source', async () => {
const pairCountBefore = await vmEnv.allPairRows.count();
await vmEnv.deleteButton.first().click();
await browser.wait(until.elementToBeClickable(vmEnv.saveBtn), PAGE_LOAD_TIMEOUT_SECS);
await click(vmEnv.saveBtn, PAGE_LOAD_TIMEOUT_SECS);
const pairCountAfter = await vmEnv.allPairRows.count();
expect(pairCountBefore).toEqual(pairCountAfter + 1);
});
});