Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/integration-tests/protractor.conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export const config: Config = {
'../packages/kubevirt-plugin/integration-tests/tests/vm.actions.scenario.ts',
'../packages/kubevirt-plugin/integration-tests/tests/vm.migration.scenario.ts',
'../packages/kubevirt-plugin/integration-tests/tests/vm.resources.scenario.ts',
'../packages/kubevirt-plugin/integration-tests/tests/vm.clone.scenario.ts',
]),
all: suite([
'tests/crud.scenario.ts',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-disable no-unused-vars, no-undef */
import { browser, ExpectedConditions as until } from 'protractor';
import { click } from '../../../../console-shared/src/test-utils/utils';
import { fillInput, selectSelectorOption } from '../utils/utils';
import { PAGE_LOAD_TIMEOUT_SECS } from '../utils/consts';
import * as cloneDialogView from '../../views/cloneDialog.view';

export class CloneDialog {
async close() {
await click(cloneDialogView.cancelButton);
await browser.wait(until.invisibilityOf(cloneDialogView.modalDialog), PAGE_LOAD_TIMEOUT_SECS);
}

async fillName(name: string) {
await fillInput(cloneDialogView.nameInput, name);
}

async fillDescription(description: string) {
await fillInput(cloneDialogView.descriptionInput, description);
}

async selectNamespace(namespace: string) {
await selectSelectorOption(cloneDialogView.namespaceSelectorId, namespace);
}

async startOnCreation() {
await click(cloneDialogView.startOnCreationCheckBox);
}

async clone() {
await click(cloneDialogView.confirmButton);
await browser.wait(until.invisibilityOf(cloneDialogView.modalDialog), PAGE_LOAD_TIMEOUT_SECS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import { VMConfig } from '../utils/types';
import {
PAGE_LOAD_TIMEOUT_SECS,
VM_BOOTUP_TIMEOUT_SECS,
VM_MIGRATION_TIMEOUT_SECS,
WIZARD_CREATE_VM_ERROR,
WIZARD_TABLE_FIRST_ROW,
TABS,
DASH,
VM_IMPORT_TIMEOUT_SECS,
} from '../utils/consts';
import { detailViewAction } from '../../views/vm.actions.view';
import { detailViewAction, listViewAction } from '../../views/vm.actions.view';
import { tableRowForName } from '../../views/kubevirtDetailView.view';
import { Wizard } from './wizard';
import { KubevirtDetailView } from './kubevirtDetailView';
Expand Down Expand Up @@ -47,18 +48,27 @@ export class VirtualMachine extends KubevirtDetailView {
confirmDialog = false;
}

await detailViewAction(action, confirmDialog);
await detailViewAction(`${action} Virtual Machine`, confirmDialog);
if (waitForAction !== false) {
await vmView.waitForActionFinished(action, timeout);
}
}

async listViewAction(action: string) {
await this.navigateToListView();

let confirmDialog = true;
if (['Clone'].includes(action)) {
confirmDialog = false;
}

await listViewAction(this.name)(`${action} Virtual Machine`, confirmDialog);
}

async waitForMigrationComplete(fromNode: string, timeout: number) {
await vmView.waitForStatusIcon(vmView.statusIcons.running, VM_MIGRATION_TIMEOUT_SECS);
await browser.wait(
until.and(
waitForStringNotInElement(vmView.vmDetailNode(this.namespace, this.name), fromNode),
waitForStringNotInElement(vmView.vmDetailNode(this.namespace, this.name), DASH),
),
waitForStringNotInElement(vmView.vmDetailNode(this.namespace, this.name), fromNode),
timeout,
);
}
Expand Down Expand Up @@ -160,9 +170,13 @@ export class VirtualMachine extends KubevirtDetailView {
}
await wizard.next();

await this.navigateToTab(TABS.OVERVIEW);
if (startOnCreation === true) {
// If startOnCreation is true, wait for VM to boot up
await vmView.waitForStatusIcon(vmView.statusIcons.running, VM_BOOTUP_TIMEOUT_SECS);
} else {
// Else wait for possible import to finish
await vmView.waitForStatusIcon(vmView.statusIcons.off, VM_IMPORT_TIMEOUT_SECS);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
export const DASH = '-';
export const { STORAGE_CLASS = 'nfs-sc' } = process.env;
export const { STORAGE_CLASS = 'rook-ceph-block' } = process.env;

// TIMEOUTS
const SEC = 1000;
export const CLONE_VM_TIMEOUT_SECS = 300 * SEC;
export const CLONE_VM_TIMEOUT_SECS = 360 * SEC;
export const CLONED_VM_BOOTUP_TIMEOUT_SECS = 150 * SEC;
export const PAGE_LOAD_TIMEOUT_SECS = 15 * SEC;
export const TEMPLATE_ACTIONS_TIMEOUT_SECS = 90 * SEC;
export const VM_ACTIONS_TIMEOUT_SECS = 250 * SEC;
export const VM_BOOTUP_TIMEOUT_SECS = 200 * SEC;
export const VM_MIGRATION_TIMEOUT_SECS = 190 * SEC;
export const VM_BOOTUP_TIMEOUT_SECS = 230 * SEC;
export const VM_MIGRATION_TIMEOUT_SECS = 260 * SEC;
export const VM_STOP_TIMEOUT_SECS = 10 * SEC;
export const VM_IP_ASSIGNMENT_TIMEOUT_SECS = 180 * SEC;
export const VM_IMPORT_TIMEOUT_SECS = 80 * SEC;
export const WINDOWS_IMPORT_TIMEOUT_SECS = 150 * SEC;
export const VM_CREATE_AND_EDIT_TIMEOUT_SECS = 200 * SEC;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CloudInitConfig } from './types';
import { STORAGE_CLASS } from './consts';
import { getRandomMacAddress } from './utils';

export const multusNad = {
export const multusNAD = {
apiVersion: 'k8s.cni.cncf.io/v1',
kind: 'NetworkAttachmentDefinition',
metadata: {
Expand Down Expand Up @@ -45,20 +45,20 @@ export const dataVolumeManifest = ({ name, namespace, sourceURL, accessMode, vol
};
};

export const basicVmConfig = {
export const basicVMConfig = {
operatingSystem: 'Red Hat Enterprise Linux 7.6',
flavor: 'tiny',
workloadProfile: 'desktop',
sourceURL: 'https://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img',
sourceContainer: 'kubevirt/cirros-registry-disk-demo:latest',
sourceContainer: 'kubevirt/cirros-registry-disk-demo',
cloudInitScript: `#cloud-config\nuser: cloud-user\npassword: atomic\nchpasswd: {expire: False}\nhostname: vm-${testName}.example.com`,
};

export const networkInterface = {
name: `nic1-${testName.slice(-5)}`,
mac: getRandomMacAddress(),
binding: 'bridge',
networkDefinition: multusNad.metadata.name,
networkDefinition: multusNAD.metadata.name,
};

export const networkBindingMethods = {
Expand All @@ -82,7 +82,7 @@ export const hddDisk = {
export const cloudInitCustomScriptConfig: CloudInitConfig = {
useCloudInit: true,
useCustomScript: true,
customScript: basicVmConfig.cloudInitScript,
customScript: basicVMConfig.cloudInitScript,
};

export function getVmManifest(
Expand All @@ -91,25 +91,28 @@ export function getVmManifest(
name?: string,
cloudinit?: string,
) {
const vmName = name || `${provisionSource.toLowerCase()}-${namespace.slice(-5)}`;
const metadata = {
name: name || `${provisionSource.toLowerCase()}-${namespace.slice(-5)}`,
name: vmName,
annotations: {
'name.os.template.kubevirt.io/rhel7.6': 'Red Hat Enterprise Linux 7.6',
description: namespace,
},
namespace,
labels: {
app: `vm-${provisionSource.toLowerCase()}-${namespace}`,
app: vmName,
'flavor.template.kubevirt.io/tiny': 'true',
'os.template.kubevirt.io/rhel7.6': 'true',
'vm.kubevirt.io/template': 'rhel7-desktop-tiny',
'vm.kubevirt.io/template': 'rhel7-desktop-tiny-v0.6.2',
'vm.kubevirt.io/template-namespace': 'openshift',
'vm.kubevirt.io/template.revision': '1',
'vm.kubevirt.io/template.version': 'v0.6.2',
'workload.template.kubevirt.io/desktop': 'true',
},
};
const urlSource = {
http: {
url: 'https://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img',
url: basicVMConfig.sourceURL,
},
};
const dataVolumeTemplate = {
Expand All @@ -119,6 +122,7 @@ export function getVmManifest(
spec: {
pvc: {
accessModes: ['ReadWriteMany'],
volumeMode: 'Block',
resources: {
requests: {
storage: '1Gi',
Expand All @@ -137,7 +141,7 @@ export function getVmManifest(
};
const containerDisk = {
containerDisk: {
image: 'kubevirt/cirros-registry-disk-demo:latest',
image: basicVMConfig.sourceContainer,
},
name: 'rootdisk',
};
Expand Down Expand Up @@ -201,6 +205,8 @@ export function getVmManifest(
template: {
metadata: {
labels: {
'kubevirt.io/domain': metadata.name,
'kubevirt.io/size': 'tiny',
'vm.kubevirt.io/name': metadata.name,
},
},
Expand All @@ -213,6 +219,13 @@ export function getVmManifest(
},
devices: {
disks,
inputs: [
{
bus: 'virtio',
name: 'tablet',
type: 'tablet',
},
],
interfaces: [
{
bootOrder: 2,
Expand All @@ -228,6 +241,7 @@ export function getVmManifest(
},
},
},
evictionStrategy: 'LiveMigrate',
terminationGracePeriodSeconds: 0,
networks: [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
/* eslint-disable no-await-in-loop */
import { execSync } from 'child_process';
import * as _ from 'lodash';
import { ElementFinder, browser, ExpectedConditions as until } from 'protractor';
import { STORAGE_CLASS } from './consts';
import { $, $$, ElementFinder, browser, by, ExpectedConditions as until } from 'protractor';
import { appHost } from '../../../../../integration-tests/protractor.conf';
import {
isLoaded,
createYAMLButton,
rowForName,
} from '../../../../../integration-tests/views/crud.view';
import { STORAGE_CLASS, PAGE_LOAD_TIMEOUT_SECS } from './consts';
import { NodePortService } from './types';

export async function fillInput(elem: ElementFinder, value: string) {
Expand All @@ -13,18 +19,48 @@ export async function fillInput(elem: ElementFinder, value: string) {
if (attempts < 0) {
throw Error(`Failed to fill input with value: '${value}'.`);
}
await browser.wait(until.elementToBeClickable(elem));
await browser.wait(until.and(until.presenceOf(elem), until.elementToBeClickable(elem)));
// TODO: line below can be removed when pf4 tables in use.
await elem.click();
await elem.clear();
await elem.sendKeys(value);
} while ((await elem.getAttribute('value')) !== value && attempts > 0);
}

export async function createProject(name: string) {
// Use projects if OpenShift so non-admin users can run tests.
const resource = browser.params.openshift === 'true' ? 'projects' : 'namespaces';
await browser.get(`${appHost}/k8s/cluster/${resource}`);
await isLoaded();
const exists = await rowForName(name).isPresent();
if (!exists) {
await createYAMLButton.click();
await browser.wait(until.presenceOf($('.modal-body__field')));
await $$('.modal-body__field')
.get(0)
.$('input')
.sendKeys(name);
await $$('.modal-body__field')
.get(1)
.$('input')
.sendKeys(`test-name=${name}`);
await $('.modal-content')
.$('#confirm-action')
.click();
await browser.wait(until.urlContains(`/${name}`), PAGE_LOAD_TIMEOUT_SECS);
}
}

export async function getInputValue(elem: ElementFinder) {
return elem.getAttribute('value');
}

export async function selectSelectorOption(selectorId: string, option: string) {
await $(selectorId)
.all(by.css(`option[value="${option}"]`))
.click();
}

export function getRandStr(length: number) {
return Math.random()
.toString(36)
Expand Down
Loading