From eff2da2518a7908f69d6f62557a3b1c9656aa94d Mon Sep 17 00:00:00 2001 From: SadiJr <31869303+SadiJr@users.noreply.github.com> Date: Wed, 21 Jul 2021 15:07:25 -0300 Subject: [PATCH] Refactor and improvements for method com.cloud.hypervisor.kvm.resource.LibvirtComputingResource.createVMFromSpec() (#5149) * Refactor method createVMFromSpec * Add unit tests * Fix test * Extract if block to method for add extra configs to VM Domain XML * Split travis tests trying to isolate which test is causing an error * Override toString() method * Update documentation * Fix checkstyle error (line with trailing spaces) * Change VirtualMachineTO print of object * Add try except to find message error. Remove after test * Fix indent * Trying to understanding why is happening in this code * Refactor method createVMFromSpec * Add unit tests * Fix test * Extract if block to method for add extra configs to VM Domain XML * Split travis tests trying to isolate which test is causing an error * Override toString() method * Update documentation * Fix checkstyle error (line with trailing spaces) * Remove unnecessary comment * Revert travis tests Co-authored-by: SadiJr <17a0db2854@firemailbox.club> --- .../cloud/agent/api/to/VirtualMachineTO.java | 5 + .../resource/LibvirtComputingResource.java | 530 +++++++++++------ .../LibvirtComputingResourceTest.java | 545 +++++++++++++++--- 3 files changed, 829 insertions(+), 251 deletions(-) diff --git a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java index c4729383dd4..8a30b5ef9fe 100644 --- a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java @@ -413,4 +413,9 @@ public class VirtualMachineTO { public void setDeployAsIsInfo(DeployAsIsInfoTO deployAsIsInfo) { this.deployAsIsInfo = deployAsIsInfo; } + + @Override + public String toString() { + return String.format("VM {id: \"%s\", name: \"%s\", uuid: \"%s\", type: \"%s\"}", id, name, uuid, type); + } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 338a41b6c25..48752d1c88c 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -209,6 +209,71 @@ import com.google.common.base.Strings; public class LibvirtComputingResource extends ServerResourceBase implements ServerResource, VirtualRouterDeployer { private static final Logger s_logger = Logger.getLogger(LibvirtComputingResource.class); + private static final String LEGACY = "legacy"; + private static final String SECURE = "secure"; + + /** + * Machine type. + */ + private static final String PC = "pc"; + private static final String VIRT = "virt"; + + /** + * Possible devices to add to VM. + */ + private static final String TABLET = "tablet"; + private static final String USB = "usb"; + private static final String MOUSE = "mouse"; + private static final String KEYBOARD = "keyboard"; + + /** + * Policies used by VM. + */ + private static final String RESTART = "restart"; + private static final String DESTROY = "destroy"; + + private static final String KVMCLOCK = "kvmclock"; + private static final String HYPERVCLOCK = "hypervclock"; + private static final String WINDOWS = "Windows"; + private static final String Q35 = "q35"; + private static final String PTY = "pty"; + private static final String VNC = "vnc"; + + /** + * Acronym of System Management Mode. Perform low-level system management operations while an OS is running. + */ + private static final String SMM = "smm"; + /** + * Acronym of Advanced Configuration and Power Interface.
+ * Provides an open standard that operating systems can use to discover and configure + * computer hardware components, to perform power management. + */ + private static final String ACPI = "acpi"; + /** + * Acronym of Advanced Programmable Interrupt Controllers.
+ * With an I/O APIC, operating systems can use more than 16 interrupt requests (IRQs) + * and therefore avoid IRQ sharing for improved reliability. + */ + private static final String APIC = "apic"; + /** + * Acronym of Physical Address Extension. Feature implemented in modern x86 processors.
+ * PAE extends memory addressing capabilities, allowing more than 4 GB of random access memory (RAM) to be used. + */ + private static final String PAE = "pae"; + /** + * Libvirt supports guest CPU mode since 0.9.10. + */ + private static final int MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_MODE = 9010; + /** + * The CPU tune element provides details of the CPU tunable parameters for the domain.
+ * It is supported since Libvirt 0.9.0 + */ + private static final int MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_TUNE = 9000; + /** + * Constant that defines ARM64 (aarch64) guest architectures. + */ + private static final String AARCH64 = "aarch64"; + private String _modifyVlanPath; private String _versionstringpath; private String _patchScriptPath; @@ -2239,8 +2304,13 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv } } + /** + * Creates VM KVM definitions from virtual machine transfer object specifications. + */ public LibvirtVMDef createVMFromSpec(final VirtualMachineTO vmTO) { - final LibvirtVMDef vm = new LibvirtVMDef(); + s_logger.debug(String.format("Creating VM from specifications [%s]", vmTO.toString())); + + LibvirtVMDef vm = new LibvirtVMDef(); vm.setDomainName(vmTO.getName()); String uuid = vmTO.getUuid(); uuid = getUuid(uuid); @@ -2251,215 +2321,311 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv Map customParams = vmTO.getDetails(); boolean isUefiEnabled = false; boolean isSecureBoot = false; - String bootMode =null; + String bootMode = null; + if (MapUtils.isNotEmpty(customParams) && customParams.containsKey(GuestDef.BootType.UEFI.toString())) { isUefiEnabled = true; - bootMode = customParams.get(GuestDef.BootType.UEFI.toString()); - if (StringUtils.isNotBlank(bootMode) && "secure".equalsIgnoreCase(bootMode)) { + s_logger.debug(String.format("Enabled UEFI for VM UUID [%s].", uuid)); + + if (isSecureMode(customParams.get(GuestDef.BootType.UEFI.toString()))) { + s_logger.debug(String.format("Enabled Secure Boot for VM UUID [%s].", uuid)); isSecureBoot = true; } } Map extraConfig = vmTO.getExtraConfig(); if (dpdkSupport && (!extraConfig.containsKey(DpdkHelper.DPDK_NUMA) || !extraConfig.containsKey(DpdkHelper.DPDK_HUGE_PAGES))) { - s_logger.info("DPDK is enabled but it needs extra configurations for CPU NUMA and Huge Pages for VM deployment"); + s_logger.info(String.format("DPDK is enabled for VM [%s], but it needs extra configurations for CPU NUMA and Huge Pages for VM deployment.", vmTO.toString())); + } + configureVM(vmTO, vm, customParams, isUefiEnabled, isSecureBoot, bootMode, extraConfig, uuid); + return vm; + } + + /** + * Configures created VM from specification, adding the necessary components to VM. + */ + private void configureVM(VirtualMachineTO vmTO, LibvirtVMDef vm, Map customParams, boolean isUefiEnabled, boolean isSecureBoot, String bootMode, + Map extraConfig, String uuid) { + s_logger.debug(String.format("Configuring VM with UUID [%s].", uuid)); + + GuestDef guest = createGuestFromSpec(vmTO, vm, uuid, customParams); + if (isUefiEnabled) { + configureGuestIfUefiEnabled(isSecureBoot, bootMode, guest); } - final GuestDef guest = new GuestDef(); + vm.addComp(guest); + vm.addComp(createGuestResourceDef(vmTO)); - if (HypervisorType.LXC == _hypervisorType && VirtualMachine.Type.User == vmTO.getType()) { - // LXC domain is only valid for user VMs. Use KVM for system VMs. - guest.setGuestType(GuestDef.GuestType.LXC); - vm.setHvsType(HypervisorType.LXC.toString().toLowerCase()); - } else { - guest.setGuestType(GuestDef.GuestType.KVM); - vm.setHvsType(HypervisorType.KVM.toString().toLowerCase()); - vm.setLibvirtVersion(_hypervisorLibvirtVersion); - vm.setQemuVersion(_hypervisorQemuVersion); + int vcpus = vmTO.getCpus(); + if (!extraConfig.containsKey(DpdkHelper.DPDK_NUMA)) { + vm.addComp(createCpuModeDef(vmTO, vcpus)); } + + if (_hypervisorLibvirtVersion >= MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_TUNE) { + vm.addComp(createCpuTuneDef(vmTO)); + } + + FeaturesDef features = createFeaturesDef(customParams, isUefiEnabled, isSecureBoot); + enlightenWindowsVm(vmTO, features); + vm.addComp(features); + + vm.addComp(createTermPolicy()); + vm.addComp(createClockDef(vmTO)); + vm.addComp(createDevicesDef(vmTO, guest, vcpus, isUefiEnabled)); + + addExtraConfigsToVM(vmTO, vm, extraConfig); + } + + /** + * Adds extra configuration to User VM Domain XML before starting. + */ + private void addExtraConfigsToVM(VirtualMachineTO vmTO, LibvirtVMDef vm, Map extraConfig) { + if (MapUtils.isNotEmpty(extraConfig) && VirtualMachine.Type.User.equals(vmTO.getType())) { + s_logger.debug(String.format("Appending extra configuration data [%s] to guest VM [%s] domain XML.", extraConfig, vmTO.toString())); + addExtraConfigComponent(extraConfig, vm); + } + } + + /** + * Adds devices components to VM. + */ + protected DevicesDef createDevicesDef(VirtualMachineTO vmTO, GuestDef guest, int vcpus, boolean isUefiEnabled) { + DevicesDef devices = new DevicesDef(); + devices.setEmulatorPath(_hypervisorPath); + devices.setGuestType(guest.getGuestType()); + devices.addDevice(createSerialDef()); + + if (_rngEnable) { + devices.addDevice(createRngDef()); + } + + devices.addDevice(createChannelDef(vmTO)); + devices.addDevice(createWatchDogDef()); + devices.addDevice(createVideoDef()); + devices.addDevice(createConsoleDef()); + devices.addDevice(createGraphicDef(vmTO)); + devices.addDevice(createTabletInputDef()); + + if (isGuestAarch64()) { + createArm64UsbDef(devices); + } + + DiskDef.DiskBus busT = getDiskModelFromVMDetail(vmTO); + if (busT == null) { + busT = getGuestDiskModel(vmTO.getPlatformEmulator(), isUefiEnabled); + } + + if (busT == DiskDef.DiskBus.SCSI) { + devices.addDevice(createSCSIDef(vcpus)); + } + return devices; + } + + protected WatchDogDef createWatchDogDef() { + return new WatchDogDef(_watchDogAction, _watchDogModel); + } + + protected void createArm64UsbDef(DevicesDef devices) { + devices.addDevice(new InputDef(KEYBOARD, USB)); + devices.addDevice(new InputDef(MOUSE, USB)); + devices.addDevice(new LibvirtVMDef.USBDef((short)0, 0, 5, 0, 0)); + } + + protected InputDef createTabletInputDef() { + return new InputDef(TABLET, USB); + } + + /** + * Creates a Libvirt Graphic Definition with the VM's password and VNC address. + */ + protected GraphicDef createGraphicDef(VirtualMachineTO vmTO) { + return new GraphicDef(VNC, (short)0, true, vmTO.getVncAddr(), vmTO.getVncPassword(), null); + } + + /** + * Adds a Virtio channel for the Qemu Guest Agent tools. + */ + protected ChannelDef createChannelDef(VirtualMachineTO vmTO) { + File virtIoChannel = Paths.get(_qemuSocketsPath.getPath(), vmTO.getName() + "." + _qemuGuestAgentSocketName).toFile(); + return new ChannelDef(_qemuGuestAgentSocketName, ChannelDef.ChannelType.UNIX, virtIoChannel); + } + + /** + * Creates Virtio SCSI controller.
+ * The respective Virtio SCSI XML definition is generated only if the VM's Disk Bus is of ISCSI. + */ + protected SCSIDef createSCSIDef(int vcpus) { + return new SCSIDef((short)0, 0, 0, 9, 0, vcpus); + } + + protected ConsoleDef createConsoleDef() { + return new ConsoleDef(PTY, null, null, (short)0); + } + + protected VideoDef createVideoDef() { + return new VideoDef(_videoHw, _videoRam); + } + + protected RngDef createRngDef() { + return new RngDef(_rngPath, _rngBackendModel, _rngRateBytes, _rngRatePeriod); + } + + protected SerialDef createSerialDef() { + return new SerialDef(PTY, null, (short)0); + } + + protected ClockDef createClockDef(final VirtualMachineTO vmTO) { + ClockDef clock = new ClockDef(); + if (org.apache.commons.lang.StringUtils.startsWith(vmTO.getOs(), WINDOWS)) { + clock.setClockOffset(ClockDef.ClockOffset.LOCALTIME); + clock.setTimer(HYPERVCLOCK, null, null); + } else if ((vmTO.getType() != VirtualMachine.Type.User || isGuestPVEnabled(vmTO.getOs())) && _hypervisorLibvirtVersion >= MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_MODE) { + clock.setTimer(KVMCLOCK, null, null, _noKvmClock); + } + return clock; + } + + protected TermPolicy createTermPolicy() { + TermPolicy term = new TermPolicy(); + term.setCrashPolicy(DESTROY); + term.setPowerOffPolicy(DESTROY); + term.setRebootPolicy(RESTART); + return term; + } + + protected FeaturesDef createFeaturesDef(Map customParams, boolean isUefiEnabled, boolean isSecureBoot) { + FeaturesDef features = new FeaturesDef(); + features.addFeatures(PAE); + features.addFeatures(APIC); + features.addFeatures(ACPI); + if (isUefiEnabled && isSecureBoot) { + features.addFeatures(SMM); + } + return features; + } + + /** + * A 4.0.X/4.1.X management server doesn't send the correct JSON + * command for getMinSpeed, it only sends a 'speed' field.
+ * So, to create a cpu tune, if getMinSpeed() returns null we fall back to getSpeed().
+ * This way a >4.1 agent can work communicate a <=4.1 management server.
+ * This change is due to the overcommit feature in 4.2. + */ + protected CpuTuneDef createCpuTuneDef(VirtualMachineTO vmTO) { + CpuTuneDef ctd = new CpuTuneDef(); + int shares = vmTO.getCpus() * (vmTO.getMinSpeed() != null ? vmTO.getMinSpeed() : vmTO.getSpeed()); + ctd.setShares(shares); + setQuotaAndPeriod(vmTO, ctd); + return ctd; + } + + private CpuModeDef createCpuModeDef(VirtualMachineTO vmTO, int vcpus) { + final CpuModeDef cmd = new CpuModeDef(); + cmd.setMode(_guestCpuMode); + cmd.setModel(_guestCpuModel); + if (VirtualMachine.Type.User.equals(vmTO.getType())) { + cmd.setFeatures(_cpuFeatures); + } + setCpuTopology(cmd, vcpus, vmTO.getDetails()); + return cmd; + } + + /** + * Creates guest resources based in VM specification. + */ + protected GuestResourceDef createGuestResourceDef(VirtualMachineTO vmTO) { + GuestResourceDef grd = new GuestResourceDef(); + + grd.setMemorySize(vmTO.getMaxRam() / 1024); + if (vmTO.getMinRam() != vmTO.getMaxRam() && !_noMemBalloon) { + grd.setMemBalloning(true); + grd.setCurrentMem(vmTO.getMinRam() / 1024); + } + grd.setVcpuNum(vmTO.getCpus()); + return grd; + } + + private void configureGuestIfUefiEnabled(boolean isSecureBoot, String bootMode, GuestDef guest) { + setGuestLoader(bootMode, SECURE, guest, GuestDef.GUEST_LOADER_SECURE); + setGuestLoader(bootMode, LEGACY, guest, GuestDef.GUEST_LOADER_LEGACY); + + if (isUefiPropertieNotNull(GuestDef.GUEST_NVRAM_PATH)) { + guest.setNvram(_uefiProperties.getProperty(GuestDef.GUEST_NVRAM_PATH)); + } + + if (isSecureBoot && isUefiPropertieNotNull(GuestDef.GUEST_NVRAM_TEMPLATE_SECURE) && SECURE.equalsIgnoreCase(bootMode)) { + guest.setNvramTemplate(_uefiProperties.getProperty(GuestDef.GUEST_NVRAM_TEMPLATE_SECURE)); + } else if (isUefiPropertieNotNull(GuestDef.GUEST_NVRAM_TEMPLATE_LEGACY)) { + guest.setNvramTemplate(_uefiProperties.getProperty(GuestDef.GUEST_NVRAM_TEMPLATE_LEGACY)); + } + } + + private void setGuestLoader(String bootMode, String mode, GuestDef guest, String propertie) { + if (isUefiPropertieNotNull(propertie) && mode.equalsIgnoreCase(bootMode)) { + guest.setLoader(_uefiProperties.getProperty(propertie)); + } + } + + private boolean isUefiPropertieNotNull(String propertie) { + return _uefiProperties.getProperty(propertie) != null; + } + + private boolean isGuestAarch64() { + return AARCH64.equals(_guestCpuArch); + } + + /** + * Creates a guest definition from a VM specification. + */ + protected GuestDef createGuestFromSpec(VirtualMachineTO vmTO, LibvirtVMDef vm, String uuid, Map customParams) { + GuestDef guest = new GuestDef(); + + configureGuestAndVMHypervisorType(vmTO, vm, guest); guest.setGuestArch(_guestCpuArch != null ? _guestCpuArch : vmTO.getArch()); - guest.setMachineType(_guestCpuArch != null && _guestCpuArch.equals("aarch64") ? "virt" : "pc"); + guest.setMachineType(isGuestAarch64() ? VIRT : PC); guest.setBootType(GuestDef.BootType.BIOS); if (MapUtils.isNotEmpty(customParams) && customParams.containsKey(GuestDef.BootType.UEFI.toString())) { guest.setBootType(GuestDef.BootType.UEFI); guest.setBootMode(GuestDef.BootMode.LEGACY); - guest.setMachineType("q35"); - if (StringUtils.isNotBlank(customParams.get(GuestDef.BootType.UEFI.toString())) && "secure".equalsIgnoreCase(customParams.get(GuestDef.BootType.UEFI.toString()))) { - guest.setBootMode(GuestDef.BootMode.SECURE); // setting to secure mode + guest.setMachineType(Q35); + if (SECURE.equalsIgnoreCase(customParams.get(GuestDef.BootType.UEFI.toString()))) { + guest.setBootMode(GuestDef.BootMode.SECURE); } } guest.setUuid(uuid); guest.setBootOrder(GuestDef.BootOrder.CDROM); guest.setBootOrder(GuestDef.BootOrder.HARDISK); + return guest; + } - if (isUefiEnabled) { - if (_uefiProperties.getProperty(GuestDef.GUEST_LOADER_SECURE) != null && "secure".equalsIgnoreCase(bootMode)) { - guest.setLoader(_uefiProperties.getProperty(GuestDef.GUEST_LOADER_SECURE)); - } - - if (_uefiProperties.getProperty(GuestDef.GUEST_LOADER_LEGACY) != null && "legacy".equalsIgnoreCase(bootMode)) { - guest.setLoader(_uefiProperties.getProperty(GuestDef.GUEST_LOADER_LEGACY)); - } - - if (_uefiProperties.getProperty(GuestDef.GUEST_NVRAM_PATH) != null) { - guest.setNvram(_uefiProperties.getProperty(GuestDef.GUEST_NVRAM_PATH)); - } - - if (isSecureBoot) { - if (_uefiProperties.getProperty(GuestDef.GUEST_NVRAM_TEMPLATE_SECURE) != null && "secure".equalsIgnoreCase(bootMode)) { - guest.setNvramTemplate(_uefiProperties.getProperty(GuestDef.GUEST_NVRAM_TEMPLATE_SECURE)); - } - } else { - if (_uefiProperties.getProperty(GuestDef.GUEST_NVRAM_TEMPLATE_LEGACY) != null) { - guest.setNvramTemplate(_uefiProperties.getProperty(GuestDef.GUEST_NVRAM_TEMPLATE_LEGACY)); - } - } - } - - vm.addComp(guest); - - final GuestResourceDef grd = new GuestResourceDef(); - - if (vmTO.getMinRam() != vmTO.getMaxRam() && !_noMemBalloon) { - grd.setMemBalloning(true); - grd.setCurrentMem(vmTO.getMinRam() / 1024); - grd.setMemorySize(vmTO.getMaxRam() / 1024); + protected void configureGuestAndVMHypervisorType(VirtualMachineTO vmTO, LibvirtVMDef vm, GuestDef guest) { + if (HypervisorType.LXC == _hypervisorType && VirtualMachine.Type.User.equals(vmTO.getType())) { + configureGuestAndUserVMToUseLXC(vm, guest); } else { - grd.setMemorySize(vmTO.getMaxRam() / 1024); + configureGuestAndSystemVMToUseKVM(vm, guest); } - final int vcpus = vmTO.getCpus(); - grd.setVcpuNum(vcpus); - vm.addComp(grd); - - if (!extraConfig.containsKey(DpdkHelper.DPDK_NUMA)) { - final CpuModeDef cmd = new CpuModeDef(); - cmd.setMode(_guestCpuMode); - cmd.setModel(_guestCpuModel); - if (vmTO.getType() == VirtualMachine.Type.User) { - cmd.setFeatures(_cpuFeatures); - } - setCpuTopology(cmd, vcpus, vmTO.getDetails()); - vm.addComp(cmd); - } - - if (_hypervisorLibvirtVersion >= 9000) { - final CpuTuneDef ctd = new CpuTuneDef(); - /** - A 4.0.X/4.1.X management server doesn't send the correct JSON - command for getMinSpeed, it only sends a 'speed' field. - - So if getMinSpeed() returns null we fall back to getSpeed(). - - This way a >4.1 agent can work communicate a <=4.1 management server - - This change is due to the overcommit feature in 4.2 - */ - if (vmTO.getMinSpeed() != null) { - ctd.setShares(vmTO.getCpus() * vmTO.getMinSpeed()); - } else { - ctd.setShares(vmTO.getCpus() * vmTO.getSpeed()); - } - - setQuotaAndPeriod(vmTO, ctd); - - vm.addComp(ctd); - } - - final FeaturesDef features = new FeaturesDef(); - features.addFeatures("pae"); - features.addFeatures("apic"); - features.addFeatures("acpi"); - if (isUefiEnabled && isSecureMode(customParams.get(GuestDef.BootType.UEFI.toString()))) { - features.addFeatures("smm"); - } - - //KVM hyperv enlightenment features based on OS Type - enlightenWindowsVm(vmTO, features); - - vm.addComp(features); - - final TermPolicy term = new TermPolicy(); - term.setCrashPolicy("destroy"); - term.setPowerOffPolicy("destroy"); - term.setRebootPolicy("restart"); - vm.addComp(term); - - final ClockDef clock = new ClockDef(); - if (vmTO.getOs().startsWith("Windows")) { - clock.setClockOffset(ClockDef.ClockOffset.LOCALTIME); - clock.setTimer("hypervclock", null, null); - } else if (vmTO.getType() != VirtualMachine.Type.User || isGuestPVEnabled(vmTO.getOs())) { - if (_hypervisorLibvirtVersion >= 9 * 1000 + 10) { - clock.setTimer("kvmclock", null, null, _noKvmClock); - } - } - - vm.addComp(clock); - - final DevicesDef devices = new DevicesDef(); - devices.setEmulatorPath(_hypervisorPath); - devices.setGuestType(guest.getGuestType()); - - final SerialDef serial = new SerialDef("pty", null, (short)0); - devices.addDevice(serial); - - if (_rngEnable) { - final RngDef rngDevice = new RngDef(_rngPath, _rngBackendModel, _rngRateBytes, _rngRatePeriod); - devices.addDevice(rngDevice); - } - - /* Add a VirtIO channel for the Qemu Guest Agent tools */ - File virtIoChannel = Paths.get(_qemuSocketsPath.getPath(), vmTO.getName() + "." + _qemuGuestAgentSocketName).toFile(); - devices.addDevice(new ChannelDef(_qemuGuestAgentSocketName, ChannelDef.ChannelType.UNIX, virtIoChannel)); - - devices.addDevice(new WatchDogDef(_watchDogAction, _watchDogModel)); - - final VideoDef videoCard = new VideoDef(_videoHw, _videoRam); - devices.addDevice(videoCard); - - final ConsoleDef console = new ConsoleDef("pty", null, null, (short)0); - devices.addDevice(console); - - //add the VNC port passwd here, get the passwd from the vmInstance. - final String passwd = vmTO.getVncPassword(); - final GraphicDef grap = new GraphicDef("vnc", (short)0, true, vmTO.getVncAddr(), passwd, null); - devices.addDevice(grap); - - final InputDef input = new InputDef("tablet", "usb"); - devices.addDevice(input); - - // Add an explicit USB devices for ARM64 - if (_guestCpuArch != null && _guestCpuArch.equals("aarch64")) { - devices.addDevice(new InputDef("keyboard", "usb")); - devices.addDevice(new InputDef("mouse", "usb")); - devices.addDevice(new LibvirtVMDef.USBDef((short)0, 0, 5, 0, 0)); - } - - DiskDef.DiskBus busT = getDiskModelFromVMDetail(vmTO); - - if (busT == null) { - busT = getGuestDiskModel(vmTO.getPlatformEmulator(), isUefiEnabled); - } - - // If we're using virtio scsi, then we need to add a virtual scsi controller - if (busT == DiskDef.DiskBus.SCSI) { - final SCSIDef sd = new SCSIDef((short)0, 0, 0, 9, 0, vcpus); - devices.addDevice(sd); - } - - vm.addComp(devices); - - // Add extra configuration to User VM Domain XML before starting - if (vmTO.getType().equals(VirtualMachine.Type.User) && MapUtils.isNotEmpty(extraConfig)) { - s_logger.info("Appending extra configuration data to guest VM domain XML"); - addExtraConfigComponent(extraConfig, vm); - } - - return vm; } /** - * Add extra configurations (if any) as a String component to the domain XML + * KVM domain is only valid for system VMs. Use LXC for user VMs. + */ + private void configureGuestAndSystemVMToUseKVM(LibvirtVMDef vm, GuestDef guest) { + guest.setGuestType(GuestDef.GuestType.KVM); + vm.setHvsType(HypervisorType.KVM.toString().toLowerCase()); + vm.setLibvirtVersion(_hypervisorLibvirtVersion); + vm.setQemuVersion(_hypervisorQemuVersion); + } + + /** + * LXC domain is only valid for user VMs. Use KVM for system VMs. + */ + private void configureGuestAndUserVMToUseLXC(LibvirtVMDef vm, GuestDef guest) { + guest.setGuestType(GuestDef.GuestType.LXC); + vm.setHvsType(HypervisorType.LXC.toString().toLowerCase()); + } + + /** + * Adds extra configurations (if any) as a String component to the domain XML */ protected void addExtraConfigComponent(Map extraConfig, LibvirtVMDef vm) { if (MapUtils.isNotEmpty(extraConfig)) { diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index 1020ff86e3a..86f30bf3bc3 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -79,6 +79,7 @@ import org.libvirt.jna.virDomainMemoryStats; import org.mockito.BDDMockito; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.Spy; import org.mockito.invocation.InvocationOnMock; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PowerMockIgnore; @@ -160,11 +161,27 @@ import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.agent.api.to.VolumeTO; import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource; import com.cloud.exception.InternalErrorException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.kvm.resource.KVMHABase.NfsStoragePool; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ClockDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ConsoleDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.CpuTuneDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DevicesDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.FeaturesDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GraphicDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestDef.GuestType; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestResourceDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InputDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SCSIDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SerialDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.TermPolicy; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.VideoDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef; import com.cloud.hypervisor.kvm.resource.wrapper.LibvirtRequestWrapper; import com.cloud.hypervisor.kvm.resource.wrapper.LibvirtUtilitiesHelper; import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk; @@ -201,6 +218,8 @@ public class LibvirtComputingResourceTest { VirtualMachineTO vmTO; @Mock LibvirtVMDef vmDef; + @Spy + private LibvirtComputingResource libvirtComputingResourceSpy = Mockito.spy(LibvirtComputingResource.class); private final static long HYPERVISOR_LIBVIRT_VERSION_SUPPORTS_IOURING = 6003000; private final static long HYPERVISOR_QEMU_VERSION_SUPPORTS_IOURING = 5000000; @@ -217,6 +236,7 @@ public class LibvirtComputingResourceTest { @Before public void setup() throws Exception { + libvirtComputingResourceSpy._qemuSocketsPath = new File("/var/run/qemu"); Scanner scanner = new Scanner(memInfo); PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scanner); } @@ -243,15 +263,13 @@ public class LibvirtComputingResourceTest { final String vncAddr = ""; final String vncPassword = "mySuperSecretPassword"; - final LibvirtComputingResource lcr = new LibvirtComputingResource(); - lcr._qemuSocketsPath = new File("/var/run/qemu"); - final VirtualMachineTO to = new VirtualMachineTO(id, name, VirtualMachine.Type.User, cpus, speed, minRam, maxRam, BootloaderType.HVM, os, false, false, vncPassword); to.setVncAddr(vncAddr); to.setArch("x86_64"); to.setUuid("b0f0a72d-7efb-3cad-a8ff-70ebf30b3af9"); + to.setVcpuMaxLimit(cpus + 1); - final LibvirtVMDef vm = lcr.createVMFromSpec(to); + final LibvirtVMDef vm = libvirtComputingResourceSpy.createVMFromSpec(to); vm.setHvsType(hyperVisorType); verifyVm(to, vm); @@ -276,15 +294,13 @@ public class LibvirtComputingResourceTest { final String vncAddr = ""; final String vncPassword = "mySuperSecretPassword"; - final LibvirtComputingResource lcr = new LibvirtComputingResource(); - lcr._qemuSocketsPath = new File("/var/run/qemu"); - final VirtualMachineTO to = new VirtualMachineTO(id, name, VirtualMachine.Type.User, cpus, minSpeed, maxSpeed, minRam, maxRam, BootloaderType.HVM, os, false, false, vncPassword); to.setVncAddr(vncAddr); to.setArch("x86_64"); to.setUuid("b0f0a72d-7efb-3cad-a8ff-70ebf30b3af9"); + to.setVcpuMaxLimit(cpus + 1); - final LibvirtVMDef vm = lcr.createVMFromSpec(to); + final LibvirtVMDef vm = libvirtComputingResourceSpy.createVMFromSpec(to); vm.setHvsType(hyperVisorType); verifyVm(to, vm); @@ -309,14 +325,11 @@ public class LibvirtComputingResourceTest { final String vncAddr = ""; final String vncPassword = "mySuperSecretPassword"; - final LibvirtComputingResource lcr = new LibvirtComputingResource(); - lcr._qemuSocketsPath = new File("/var/run/qemu"); - final VirtualMachineTO to = new VirtualMachineTO(id, name, VirtualMachine.Type.User, cpus, minSpeed, maxSpeed, minRam, maxRam, BootloaderType.HVM, os, false, false, vncPassword); to.setVncAddr(vncAddr); to.setUuid("b0f0a72d-7efb-3cad-a8ff-70ebf30b3af9"); - final LibvirtVMDef vm = lcr.createVMFromSpec(to); + LibvirtVMDef vm = libvirtComputingResourceSpy.createVMFromSpec(to); vm.setHvsType(hyperVisorType); verifyVm(to, vm); @@ -331,59 +344,388 @@ public class LibvirtComputingResourceTest { */ @Test public void testCreateVMFromSpec() { - final int id = random.nextInt(65534); - final String name = "test-instance-1"; - - final int cpus = random.nextInt(2) + 1; - final int minSpeed = 1024; - final int maxSpeed = 2048; - final int minRam = 256 * 1024; - final int maxRam = 512 * 1024; - - final String os = "Ubuntu"; - - final String vncAddr = ""; - final String vncPassword = "mySuperSecretPassword"; - - final LibvirtComputingResource lcr = new LibvirtComputingResource(); - lcr._qemuSocketsPath = new File("/var/run/qemu"); - - final VirtualMachineTO to = - new VirtualMachineTO(id, name, VirtualMachine.Type.User, cpus, minSpeed, maxSpeed, minRam, maxRam, BootloaderType.HVM, os, false, false, vncPassword); - to.setVncAddr(vncAddr); - to.setArch("x86_64"); - to.setUuid("b0f0a72d-7efb-3cad-a8ff-70ebf30b3af9"); - - final LibvirtVMDef vm = lcr.createVMFromSpec(to); + VirtualMachineTO to = createDefaultVM(false); + final LibvirtVMDef vm = libvirtComputingResourceSpy.createVMFromSpec(to); vm.setHvsType(hyperVisorType); verifyVm(to, vm); } - private void verifyVm(final VirtualMachineTO to, final LibvirtVMDef vm) { - final Document domainDoc = parse(vm.toString()); - assertXpath(domainDoc, "/domain/@type", vm.getHvsType()); - assertXpath(domainDoc, "/domain/name/text()", to.getName()); - assertXpath(domainDoc, "/domain/uuid/text()", to.getUuid()); - assertXpath(domainDoc, "/domain/description/text()", to.getOs()); - assertXpath(domainDoc, "/domain/clock/@offset", "utc"); - assertNodeExists(domainDoc, "/domain/features/pae"); - assertNodeExists(domainDoc, "/domain/features/apic"); - assertNodeExists(domainDoc, "/domain/features/acpi"); - assertXpath(domainDoc, "/domain/devices/serial/@type", "pty"); - assertXpath(domainDoc, "/domain/devices/serial/target/@port", "0"); - assertXpath(domainDoc, "/domain/devices/graphics/@type", "vnc"); - assertXpath(domainDoc, "/domain/devices/graphics/@listen", to.getVncAddr()); - assertXpath(domainDoc, "/domain/devices/graphics/@autoport", "yes"); - assertXpath(domainDoc, "/domain/devices/graphics/@passwd", to.getVncPassword()); + @Test + public void testCreateGuestFromSpecWithoutCustomParam() { + VirtualMachineTO to = createDefaultVM(false); + LibvirtVMDef vm = new LibvirtVMDef(); + GuestDef guestDef = libvirtComputingResourceSpy.createGuestFromSpec(to, vm, to.getUuid(), null); + verifySysInfo(guestDef, "smbios", to.getUuid(), "pc"); + Assert.assertEquals(GuestDef.BootType.BIOS, guestDef.getBootType()); + Assert.assertNull(guestDef.getBootMode()); + } - assertXpath(domainDoc, "/domain/devices/console/@type", "pty"); - assertXpath(domainDoc, "/domain/devices/console/target/@port", "0"); - assertXpath(domainDoc, "/domain/devices/input/@type", "tablet"); - assertXpath(domainDoc, "/domain/devices/input/@bus", "usb"); + @Test + public void testCreateGuestFromSpecWithCustomParamAndUefi() { + VirtualMachineTO to = createDefaultVM(false); - assertNodeExists(domainDoc, "/domain/devices/channel"); - assertXpath(domainDoc, "/domain/devices/channel/@type", ChannelDef.ChannelType.UNIX.toString()); + Map extraConfig = new HashMap<>(); + extraConfig.put(GuestDef.BootType.UEFI.toString(), "legacy"); + + LibvirtVMDef vm = new LibvirtVMDef(); + + GuestDef guestDef = libvirtComputingResourceSpy.createGuestFromSpec(to, vm, to.getUuid(), extraConfig); + verifySysInfo(guestDef, "smbios", to.getUuid(), "q35"); + Assert.assertEquals(GuestDef.BootType.UEFI, guestDef.getBootType()); + Assert.assertEquals(GuestDef.BootMode.LEGACY, guestDef.getBootMode()); + } + + @Test + public void testCreateGuestFromSpecWithCustomParamUefiAndSecure() { + VirtualMachineTO to = createDefaultVM(false); + + Map extraConfig = new HashMap<>(); + extraConfig.put(GuestDef.BootType.UEFI.toString(), "secure"); + + LibvirtVMDef vm = new LibvirtVMDef(); + + GuestDef guestDef = libvirtComputingResourceSpy.createGuestFromSpec(to, vm, to.getUuid(), extraConfig); + verifySysInfo(guestDef, "smbios", to.getUuid(), "q35"); + Assert.assertEquals(GuestDef.BootType.UEFI, guestDef.getBootType()); + Assert.assertEquals(GuestDef.BootMode.SECURE, guestDef.getBootMode()); + } + + @Test + public void testCreateGuestResourceDef() { + VirtualMachineTO to = createDefaultVM(false); + + GuestResourceDef guestResourceDef = libvirtComputingResourceSpy.createGuestResourceDef(to); + verifyGuestResourceDef(guestResourceDef, to); + } + + @Test + public void testCreateDevicesDef() { + VirtualMachineTO to = createDefaultVM(false); + + GuestDef guest = new GuestDef(); + guest.setGuestType(GuestType.KVM); + + DevicesDef devicesDef = libvirtComputingResourceSpy.createDevicesDef(to, guest, to.getCpus() + 1, false); + verifyDevices(devicesDef, to); + } + + @Test + public void testCreateDevicesWithSCSIDisk() { + VirtualMachineTO to = createDefaultVM(false); + to.setDetails(new HashMap<>()); + libvirtComputingResourceSpy._guestCpuArch = "aarch64"; + + GuestDef guest = new GuestDef(); + guest.setGuestType(GuestType.KVM); + + DevicesDef devicesDef = libvirtComputingResourceSpy.createDevicesDef(to, guest, to.getCpus() + 1, false); + verifyDevices(devicesDef, to); + + Document domainDoc = parse(devicesDef.toString()); + assertNodeExists(domainDoc, "/devices/controller[@type='scsi']"); + assertNodeExists(domainDoc, "/devices/controller[@model='virtio-scsi']"); + assertNodeExists(domainDoc, "/devices/controller/address[@type='pci']"); + assertNodeExists(domainDoc, "/devices/controller/driver[@queues='" + (to.getCpus() + 1) + "']"); + } + + @Test + public void testConfigureGuestAndSystemVMToUseKVM() { + VirtualMachineTO to = createDefaultVM(false); + libvirtComputingResourceSpy._hypervisorLibvirtVersion = 100; + libvirtComputingResourceSpy._hypervisorQemuVersion = 10; + LibvirtVMDef vm = new LibvirtVMDef(); + + GuestDef guestFromSpec = libvirtComputingResourceSpy.createGuestFromSpec(to, vm, to.getUuid(), null); + Assert.assertEquals(GuestDef.GuestType.KVM, guestFromSpec.getGuestType()); + Assert.assertEquals(HypervisorType.KVM.toString().toLowerCase(), vm.getHvsType()); + } + + @Test + public void testConfigureGuestAndUserVMToUseLXC() { + VirtualMachineTO to = createDefaultVM(false); + libvirtComputingResourceSpy._hypervisorType = HypervisorType.LXC; + LibvirtVMDef vm = new LibvirtVMDef(); + + GuestDef guestFromSpec = libvirtComputingResourceSpy.createGuestFromSpec(to, vm, to.getUuid(), null); + Assert.assertEquals(GuestDef.GuestType.LXC, guestFromSpec.getGuestType()); + Assert.assertEquals(HypervisorType.LXC.toString().toLowerCase(), vm.getHvsType()); + } + + @Test + public void testCreateCpuTuneDefWithoutQuotaAndPeriod() { + VirtualMachineTO to = createDefaultVM(false); + + CpuTuneDef cpuTuneDef = libvirtComputingResourceSpy.createCpuTuneDef(to); + Document domainDoc = parse(cpuTuneDef.toString()); + assertXpath(domainDoc, "/cputune/shares/text()", String.valueOf(cpuTuneDef.getShares())); + } + + @Test + public void testCreateCpuTuneDefWithQuotaAndPeriod() { + VirtualMachineTO to = createDefaultVM(true); + to.setCpuQuotaPercentage(10.0); + + CpuTuneDef cpuTuneDef = libvirtComputingResourceSpy.createCpuTuneDef(to); + Document domainDoc = parse(cpuTuneDef.toString()); + assertXpath(domainDoc, "/cputune/shares/text()", String.valueOf(cpuTuneDef.getShares())); + assertXpath(domainDoc, "/cputune/quota/text()", String.valueOf(cpuTuneDef.getQuota())); + assertXpath(domainDoc, "/cputune/period/text()", String.valueOf(cpuTuneDef.getPeriod())); + } + + @Test + public void testCreateCpuTuneDefWithMinQuota() { + VirtualMachineTO to = createDefaultVM(true); + to.setCpuQuotaPercentage(0.01); + + CpuTuneDef cpuTuneDef = libvirtComputingResourceSpy.createCpuTuneDef(to); + Document domainDoc = parse(cpuTuneDef.toString()); + assertXpath(domainDoc, "/cputune/shares/text()", String.valueOf(cpuTuneDef.getShares())); + assertXpath(domainDoc, "/cputune/quota/text()", "1000"); + assertXpath(domainDoc, "/cputune/period/text()", String.valueOf(cpuTuneDef.getPeriod())); + } + + @Test + public void testCreateDefaultClockDef() { + VirtualMachineTO to = createDefaultVM(false); + + ClockDef clockDef = libvirtComputingResourceSpy.createClockDef(to); + Document domainDoc = parse(clockDef.toString()); + + assertXpath(domainDoc, "/clock/@offset", "utc"); + } + + @Test + public void testCreateClockDefWindows() { + VirtualMachineTO to = createDefaultVM(false); + to.setOs("Windows"); + + ClockDef clockDef = libvirtComputingResourceSpy.createClockDef(to); + Document domainDoc = parse(clockDef.toString()); + + assertXpath(domainDoc, "/clock/@offset", "localtime"); + assertXpath(domainDoc, "/clock/timer/@name", "hypervclock"); + assertXpath(domainDoc, "/clock/timer/@present", "yes"); + } + + @Test + public void testCreateClockDefKvmclock() { + VirtualMachineTO to = createDefaultVM(false); + libvirtComputingResourceSpy._hypervisorLibvirtVersion = 9020; + + ClockDef clockDef = libvirtComputingResourceSpy.createClockDef(to); + Document domainDoc = parse(clockDef.toString()); + + assertXpath(domainDoc, "/clock/@offset", "utc"); + assertXpath(domainDoc, "/clock/timer/@name", "kvmclock"); + } + + @Test + public void testCreateTermPolicy() { + TermPolicy termPolicy = libvirtComputingResourceSpy.createTermPolicy(); + + String xml = "\n" + termPolicy.toString() + ""; + Document domainDoc = parse(xml); + + assertXpath(domainDoc, "/terms/on_reboot/text()", "restart"); + assertXpath(domainDoc, "/terms/on_poweroff/text()", "destroy"); + assertXpath(domainDoc, "/terms/on_crash/text()", "destroy"); + } + + @Test + public void testCreateFeaturesDef() { + VirtualMachineTO to = createDefaultVM(false); + FeaturesDef featuresDef = libvirtComputingResourceSpy.createFeaturesDef(null, false, false); + + String xml = "" + featuresDef.toString() + ""; + Document domainDoc = parse(xml); + + verifyFeatures(domainDoc); + } + + @Test + public void testCreateFeaturesDefWithUefi() { + VirtualMachineTO to = createDefaultVM(false); + HashMap extraConfig = new HashMap<>(); + extraConfig.put(GuestDef.BootType.UEFI.toString(), ""); + + FeaturesDef featuresDef = libvirtComputingResourceSpy.createFeaturesDef(extraConfig, true, true); + + String xml = "" + featuresDef.toString() + ""; + Document domainDoc = parse(xml); + + verifyFeatures(domainDoc); + } + + @Test + public void testCreateWatchDog() { + WatchDogDef watchDogDef = libvirtComputingResourceSpy.createWatchDogDef(); + verifyWatchDogDevices(parse(watchDogDef.toString()), ""); + } + + @Test + public void testCreateArm64UsbDef() { + DevicesDef devicesDef = new DevicesDef(); + + libvirtComputingResourceSpy.createArm64UsbDef(devicesDef); + Document domainDoc = parse(devicesDef.toString()); + + assertXpath(domainDoc, "/devices/controller/@type", "usb"); + assertXpath(domainDoc, "/devices/controller/@model", "qemu-xhci"); + assertXpath(domainDoc, "/devices/controller/address/@type", "pci"); + assertNodeExists(domainDoc, "/devices/input[@type='keyboard']"); + assertNodeExists(domainDoc, "/devices/input[@bus='usb']"); + assertNodeExists(domainDoc, "/devices/input[@type='mouse']"); + assertNodeExists(domainDoc, "/devices/input[@bus='usb']"); + } + + @Test + public void testCreateInputDef() { + InputDef inputDef = libvirtComputingResourceSpy.createTabletInputDef(); + verifyTabletInputDevice(parse(inputDef.toString()), ""); + } + + @Test + public void testCreateGraphicDef() { + VirtualMachineTO to = createDefaultVM(false); + GraphicDef graphicDef = libvirtComputingResourceSpy.createGraphicDef(to); + verifyGraphicsDevices(to, parse(graphicDef.toString()), ""); + } + + @Test + public void testCreateChannelDef() { + VirtualMachineTO to = createDefaultVM(false); + ChannelDef channelDef = libvirtComputingResourceSpy.createChannelDef(to); + verifyChannelDevices(to, parse(channelDef.toString()), ""); + } + + @Test + public void testCreateSCSIDef() { + VirtualMachineTO to = createDefaultVM(false); + + SCSIDef scsiDef = libvirtComputingResourceSpy.createSCSIDef(to.getCpus()); + Document domainDoc = parse(scsiDef.toString()); + verifyScsi(to, domainDoc, ""); + } + + @Test + public void testCreateConsoleDef() { + VirtualMachineTO to = createDefaultVM(false); + ConsoleDef consoleDef = libvirtComputingResourceSpy.createConsoleDef(); + verifyConsoleDevices(parse(consoleDef.toString()), ""); + } + + @Test + public void testCreateVideoDef() { + VirtualMachineTO to = createDefaultVM(false); + libvirtComputingResourceSpy._videoRam = 200; + libvirtComputingResourceSpy._videoHw = "vGPU"; + + VideoDef videoDef = libvirtComputingResourceSpy.createVideoDef(); + Document domainDoc = parse(videoDef.toString()); + assertXpath(domainDoc, "/video/model/@type", "vGPU"); + assertXpath(domainDoc, "/video/model/@vram", "200"); + } + + @Test + public void testCreateRngDef() { + VirtualMachineTO to = createDefaultVM(false); + RngDef rngDef = libvirtComputingResourceSpy.createRngDef(); + Document domainDoc = parse(rngDef.toString()); + + assertXpath(domainDoc, "/rng/@model", "virtio"); + assertXpath(domainDoc, "/rng/rate/@period", "1000"); + assertXpath(domainDoc, "/rng/rate/@bytes", "2048"); + assertXpath(domainDoc, "/rng/backend/@model", "random"); + assertXpath(domainDoc, "/rng/backend/text()", "/dev/random"); + } + + @Test + public void testCreateSerialDef() { + VirtualMachineTO to = createDefaultVM(false); + SerialDef serialDef = libvirtComputingResourceSpy.createSerialDef(); + verifySerialDevices(parse(serialDef.toString()), ""); + } + + private VirtualMachineTO createDefaultVM(boolean limitCpuUse) { + int id = random.nextInt(65534); + String name = "test-instance-1"; + + int cpus = random.nextInt(2) + 1; + int minSpeed = 1024; + int maxSpeed = 2048; + int minRam = 256 * 1024; + int maxRam = 512 * 1024; + + String os = "Ubuntu"; + String vncAddr = ""; + String vncPassword = "mySuperSecretPassword"; + + final VirtualMachineTO to = new VirtualMachineTO(id, name, VirtualMachine.Type.User, cpus, minSpeed, maxSpeed, minRam, maxRam, BootloaderType.HVM, os, false, limitCpuUse, + vncPassword); + to.setArch("x86_64"); + to.setVncAddr(vncAddr); + to.setUuid("b0f0a72d-7efb-3cad-a8ff-70ebf30b3af9"); + to.setVcpuMaxLimit(cpus + 1); + + return to; + } + + private void verifyGuestResourceDef(GuestResourceDef guestResourceDef, VirtualMachineTO to) { + String xml = "" + guestResourceDef.toString() + ""; + Document domainDoc = parse(xml); + + String minRam = String.valueOf(to.getMinRam() / 1024); + verifyMemory(to, domainDoc, minRam); + assertNodeExists(domainDoc, "/domain/vcpu"); + verifyMemballoonDevices(domainDoc); + verifyVcpu(to, domainDoc); + } + + private void verifyVm(VirtualMachineTO to, LibvirtVMDef vm) { + Document domainDoc = parse(vm.toString()); + verifyHeader(domainDoc, vm.getHvsType(), to.getName(), to.getUuid(), to.getOs()); + verifyFeatures(domainDoc); + verifyClock(domainDoc); + verifySerialDevices(domainDoc, "/domain/devices"); + verifyGraphicsDevices(to, domainDoc, "/domain/devices"); + verifyConsoleDevices(domainDoc, "/domain/devices"); + verifyTabletInputDevice(domainDoc, "/domain/devices"); + verifyChannelDevices(to, domainDoc, "/domain/devices"); + + String minRam = String.valueOf(to.getMinRam() / 1024); + verifyMemory(to, domainDoc, minRam); + assertNodeExists(domainDoc, "/domain/cpu"); + + verifyMemballoonDevices(domainDoc); + verifyVcpu(to, domainDoc); + verifyOsType(domainDoc); + verifyOsBoot(domainDoc); + verifyPoliticOn_(domainDoc); + verifyWatchDogDevices(domainDoc, "/domain/devices"); + } + + private void verifyMemballoonDevices(Document domainDoc) { + assertXpath(domainDoc, "/domain/devices/memballoon/@model", "virtio"); + } + + private void verifyVcpu(VirtualMachineTO to, Document domainDoc) { + assertXpath(domainDoc, "/domain/vcpu/text()", String.valueOf(to.getCpus())); + } + + private void verifyMemory(VirtualMachineTO to, Document domainDoc, String minRam) { + assertXpath(domainDoc, "/domain/memory/text()", String.valueOf(to.getMaxRam() / 1024)); + assertXpath(domainDoc, "/domain/currentMemory/text()", minRam); + } + + private void verifyWatchDogDevices(Document domainDoc, String prefix) { + assertXpath(domainDoc, prefix + "/watchdog/@model", "i6300esb"); + assertXpath(domainDoc, prefix + "/watchdog/@action", "none"); + } + + private void verifyChannelDevices(VirtualMachineTO to, Document domainDoc, String prefix) { + assertNodeExists(domainDoc, prefix + "/channel"); + assertXpath(domainDoc, prefix + "/channel/@type", ChannelDef.ChannelType.UNIX.toString()); /* The configure() method of LibvirtComputingResource has not been called, so the default path for the sockets @@ -392,28 +734,93 @@ public class LibvirtComputingResourceTest { Calling configure is also not possible since that looks for certain files on the system which are not present during testing */ - assertXpath(domainDoc, "/domain/devices/channel/source/@path", "/var/run/qemu/" + to.getName() + ".org.qemu.guest_agent.0"); - assertXpath(domainDoc, "/domain/devices/channel/target/@name", "org.qemu.guest_agent.0"); + assertXpath(domainDoc, prefix + "/channel/source/@path", "/var/run/qemu/" + to.getName() + ".org.qemu.guest_agent.0"); + assertXpath(domainDoc, prefix + "/channel/target/@name", "org.qemu.guest_agent.0"); + } - assertXpath(domainDoc, "/domain/memory/text()", String.valueOf( to.getMaxRam() / 1024 )); - assertXpath(domainDoc, "/domain/currentMemory/text()", String.valueOf( to.getMinRam() / 1024 )); + private void verifyTabletInputDevice(Document domainDoc, String prefix) { + assertXpath(domainDoc, prefix + "/input/@type", "tablet"); + assertXpath(domainDoc, prefix + "/input/@bus", "usb"); + } - assertXpath(domainDoc, "/domain/devices/memballoon/@model", "virtio"); - assertXpath(domainDoc, "/domain/vcpu/text()", String.valueOf(to.getCpus())); + private void verifyConsoleDevices(Document domainDoc, String prefix) { + assertXpath(domainDoc, prefix + "/console/@type", "pty"); + assertXpath(domainDoc, prefix + "/console/target/@port", "0"); + } - assertXpath(domainDoc, "/domain/os/type/@machine", "pc"); - assertXpath(domainDoc, "/domain/os/type/text()", "hvm"); + private void verifyScsi(VirtualMachineTO to, Document domainDoc, String prefix) { + assertXpath(domainDoc, prefix + "/controller/@type", "scsi"); + assertXpath(domainDoc, prefix + "/controller/@model", "virtio-scsi"); + assertXpath(domainDoc, prefix + "/controller/address/@type", "pci"); + assertXpath(domainDoc, prefix + "/controller/driver/@queues", String.valueOf(to.getCpus())); + } - assertNodeExists(domainDoc, "/domain/cpu"); + private void verifyClock(Document domainDoc) { + assertXpath(domainDoc, "/domain/clock/@offset", "utc"); + } + + private void verifyGraphicsDevices(VirtualMachineTO to, Document domainDoc, String prefix) { + assertXpath(domainDoc, prefix + "/graphics/@type", "vnc"); + assertXpath(domainDoc, prefix + "/graphics/@listen", to.getVncAddr()); + assertXpath(domainDoc, prefix + "/graphics/@autoport", "yes"); + assertXpath(domainDoc, prefix + "/graphics/@passwd", to.getVncPassword()); + } + + private void verifySerialDevices(Document domainDoc, String prefix) { + assertXpath(domainDoc, prefix + "/serial/@type", "pty"); + assertXpath(domainDoc, prefix + "/serial/target/@port", "0"); + } + + private void verifyOsBoot(Document domainDoc) { assertNodeExists(domainDoc, "/domain/os/boot[@dev='cdrom']"); assertNodeExists(domainDoc, "/domain/os/boot[@dev='hd']"); + } + private void verifyOsType(Document domainDoc) { + assertXpath(domainDoc, "/domain/os/type/@machine", "pc"); + assertXpath(domainDoc, "/domain/os/type/text()", "hvm"); + } + + private void verifyPoliticOn_(Document domainDoc) { assertXpath(domainDoc, "/domain/on_reboot/text()", "restart"); assertXpath(domainDoc, "/domain/on_poweroff/text()", "destroy"); assertXpath(domainDoc, "/domain/on_crash/text()", "destroy"); + } - assertXpath(domainDoc, "/domain/devices/watchdog/@model", "i6300esb"); - assertXpath(domainDoc, "/domain/devices/watchdog/@action", "none"); + private void verifyFeatures(Document domainDoc) { + assertNodeExists(domainDoc, "/domain/features/pae"); + assertNodeExists(domainDoc, "/domain/features/apic"); + assertNodeExists(domainDoc, "/domain/features/acpi"); + } + + private void verifyHeader(Document domainDoc, String hvsType, String name, String uuid, String os) { + assertXpath(domainDoc, "/domain/@type", hvsType); + assertXpath(domainDoc, "/domain/name/text()", name); + assertXpath(domainDoc, "/domain/uuid/text()", uuid); + assertXpath(domainDoc, "/domain/description/text()", os); + } + + private void verifyDevices(DevicesDef devicesDef, VirtualMachineTO to) { + Document domainDoc = parse(devicesDef.toString()); + + verifyWatchDogDevices(domainDoc, "/devices"); + verifyConsoleDevices(domainDoc, "/devices"); + verifySerialDevices(domainDoc, "/devices"); + verifyGraphicsDevices(to, domainDoc, "/devices"); + verifyChannelDevices(to, domainDoc, "/devices"); + verifyTabletInputDevice(domainDoc, "/devices"); + } + + private void verifySysInfo(GuestDef guestDef, String type, String uuid, String machine) { + // Need put because the string of guestdef generate two root element in XML, raising a error in parse. + String xml = "\n" + guestDef.toString() + ""; + + Document domainDoc = parse(xml); + assertXpath(domainDoc, "/guestdef/sysinfo/@type", type); + assertNodeExists(domainDoc, "/guestdef/sysinfo/system/entry[@name='manufacturer']"); + assertNodeExists(domainDoc, "/guestdef/sysinfo/system/entry[@name='product']"); + assertXpath(domainDoc, "/guestdef/sysinfo/system/entry[@name='uuid']/text()", uuid); + assertXpath(domainDoc, "/guestdef/os/type/@machine", machine); } static Document parse(final String input) {