Skip to content
This repository has been archived by the owner on Mar 1, 2024. It is now read-only.

Commit

Permalink
Added tests for vm list/vies actions (start, stop, ...) (#163)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhrazdil authored and suomiy committed Jan 17, 2019
1 parent 73d2a86 commit 4a3c423
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 93 deletions.
2 changes: 1 addition & 1 deletion frontend/integration-tests/protractor.conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export const config: Config = {
catalog: ['tests/base.scenario.ts', 'tests/catalog.scenario.ts'],
marketplace: ['tests/base.scenario.ts', 'tests/marketplace/kubernetes-marketplace.scenario.ts'],
overview: ['tests/base.scenario.ts', 'tests/overview/overview.scenario.ts'],
kubevirt: ['tests/base.scenario.ts', 'tests/kubevirt/vm.wizard.scenario.ts'],
kubevirt: ['tests/base.scenario.ts', 'tests/kubevirt/vm.wizard.scenario.ts', 'tests/kubevirt/vm.actions.scenario.ts'],
all: ['tests/base.scenario.ts',
'tests/crud.scenario.ts',
'tests/overview/overview.scenareio.ts',
Expand Down
76 changes: 76 additions & 0 deletions frontend/integration-tests/tests/kubevirt/mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* eslint-disable no-undef */
import { testName } from '../../protractor.conf';

const testLabel = 'automatedTestName';

export const testVM = {
apiVersion: 'kubevirt.io/v1alpha2',
kind: 'VirtualMachine',
metadata: {
name: `vm-${testName}`,
namespace: testName,
labels: {[testLabel]: testName},
},
spec: {
running: false,
template: {
spec: {
domain: {
cpu: {
cores: 1,
},
devices: {
disks: [
{
bootOrder: 1,
disk: {
bus: 'virtio',
},
name: 'rootdisk',
volumeName: 'rootdisk',
},
],
interfaces: [
{
bridge: {},
name: 'eth0',
},
],
},
resources: {
requests: {
memory: '1G',
},
},
},
networks: [
{
name: 'eth0',
pod: {},
},
],
volumes: [
{
containerDisk: {
image: 'kubevirt/cirros-registry-disk-demo:latest',
},
name: 'rootdisk',
},
],
},
},
},
};

export const testNAD = {
apiVersion: 'k8s.cni.cncf.io/v1',
kind: 'NetworkAttachmentDefinition',
metadata: {
name: `ovs-net-1${testName}-${testName}`,
namespace: testName,
labels: {[testLabel]: testName},
},
spec: {
config: '{ "cniVersion": "0.3.1", "type": "ovs", "bridge": "br0" }',
},
};
17 changes: 17 additions & 0 deletions frontend/integration-tests/tests/kubevirt/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-disable no-undef */
import { execSync } from 'child_process';

export function removeLeakedResources(leakedResources: Set<string>){
const leakedArray: Array<string> = [...leakedResources];
if (leakedArray.length > 0) {
console.error(`Leaked ${leakedArray.join()}`);
leakedArray.map(r => JSON.parse(r) as {name: string, namespace: string, kind: string})
.forEach(({name, namespace, kind}) => {
try {
execSync(`kubectl delete -n ${namespace} --cascade ${kind} ${name}`);
} catch (error) {
console.error(`Failed to delete ${kind} ${name}:\n${error}`);
}
});
}
}
96 changes: 96 additions & 0 deletions frontend/integration-tests/tests/kubevirt/vm.actions.scenario.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/* eslint-disable no-undef */

import { execSync } from 'child_process';
import { browser, ExpectedConditions as until } from 'protractor';

import { appHost, testName } from '../../protractor.conf';
import { resourceRowsPresent, filterForName, isLoaded } from '../../views/crud.view';
import { testVM } from './mocks';
import { removeLeakedResources } from './utils';
import {detailViewAction, detailViewVMmStatus, listViewAction, listViewVMmStatus} from '../../views/kubevirt/vm.actions.view';

const VM_BOOTUP_TIMEOUT = 60000;
const VM_ACTIONS_TIMEOUT = 90000;

describe('Test VM actions', () => {
const leakedResources = new Set<string>();
afterAll(async() => {
removeLeakedResources(leakedResources);
});

describe('Test VM list view kebab actions', () => {
const vmName = `vm-list-view-actions-${testName}`;
beforeAll(async() => {
testVM.metadata.name = vmName;
execSync(`echo '${JSON.stringify(testVM)}' | kubectl create -f -`);
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});

// Workaround for https://github.com/kubevirt/web-ui/issues/177, remove when resolved
afterEach(async() => await browser.sleep(1000));

it('Navigates to VMs', async() => {
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines`);
await isLoaded();
await filterForName(vmName);
await resourceRowsPresent();
});

it('Starts VM', async() => {
await listViewAction(vmName)('Start');
await browser.wait(until.textToBePresentInElement(listViewVMmStatus(vmName), 'Running'), VM_BOOTUP_TIMEOUT);
});

it('Restarts VM', async() => {
await listViewAction(vmName)('Restart');
await browser.wait(until.textToBePresentInElement(listViewVMmStatus(vmName), 'Starting'), VM_BOOTUP_TIMEOUT);
await browser.wait(until.textToBePresentInElement(listViewVMmStatus(vmName), 'Running'), VM_BOOTUP_TIMEOUT);
}, VM_ACTIONS_TIMEOUT);

it('Stops VM', async() => {
await listViewAction(vmName)('Stop');
await browser.wait(until.textToBePresentInElement(listViewVMmStatus(vmName), 'Off'), 10000);
});

it('Deletes VM', async() => {
await listViewAction(vmName)('Delete');
await browser.wait(until.not(until.presenceOf(listViewVMmStatus(vmName))));
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});
});

describe('Test VM detail view kebab actions', () => {
const vmName = `vm-detail-view-actions-${testName}`;
beforeAll(async() => {
testVM.metadata.name = vmName;
execSync(`echo '${JSON.stringify(testVM)}' | kubectl create -f -`);
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});

it('Navigates to VMs detail page', async() => {
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines/${vmName}`);
await isLoaded();
});

it('Starts VM', async() => {
await detailViewAction('Start');
await browser.wait(until.textToBePresentInElement(detailViewVMmStatus, 'Running'), VM_BOOTUP_TIMEOUT);
});

it('Restarts VM', async() => {
await detailViewAction('Restart');
await browser.wait(until.textToBePresentInElement(detailViewVMmStatus, 'Starting'), VM_BOOTUP_TIMEOUT);
await browser.wait(until.textToBePresentInElement(detailViewVMmStatus, 'Running'), VM_BOOTUP_TIMEOUT);
}, VM_ACTIONS_TIMEOUT);

it('Stops VM', async() => {
await detailViewAction('Stop');
await browser.wait(until.textToBePresentInElement(detailViewVMmStatus, 'Off'), 10000);
});

it('Deletes VM', async() => {
await detailViewAction('Delete');
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});
});
});
151 changes: 59 additions & 92 deletions frontend/integration-tests/tests/kubevirt/vm.wizard.scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import { execSync } from 'child_process';
import { browser, by, ExpectedConditions as until } from 'protractor';
import { appHost, testName } from '../../protractor.conf';
import { resourceRowsPresent, filterForName, deleteRow, isLoaded, createItemButton, errorMessage } from '../../views/crud.view';
import * as vmView from '../../views/kubevirt/vm.view';
import { OrderedMap } from 'immutable';

import { appHost, testName } from '../../protractor.conf';
import { resourceRowsPresent, filterForName, deleteRow, createItemButton, isLoaded, errorMessage } from '../../views/crud.view';
import { removeLeakedResources } from './utils';
import { testNAD } from './mocks';
import * as vmView from '../../views/kubevirt/vm.view';

describe('Kubevirt create VM using wizard', () => {
const leakedResources = new Set<string>();
Expand All @@ -16,19 +18,7 @@ describe('Kubevirt create VM using wizard', () => {
const workloadProfile = 'generic';
const sourceURL = 'https://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img';
const sourceContainer = 'kubevirt/cirros-registry-disk-demo:latest';
const networkDefinitionName = `${testName}-ovs-net-1`;
const pxeInterface = 'eth1';
const testNAD = {
apiVersion: 'k8s.cni.cncf.io/v1',
kind: 'NetworkAttachmentDefinition',
metadata: {
name: networkDefinitionName,
namespace: testName,
},
spec: {
config: '{ "cniVersion": "0.3.1", "type": "ovs", "bridge": "br0" }',
},
};
const provisionMethods = OrderedMap<string, (provisionSource: string) => void>()
.set('PXE', async function(provisionSource) {
await vmView.provisionSourceButton.click();
Expand All @@ -47,96 +37,73 @@ describe('Kubevirt create VM using wizard', () => {
await vmView.provisionSourceURL.sendKeys(sourceURL);
});

beforeAll(async() => {
execSync(`echo '${JSON.stringify(testNAD)}' | kubectl create -f -`);
});

afterAll(async() => {
execSync(`kubectl delete -n ${testName} net-attach-def ${networkDefinitionName}`);
const leakedArray: Array<string> = [...leakedResources];
if (leakedArray.length > 0) {
console.error(`Leaked ${leakedArray.join()}`);
leakedArray.map(r => JSON.parse(r) as {name: string, namespace: string, kind: string})
.forEach(({name, namespace, kind}) => {
try {
execSync(`kubectl delete -n ${namespace} --cascade ${kind} ${name}`);
} catch (error) {
console.error(`Failed to delete ${kind} ${name}:\n${error}`);
}
});
}
});

provisionMethods.forEach((provisionMethod, methodName) => {
describe(`Using ${methodName} method.`, () => {
it('Navigates to VMs', async() => {
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines`);
await isLoaded();
});

it('Opens VM wizard', async() => {
await createItemButton.click().then(() => vmView.createWithWizardLink.click());
});

it('Configures VM Basic Settings', async() => {
await browser.wait(until.presenceOf(vmView.nameInput), 10000);
await vmView.nameInput.sendKeys(vmName);
async function fillBasicSettings(provisionMethod: (provisionSource: string) => void, provisionSourceName: string){
await browser.wait(until.presenceOf(vmView.nameInput), 10000);
await vmView.nameInput.sendKeys(vmName);

await vmView.namespaceButton.click();
await vmView.namespaceMenu.element(by.linkText(testName)).click();
await vmView.namespaceButton.click();
await vmView.namespaceMenu.element(by.linkText(testName)).click();

await provisionMethod(methodName);
await provisionMethod(provisionSourceName);

await vmView.operatingSystemButton.click();
await vmView.operatingSystemMenu.element(by.linkText(operatingSystem)).click();
await vmView.operatingSystemButton.click();
await vmView.operatingSystemMenu.element(by.linkText(operatingSystem)).click();

await vmView.flavorButton.click();
await vmView.flavorSourceMenu.element(by.linkText(flavor)).click();
await vmView.flavorButton.click();
await vmView.flavorSourceMenu.element(by.linkText(flavor)).click();

await vmView.workloadProfileButton.click();
await vmView.workloadProfileMenu.element(by.linkText(workloadProfile)).click();
await vmView.workloadProfileButton.click();
await vmView.workloadProfileMenu.element(by.linkText(workloadProfile)).click();

await vmView.startVMOnCreation.click();
await vmView.startVMOnCreation.click();

await vmView.nextButton.click();
});
await vmView.nextButton.click();
}

it('Configures VM Networking', async() => {
if (methodName === 'PXE'){
await vmView.createNIC.click();
async function fillVMNetworking(provisionSourceName: string){
if (provisionSourceName === 'PXE'){
await vmView.createNIC.click();

await vmView.networkDefinitionButton.click();
await vmView.networkDefinitionMenu.element(by.linkText(networkDefinitionName)).click();
await vmView.networkDefinitionButton.click();
await vmView.networkDefinitionMenu.element(by.linkText(testNAD.metadata.name)).click();

await vmView.pxeNICButton.click();
await vmView.pxeNICMenu.element(by.linkText(pxeInterface)).click();
await vmView.applyButton.click();
}
await vmView.nextButton.click();
});

it('Configures VM Storage', async() => {
await vmView.nextButton.click();
});

it('Confirms to create VM', async() => {
await browser.wait(until.elementToBeClickable(vmView.nextButton), 5000).then(() => vmView.nextButton.click());
await vmView.pxeNICButton.click();
await vmView.pxeNICMenu.element(by.linkText(pxeInterface)).click();
await vmView.applyButton.click();
}
await vmView.nextButton.click();
}

expect(errorMessage.isPresent()).toBe(false);
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});
beforeAll(async() => {
execSync(`echo '${JSON.stringify(testNAD)}' | kubectl create -f -`);
});

it('Verifies created VM', async() => {
await browser.wait(until.invisibilityOf(vmView.wizardHeader), 5000);
await filterForName(vmName);
await resourceRowsPresent();
await browser.wait(until.textToBePresentInElement(vmView.firstRowVMStatus, 'Running'), 20000);
});
afterAll(async() => {
execSync(`kubectl delete -n ${testName} net-attach-def ${testNAD.metadata.name}`);
removeLeakedResources(leakedResources);
});

it('Removes created VM', async() => {
await deleteRow('VirtualMachine')(vmName);
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});
provisionMethods.forEach((provisionMethod, methodName) => {
it(`Using ${methodName} provision source.`, async() => {
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines`);
await isLoaded();
await createItemButton.click().then(() => vmView.createWithWizardLink.click());
await fillBasicSettings(provisionMethod, methodName);
await fillVMNetworking(methodName);
// Use default storage settings
await vmView.nextButton.click();
// Confirm to create VM
await browser.wait(until.elementToBeClickable(vmView.nextButton), 5000).then(() => vmView.nextButton.click());
expect(errorMessage.isPresent()).toBe(false);
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
// Verify VM is created and running
await browser.wait(until.invisibilityOf(vmView.wizardHeader), 5000);
await filterForName(vmName);
await resourceRowsPresent();
await browser.wait(until.textToBePresentInElement(vmView.firstRowVMStatus, 'Running'), 20000);
// Delete VM
await deleteRow('VirtualMachine')(vmName);
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});
});
});
2 changes: 2 additions & 0 deletions frontend/integration-tests/views/crud.view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const saveChangesBtn = $('#save-changes');
export const reloadBtn = $('#reload-object');
export const cancelBtn = $('#cancel');

export const confirmAction = () => browser.wait(until.presenceOf($('#confirm-action'))).then(() => $('#confirm-action').click());

/**
* Returns a promise that resolves after the loading spinner is not present.
*/
Expand Down
Loading

0 comments on commit 4a3c423

Please sign in to comment.