diff --git a/api/src/main/java/com/cloud/vm/VmDetailConstants.java b/api/src/main/java/com/cloud/vm/VmDetailConstants.java index add2518321b4..c045274e1b89 100644 --- a/api/src/main/java/com/cloud/vm/VmDetailConstants.java +++ b/api/src/main/java/com/cloud/vm/VmDetailConstants.java @@ -40,6 +40,8 @@ public interface VmDetailConstants { String KVM_VNC_PORT = "kvm.vnc.port"; String KVM_VNC_ADDRESS = "kvm.vnc.address"; + String KVM_VNC_PASSWORD = "kvm.vnc.password"; + // KVM specific, custom virtual GPU hardware String VIDEO_HARDWARE = "video.hardware"; String VIDEO_RAM = "video.ram"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java index 70233317cc51..d52455fbc10e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java @@ -84,9 +84,16 @@ public class ImportUnmanagedInstanceCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, - description = "the hypervisor name of the instance") + description = "the name of the instance as it is known to the hypervisor") private String name; + @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, + type = CommandType.UUID, + entityType = ServiceOfferingResponse.class, + required = true, + description = "the ID of the service offering for the virtual machine") + private Long serviceOfferingId; + @Parameter(name = ApiConstants.DISPLAY_NAME, type = CommandType.STRING, description = "the display name of the instance") @@ -120,13 +127,6 @@ public class ImportUnmanagedInstanceCmd extends BaseAsyncCmd { description = "the ID of the template for the virtual machine") private Long templateId; - @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, - type = CommandType.UUID, - entityType = ServiceOfferingResponse.class, - required = true, - description = "the ID of the service offering for the virtual machine") - private Long serviceOfferingId; - @Parameter(name = ApiConstants.NIC_NETWORK_LIST, type = CommandType.MAP, description = "VM nic to network id mapping using keys nic and network") diff --git a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java index 95675f2bf349..18c8de69d1fb 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java +++ b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java @@ -48,6 +48,7 @@ public enum PowerState { private List disks; private List nics; + private String vncPassword; public String getName() { return name; @@ -137,6 +138,15 @@ public void setNics(List nics) { this.nics = nics; } + public String getVncPassword() { + return vncPassword; + } + + public void setVncPassword(String vncPassword) { + this.vncPassword = vncPassword; + } + + public static class Disk { private String diskId; @@ -160,6 +170,8 @@ public static class Disk { private String datastoreHost; + private int datastorePort; + private String datastorePath; private String datastoreType; @@ -267,6 +279,14 @@ public String getDatastoreType() { public void setDatastoreType(String datastoreType) { this.datastoreType = datastoreType; } + + public void setDatastorePort(int datastorePort) { + this.datastorePort = datastorePort; + } + + public int getDatastorePort() { + return datastorePort; + } } public static class Nic { diff --git a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java index 2876a0127be5..53aece949649 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java +++ b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java @@ -17,13 +17,20 @@ package org.apache.cloudstack.vm; +import com.cloud.hypervisor.Hypervisor; import com.cloud.utils.component.PluggableService; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; +import static com.cloud.hypervisor.Hypervisor.HypervisorType.KVM; +import static com.cloud.hypervisor.Hypervisor.HypervisorType.VMware; public interface UnmanagedVMsManager extends VmImportService, UnmanageVMService, PluggableService, Configurable { ConfigKey UnmanageVMPreserveNic = new ConfigKey<>("Advanced", Boolean.class, "unmanage.vm.preserve.nics", "false", "If set to true, do not remove VM nics (and its MAC addresses) when unmanaging a VM, leaving them allocated but not reserved. " + "If set to false, nics are removed and MAC addresses can be reassigned", true, ConfigKey.Scope.Zone); + + static boolean isSupported(Hypervisor.HypervisorType hypervisorType) { + return hypervisorType == VMware || hypervisorType == KVM; + } } diff --git a/api/src/main/java/org/apache/cloudstack/vm/VmImportService.java b/api/src/main/java/org/apache/cloudstack/vm/VmImportService.java index cce284745413..45162e19814b 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/VmImportService.java +++ b/api/src/main/java/org/apache/cloudstack/vm/VmImportService.java @@ -17,6 +17,9 @@ package org.apache.cloudstack.vm; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; import org.apache.cloudstack.api.command.admin.vm.ImportUnmanagedInstanceCmd; import org.apache.cloudstack.api.command.admin.vm.ListUnmanagedInstancesCmd; import org.apache.cloudstack.api.response.ListResponse; @@ -25,5 +28,5 @@ public interface VmImportService { ListResponse listUnmanagedInstances(ListUnmanagedInstancesCmd cmd); - UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd); + UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException; } 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 6bbafcacef2f..44771bd10d92 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 @@ -3696,7 +3696,32 @@ private String getIqn() { } } - protected List getAllVmNames(final Connect conn) { + /** + * Given a disk path on KVM host, attempts to find source host and path using mount command + * @param diskPath KVM host path for virtual disk + * @return Pair with IP of host and path + */ + public Pair getSourceHostPath(String diskPath) { + String sourceHostIp = null, sourcePath = null; + try { + String mountResult = Script.runSimpleBashScript("mount | grep \"" + diskPath + "\""); + s_logger.debug("Got mount result for " + diskPath + "\n\n" + mountResult); + if (StringUtils.isNotEmpty(mountResult)) { + String[] res = mountResult.strip().split(" "); + res = res[0].split(":"); + sourceHostIp = res[0].strip(); + sourcePath = res[1].strip(); + } + if (StringUtils.isNotEmpty(sourceHostIp) && StringUtils.isNotEmpty(sourcePath)) { + return new Pair<>(sourceHostIp, sourcePath); + } + } catch (Exception ex) { + s_logger.warn("Failed to list source host and IP for " + diskPath + ex.toString()); + } + return null; + } + + public List getAllVmNames(final Connect conn) { final ArrayList la = new ArrayList(); try { final String names[] = conn.listDefinedDomains(); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java index 9a27e5e4322a..c6c6752a8ec0 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java @@ -38,6 +38,8 @@ import org.xml.sax.SAXException; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.CpuModeDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.CpuTuneDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef.NicModel; @@ -57,8 +59,13 @@ public class LibvirtDomainXMLParser { private final List channels = new ArrayList(); private final List watchDogDefs = new ArrayList(); private Integer vncPort; + private String vncPasswd; private String desc; + private CpuTuneDef cpuTuneDef; + + private CpuModeDef cpuModeDef; + public boolean parseDomainXML(String domXML) { DocumentBuilder builder; try { @@ -302,6 +309,12 @@ public boolean parseDomainXML(String domXML) { vncPort = null; } } + + String passwd = graphic.getAttribute("passwd"); + if (passwd != null) { + vncPasswd = passwd; + } + } NodeList rngs = devices.getElementsByTagName("rng"); @@ -343,7 +356,8 @@ public boolean parseDomainXML(String domXML) { watchDogDefs.add(def); } - + cpuTuneDef = extractCpuTuneDef(rootElement); + cpuModeDef = extractCpuModeDef(rootElement); return true; } catch (ParserConfigurationException e) { s_logger.debug(e.toString()); @@ -400,6 +414,10 @@ public Integer getVncPort() { return vncPort; } + public String getVncPasswd() { + return vncPasswd; + } + public List getInterfaces() { return interfaces; } @@ -427,4 +445,69 @@ public List getWatchDogs() { public String getDescription() { return desc; } + + public CpuTuneDef getCpuTuneDef() { + return cpuTuneDef; + } + + public CpuModeDef getCpuModeDef() { + return cpuModeDef; + } + + private static CpuTuneDef extractCpuTuneDef(final Element rootElement) { + NodeList cpuTunesList = rootElement.getElementsByTagName("cputune"); + CpuTuneDef def = null; + if (cpuTunesList.getLength() > 0) { + def = new CpuTuneDef(); + final Element cpuTuneDefElement = (Element) cpuTunesList.item(0); + final String cpuShares = getTagValue("shares", cpuTuneDefElement); + if (StringUtils.isNotBlank(cpuShares)) { + def.setShares((Integer.parseInt(cpuShares))); + } + + final String quota = getTagValue("quota", cpuTuneDefElement); + if (StringUtils.isNotBlank(quota)) { + def.setQuota((Integer.parseInt(quota))); + } + + final String period = getTagValue("period", cpuTuneDefElement); + if (StringUtils.isNotBlank(period)) { + def.setPeriod((Integer.parseInt(period))); + } + } + return def; + } + + private static CpuModeDef extractCpuModeDef(final Element rootElement){ + NodeList cpuModeList = rootElement.getElementsByTagName("cpu"); + CpuModeDef def = null; + if (cpuModeList.getLength() > 0){ + def = new CpuModeDef(); + final Element cpuModeDefElement = (Element) cpuModeList.item(0); + final String cpuModel = getTagValue("model", cpuModeDefElement); + if (StringUtils.isNotBlank(cpuModel)){ + def.setModel(cpuModel); + } + NodeList cpuFeatures = cpuModeDefElement.getElementsByTagName("features"); + if (cpuFeatures.getLength() > 0) { + final ArrayList features = new ArrayList<>(cpuFeatures.getLength()); + for (int i = 0; i < cpuFeatures.getLength(); i++) { + final Element feature = (Element)cpuFeatures.item(i); + final String policy = feature.getAttribute("policy"); + String featureName = feature.getAttribute("name"); + if ("disable".equals(policy)) { + featureName = "-" + featureName; + } + features.add(featureName); + } + def.setFeatures(features); + } + final String sockets = getAttrValue("topology", "sockets", cpuModeDefElement); + final String cores = getAttrValue("topology", "cores", cpuModeDefElement); + if (StringUtils.isNotBlank(sockets) && StringUtils.isNotBlank(cores)) { + def.setTopology(Integer.parseInt(cores), Integer.parseInt(sockets)); + } + } + return def; + } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java index aac44fc14193..5ec2a5e631f6 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java @@ -609,10 +609,9 @@ public String toString() { } } - enum DiskType { + public enum DiskType { FILE("file"), BLOCK("block"), DIRECTROY("dir"), NETWORK("network"); String _diskType; - DiskType(String type) { _diskType = type; } @@ -1072,6 +1071,18 @@ public void setSerial(String serial) { public LibvirtDiskEncryptDetails getLibvirtDiskEncryptDetails() { return this.encryptDetails; } + public String getSourceHost() { + return _sourceHost; + } + + public int getSourceHostPort() { + return _sourcePort; + } + + public String getSourcePath() { + return _sourcePath; + } + @Override public String toString() { StringBuilder diskBuilder = new StringBuilder(); @@ -1394,6 +1405,7 @@ public void defEthernet(String targetName, String macAddr, NicModel model) { defEthernet(targetName, macAddr, model, null); } + public void setHostNetType(HostNicType hostNetType) { _hostNetType = hostNetType; } @@ -1737,6 +1749,10 @@ public String toString() { modeBuilder.append(""); return modeBuilder.toString(); } + + public int getCoresPerSocket() { + return _coresPerSocket; + } } public static class SerialDef { diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java new file mode 100644 index 000000000000..d59a5e9ff4dc --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java @@ -0,0 +1,223 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.GetUnmanagedInstancesAnswer; +import com.cloud.agent.api.GetUnmanagedInstancesCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.resource.LibvirtDomainXMLParser; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.utils.qemu.QemuImg; +import org.apache.cloudstack.utils.qemu.QemuImgException; +import org.apache.cloudstack.utils.qemu.QemuImgFile; +import org.apache.cloudstack.vm.UnmanagedInstanceTO; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.libvirt.Connect; +import org.libvirt.Domain; +import org.libvirt.LibvirtException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@ResourceWrapper(handles=GetUnmanagedInstancesCommand.class) +public final class LibvirtGetUnmanagedInstancesCommandWrapper extends CommandWrapper { + private static final Logger LOGGER = Logger.getLogger(LibvirtPrepareUnmanageVMInstanceCommandWrapper.class); + + @Override + public GetUnmanagedInstancesAnswer execute(GetUnmanagedInstancesCommand command, LibvirtComputingResource libvirtComputingResource) { + LOGGER.info("Fetching unmanaged instance on host"); + + HashMap unmanagedInstances = new HashMap<>(); + try { + final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper(); + final Connect conn = libvirtUtilitiesHelper.getConnection(); + final List domains = getDomains(command, libvirtComputingResource, conn); + + for (Domain domain : domains) { + UnmanagedInstanceTO instance = getUnmanagedInstance(libvirtComputingResource, domain, conn); + unmanagedInstances.put(instance.getName(), instance); + domain.free(); + } + } catch (Exception e) { + LOGGER.error("GetUnmanagedInstancesCommand failed due to " + e.getMessage()); + throw new CloudRuntimeException("GetUnmanagedInstancesCommand failed due to " + e.getMessage()); + } + + return new GetUnmanagedInstancesAnswer(command, "True", unmanagedInstances); + } + + private List getDomains(GetUnmanagedInstancesCommand command, + LibvirtComputingResource libvirtComputingResource, + Connect conn) throws LibvirtException, CloudRuntimeException { + final List domains = new ArrayList<>(); + final String vmNameCmd = command.getInstanceName(); + if (StringUtils.isNotBlank(vmNameCmd)) { + final Domain domain = libvirtComputingResource.getDomain(conn, vmNameCmd); + if (domain == null) { + LOGGER.error("GetUnmanagedInstancesCommand: vm not found " + vmNameCmd); + throw new CloudRuntimeException("GetUnmanagedInstancesCommand: vm not found " + vmNameCmd); + } + + checkIfVmExists(vmNameCmd,domain); + checkIfVmIsManaged(command,vmNameCmd,domain); + + domains.add(domain); + } else { + final List allVmNames = libvirtComputingResource.getAllVmNames(conn); + for (String name : allVmNames) { + if (!command.hasManagedInstance(name)) { + final Domain domain = libvirtComputingResource.getDomain(conn, name); + domains.add(domain); + } + } + } + return domains; + } + + private void checkIfVmExists(String vmNameCmd,final Domain domain) throws LibvirtException { + if (StringUtils.isNotEmpty(vmNameCmd) && + !vmNameCmd.equals(domain.getName())) { + LOGGER.error("GetUnmanagedInstancesCommand: exact vm name not found " + vmNameCmd); + throw new CloudRuntimeException("GetUnmanagedInstancesCommand: exact vm name not found " + vmNameCmd); + } + } + + private void checkIfVmIsManaged(GetUnmanagedInstancesCommand command,String vmNameCmd,final Domain domain) throws LibvirtException { + if (command.hasManagedInstance(domain.getName())) { + LOGGER.error("GetUnmanagedInstancesCommand: vm already managed " + vmNameCmd); + throw new CloudRuntimeException("GetUnmanagedInstancesCommand: vm already managed " + vmNameCmd); + } + } + private UnmanagedInstanceTO getUnmanagedInstance(LibvirtComputingResource libvirtComputingResource, Domain domain, Connect conn) { + try { + final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser(); + parser.parseDomainXML(domain.getXMLDesc(1)); + + final UnmanagedInstanceTO instance = new UnmanagedInstanceTO(); + instance.setName(domain.getName()); + + instance.setCpuCores((int) LibvirtComputingResource.countDomainRunningVcpus(domain)); + instance.setCpuSpeed(parser.getCpuTuneDef().getShares()/instance.getCpuCores()); + + if (parser.getCpuModeDef() != null) { + instance.setCpuCoresPerSocket(parser.getCpuModeDef().getCoresPerSocket()); + } + instance.setPowerState(getPowerState(libvirtComputingResource.getVmState(conn,domain.getName()))); + instance.setMemory((int) LibvirtComputingResource.getDomainMemory(domain) / 1024); + instance.setNics(getUnmanagedInstanceNics(parser.getInterfaces())); + instance.setDisks(getUnmanagedInstanceDisks(parser.getDisks(),libvirtComputingResource)); + instance.setVncPassword(parser.getVncPasswd() + "aaaaaaaaaaaaaa"); // Suffix back extra characters for DB compatibility + + return instance; + } catch (Exception e) { + LOGGER.info("Unable to retrieve unmanaged instance info. " + e.getMessage()); + throw new CloudRuntimeException("Unable to retrieve unmanaged instance info. " + e.getMessage()); + } + } + + private UnmanagedInstanceTO.PowerState getPowerState(VirtualMachine.PowerState vmPowerState) { + switch (vmPowerState) { + case PowerOn: + return UnmanagedInstanceTO.PowerState.PowerOn; + case PowerOff: + return UnmanagedInstanceTO.PowerState.PowerOff; + default: + return UnmanagedInstanceTO.PowerState.PowerUnknown; + + } + } + + private List getUnmanagedInstanceNics(List interfaces) { + final ArrayList nics = new ArrayList<>(interfaces.size()); + int counter = 0; + for (LibvirtVMDef.InterfaceDef interfaceDef : interfaces) { + final UnmanagedInstanceTO.Nic nic = new UnmanagedInstanceTO.Nic(); + nic.setNicId(String.valueOf(counter++)); + nic.setMacAddress(interfaceDef.getMacAddress()); + nic.setAdapterType(interfaceDef.getModel().toString()); + nic.setNetwork(interfaceDef.getDevName()); + nic.setPciSlot(interfaceDef.getSlot().toString()); + nic.setVlan(interfaceDef.getVlanTag()); + nics.add(nic); + } + return nics; + } + + private List getUnmanagedInstanceDisks(List disksInfo, LibvirtComputingResource libvirtComputingResource){ + final ArrayList disks = new ArrayList<>(disksInfo.size()); + int counter = 0; + for (LibvirtVMDef.DiskDef diskDef : disksInfo) { + if (diskDef.getDeviceType() != LibvirtVMDef.DiskDef.DeviceType.DISK) { + continue; + } + + final UnmanagedInstanceTO.Disk disk = new UnmanagedInstanceTO.Disk(); + Long size = null; + String imagePath = null; + try { + QemuImgFile file = new QemuImgFile(diskDef.getSourcePath()); + QemuImg qemu = new QemuImg(0); + Map info = qemu.info(file); + size = Long.parseLong(info.getOrDefault("virtual_size", "0")); + imagePath = info.getOrDefault("image", null); + } catch (QemuImgException | LibvirtException e) { + throw new RuntimeException(e); + } + + disk.setPosition(counter); + disk.setCapacity(size); + disk.setDiskId(String.valueOf(counter++)); + disk.setLabel(diskDef.getDiskLabel()); + disk.setController(diskDef.getBusType().toString()); + + + Pair sourceHostPath = getSourceHostPath(libvirtComputingResource, diskDef.getSourcePath()); + if (sourceHostPath != null) { + disk.setDatastoreHost(sourceHostPath.first()); + disk.setDatastorePath(sourceHostPath.second()); + } else { + disk.setDatastorePath(diskDef.getSourcePath()); + disk.setDatastoreHost(diskDef.getSourceHost()); + } + + disk.setDatastoreType(diskDef.getDiskType().toString()); + disk.setDatastorePort(diskDef.getSourceHostPort()); + disk.setImagePath(imagePath); + disk.setDatastoreName(imagePath.substring(imagePath.lastIndexOf("/"))); + disks.add(disk); + } + return disks; + } + + private Pair getSourceHostPath(LibvirtComputingResource libvirtComputingResource, String diskPath) { + int pathEnd = diskPath.lastIndexOf("/"); + if (pathEnd >= 0) { + diskPath = diskPath.substring(0, pathEnd); + return libvirtComputingResource.getSourceHostPath(diskPath); + } + return null; + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareUnmanageVMInstanceCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareUnmanageVMInstanceCommandWrapper.java new file mode 100644 index 000000000000..683730890380 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareUnmanageVMInstanceCommandWrapper.java @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.PrepareUnmanageVMInstanceAnswer; +import com.cloud.agent.api.PrepareUnmanageVMInstanceCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import org.apache.log4j.Logger; +import org.libvirt.Connect; +import org.libvirt.Domain; + +@ResourceWrapper(handles=PrepareUnmanageVMInstanceCommand.class) +public final class LibvirtPrepareUnmanageVMInstanceCommandWrapper extends CommandWrapper { + private static final Logger LOGGER = Logger.getLogger(LibvirtPrepareUnmanageVMInstanceCommandWrapper.class); + @Override + public PrepareUnmanageVMInstanceAnswer execute(PrepareUnmanageVMInstanceCommand command, LibvirtComputingResource libvirtComputingResource) { + final String vmName = command.getInstanceName(); + final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper(); + LOGGER.debug(String.format("Verify if KVM instance: [%s] is available before Unmanaging VM.", vmName)); + try { + final Connect conn = libvirtUtilitiesHelper.getConnectionByVmName(vmName); + final Domain domain = libvirtComputingResource.getDomain(conn, vmName); + if (domain == null) { + LOGGER.error("Prepare Unmanage VMInstanceCommand: vm not found " + vmName); + new PrepareUnmanageVMInstanceAnswer(command, false, String.format("Cannot find VM with name [%s] in KVM host.", vmName)); + } + } catch (Exception e){ + LOGGER.error("PrepareUnmanagedInstancesCommand failed due to " + e.getMessage()); + return new PrepareUnmanageVMInstanceAnswer(command, false, "Error: " + e.getMessage()); + } + + return new PrepareUnmanageVMInstanceAnswer(command, true, "OK"); + } +} diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index e4dc03a72fdd..5b4c07d25a94 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -127,6 +127,7 @@ import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; import org.apache.cloudstack.utils.security.ParserUtils; import org.apache.cloudstack.vm.schedule.VMScheduleManager; +import org.apache.cloudstack.vm.UnmanagedVMsManager; import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; @@ -233,7 +234,6 @@ import com.cloud.host.HostVO; import com.cloud.host.Status; import com.cloud.host.dao.HostDao; -import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao; import com.cloud.hypervisor.kvm.dpdk.DpdkHelper; @@ -4453,7 +4453,6 @@ private UserVmVO commitUserVm(final boolean isImport, final DataCenter zone, fin if (customParameters.containsKey(VmDetailConstants.ROOT_DISK_SIZE)) { // already verified for positive number rootDiskSize = Long.parseLong(customParameters.get(VmDetailConstants.ROOT_DISK_SIZE)); - VMTemplateVO templateVO = _templateDao.findById(template.getId()); if (templateVO == null) { throw new InvalidParameterValueException("Unable to look up template by id " + template.getId()); @@ -4480,6 +4479,8 @@ private UserVmVO commitUserVm(final boolean isImport, final DataCenter zone, fin } } + setVncPasswordForKvmIfAvailable(customParameters, vm); + vm.setUserVmType(vmType); _vmDao.persist(vm); for (String key : customParameters.keySet()) { @@ -4854,7 +4855,7 @@ public String validateUserData(String userData, HTTPMethod httpmethod) { } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "deploying Vm", async = true) + @ActionEvent(eventType = EventTypes.EVENT_VM_START, eventDescription = "starting Vm", async = true) public UserVm startVirtualMachine(DeployVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ConcurrentOperationException, ResourceAllocationException { long vmId = cmd.getEntityId(); if (!cmd.getStartVm()) { @@ -4865,7 +4866,6 @@ public UserVm startVirtualMachine(DeployVMCmd cmd) throws ResourceUnavailableExc Long hostId = cmd.getHostId(); Map additionalParams = new HashMap<>(); Map diskOfferingMap = cmd.getDataDiskTemplateToDiskOfferingMap(); - Map details = cmd.getDetails(); if (cmd instanceof DeployVMCmdByAdmin) { DeployVMCmdByAdmin adminCmd = (DeployVMCmdByAdmin)cmd; podId = adminCmd.getPodId(); @@ -8199,8 +8199,9 @@ public boolean unmanageUserVM(Long vmId) { return false; } - if (vm.getHypervisorType() != Hypervisor.HypervisorType.VMware) { - throw new UnsupportedServiceException("Unmanaging a VM is currently allowed for VMware VMs only"); + if (!UnmanagedVMsManager.isSupported(vm.getHypervisorType())) { + throw new UnsupportedServiceException("Unmanaging a VM is currently not supported on hypervisor " + + vm.getHypervisorType().toString()); } List volumes = _volsDao.findByInstance(vm.getId()); @@ -8379,4 +8380,11 @@ private void collectVmDiskAndNetworkStatistics(UserVm vm, State expectedState) { public Boolean getDestroyRootVolumeOnVmDestruction(Long domainId){ return DestroyRootVolumeOnVmDestruction.valueIn(domainId); } + + private void setVncPasswordForKvmIfAvailable(Map customParameters, UserVmVO vm){ + if (customParameters.containsKey(VmDetailConstants.KVM_VNC_PASSWORD) + && StringUtils.isNotEmpty(customParameters.get(VmDetailConstants.KVM_VNC_PASSWORD))) { + vm.setVncPassword(customParameters.get(VmDetailConstants.KVM_VNC_PASSWORD)); + } + } } diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 752ad5a9fba9..e1ade0b0d186 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -26,6 +26,7 @@ import javax.inject.Inject; +import com.cloud.vm.dao.UserVmDetailsDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ResponseGenerator; @@ -169,6 +170,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { @Inject private ResourceLimitService resourceLimitService; @Inject + private UserVmDetailsDao userVmDetailsDao; + @Inject private UserVmManager userVmManager; @Inject private ResponseGenerator responseGenerator; @@ -297,6 +300,7 @@ private List getAdditionalNameFilters(Cluster cluster) { if (cluster == null) { return additionalNameFilter; } + if (cluster.getHypervisorType() == Hypervisor.HypervisorType.VMware) { // VMWare considers some templates as VM and they are not filtered by VirtualMachineMO.isTemplate() List templates = templatePoolDao.listAll(); @@ -381,7 +385,7 @@ private boolean storagePoolSupportsDiskOffering(StoragePool pool, DiskOffering d return volumeApiService.doesTargetStorageSupportDiskOffering(pool, diskOffering.getTags()); } - private ServiceOfferingVO getUnmanagedInstanceServiceOffering(final UnmanagedInstanceTO instance, ServiceOfferingVO serviceOffering, final Account owner, final DataCenter zone, final Map details) + private ServiceOfferingVO getUnmanagedInstanceServiceOffering(final UnmanagedInstanceTO instance, ServiceOfferingVO serviceOffering, final Account owner, final DataCenter zone, final Map details, Hypervisor.HypervisorType hypervisorType) throws ServerApiException, PermissionDeniedException, ResourceAllocationException { if (instance == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Cannot find VM to import."); @@ -425,7 +429,7 @@ private ServiceOfferingVO getUnmanagedInstanceServiceOffering(final UnmanagedIns if (!memory.equals(serviceOffering.getRamSize()) && !instance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOff)) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Service offering (%s) %dMB memory does not match VM memory %dMB and VM is not in powered off state (Power state: %s)", serviceOffering.getUuid(), serviceOffering.getRamSize(), memory, instance.getPowerState())); } - if (cpuSpeed != null && cpuSpeed > 0 && !cpuSpeed.equals(serviceOffering.getSpeed()) && !instance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOff)) { + if (hypervisorType == Hypervisor.HypervisorType.VMware && cpuSpeed != null && cpuSpeed > 0 && !cpuSpeed.equals(serviceOffering.getSpeed()) && !instance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOff)) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Service offering (%s) %dMHz CPU speed does not match VM CPU speed %dMHz and VM is not in powered off state (Power state: %s)", serviceOffering.getUuid(), serviceOffering.getSpeed(), cpuSpeed, instance.getPowerState())); } } @@ -491,7 +495,10 @@ private StoragePool getStoragePool(final UnmanagedInstanceTO.Disk disk, final Da return storagePool; } - private Pair> getRootAndDataDisks(List disks, final Map dataDiskOfferingMap) { + private Pair> getRootAndDataDisks( + List disks, + final Map dataDiskOfferingMap, + final long rootDiskOfferingId) { UnmanagedInstanceTO.Disk rootDisk = null; List dataDisks = new ArrayList<>(); if (disks.size() == 1) { @@ -500,7 +507,7 @@ private Pair> getRootAn } Set callerDiskIds = dataDiskOfferingMap.keySet(); if (callerDiskIds.size() != disks.size() - 1) { - String msg = String.format("VM has total %d disks for which %d disk offering mappings provided. %d disks need a disk offering for import", disks.size(), callerDiskIds.size(), disks.size()-1); + String msg = String.format("VM has total %d disks for which %d disk offering mappings provided. %d disks need a disk offering for import", disks.size(), callerDiskIds.size(), disks.size() - 1); LOGGER.error(String.format("%s. %s parameter can be used to provide disk offerings for the disks", msg, ApiConstants.DATADISK_OFFERING_LIST)); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, msg); } @@ -512,11 +519,21 @@ private Pair> getRootAn rootDisk = disk; } else { dataDisks.add(disk); + DiskOffering diskOffering = diskOfferingDao.findById(dataDiskOfferingMap.getOrDefault(disk.getDiskId(), null)); + if ((disk.getCapacity() == null || disk.getCapacity() <= 0) && diskOffering != null) { + disk.setCapacity(diskOffering.getDiskSize()); + } } } - if (diskIdsWithoutOffering.size() > 1) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM has total %d disks, disk offering mapping not provided for %d disks. Disk IDs that may need a disk offering - %s", disks.size(), diskIdsWithoutOffering.size()-1, String.join(", ", diskIdsWithoutOffering))); + if (diskIdsWithoutOffering.size() > 1 || rootDisk == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM has total %d disks, disk offering mapping not provided for %d disks. Disk IDs that may need a disk offering - %s", disks.size(), diskIdsWithoutOffering.size() - 1, String.join(", ", diskIdsWithoutOffering))); } + + DiskOffering rootDiskOffering = diskOfferingDao.findById(rootDiskOfferingId); + if ((rootDisk.getCapacity() == null || rootDisk.getCapacity() <= 0) && rootDiskOffering != null) { + rootDisk.setCapacity(rootDiskOffering.getDiskSize()); + } + return new Pair<>(rootDisk, dataDisks); } @@ -565,7 +582,7 @@ private void checkUnmanagedDiskAndOfferingForImport(String intanceName, List getUnmanagedNicNetworkMap(String instanceName, List nics, final Map callerNicNetworkMap, final Map callerNicIpAddressMap, final DataCenter zone, final String hostName, final Account owner) throws ServerApiException { + private Map getUnmanagedNicNetworkMap(String instanceName, List nics, final Map callerNicNetworkMap, final Map callerNicIpAddressMap, final DataCenter zone, final String hostName, final Account owner, final Hypervisor.HypervisorType hypervisorType) throws ServerApiException { Map nicNetworkMap = new HashMap<>(); String nicAdapter = null; for (UnmanagedInstanceTO.Nic nic : nics) { @@ -646,7 +668,7 @@ private Map getUnmanagedNicNetworkMap(String instanceName, List getUnmanagedNicNetworkMap(String instanceName, List allDetails = new HashMap<>(details); @@ -935,13 +956,15 @@ private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedI if (CollectionUtils.isEmpty(unmanagedInstanceDisks)) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("No attached disks found for the unmanaged VM: %s", instanceName)); } - Pair> rootAndDataDisksPair = getRootAndDataDisks(unmanagedInstanceDisks, dataDiskOfferingMap); + Pair> rootAndDataDisksPair = getRootAndDataDisks(unmanagedInstanceDisks, dataDiskOfferingMap, validatedServiceOffering.getDiskOfferingId()); final UnmanagedInstanceTO.Disk rootDisk = rootAndDataDisksPair.first(); final List dataDisks = rootAndDataDisksPair.second(); if (rootDisk == null || StringUtils.isEmpty(rootDisk.getController())) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed. Unable to retrieve root disk details for VM: %s ", instanceName)); } allDetails.put(VmDetailConstants.ROOT_DISK_CONTROLLER, rootDisk.getController()); + allDetails.put(VmDetailConstants.ROOT_DISK_SIZE, String.valueOf(rootDisk.getCapacity() / Resource.ResourceType.bytesToGiB)); + try { checkUnmanagedDiskAndOfferingForImport(unmanagedInstance.getName(), rootDisk, null, validatedServiceOffering, owner, zone, cluster, migrateAllowed); if (CollectionUtils.isNotEmpty(dataDisks)) { // Data disk(s) present @@ -955,14 +978,20 @@ private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedI } // Check NICs and supplied networks Map nicIpAddressMap = getNicIpAddresses(unmanagedInstance.getNics(), callerNicIpAddressMap); - Map allNicNetworkMap = getUnmanagedNicNetworkMap(unmanagedInstance.getName(), unmanagedInstance.getNics(), nicNetworkMap, nicIpAddressMap, zone, hostName, owner); + Map allNicNetworkMap = getUnmanagedNicNetworkMap(unmanagedInstance.getName(), unmanagedInstance.getNics(), nicNetworkMap, nicIpAddressMap, zone, hostName, owner, host.getHypervisorType()); if (!CollectionUtils.isEmpty(unmanagedInstance.getNics())) { allDetails.put(VmDetailConstants.NIC_ADAPTER, unmanagedInstance.getNics().get(0).getAdapterType()); } + + if (StringUtils.isNotEmpty(unmanagedInstance.getVncPassword())) { + allDetails.put(VmDetailConstants.KVM_VNC_PASSWORD, unmanagedInstance.getVncPassword()); + } + VirtualMachine.PowerState powerState = VirtualMachine.PowerState.PowerOff; if (unmanagedInstance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOn)) { powerState = VirtualMachine.PowerState.PowerOn; } + try { userVm = userVmManager.importVM(zone, host, template, internalCSName, displayName, owner, null, caller, true, null, owner.getAccountId(), userId, @@ -973,6 +1002,7 @@ private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedI LOGGER.error(errorMsg, ice); throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, errorMsg); } + if (userVm == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import vm name: %s", instanceName)); } @@ -1014,7 +1044,7 @@ private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedI for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) { Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId())); Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId()); - importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex==0, forced); + importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex == 0, forced); nicIndex++; } } catch (Exception e) { @@ -1059,8 +1089,9 @@ private Cluster basicAccessChecks(Long clusterId) { if (cluster == null) { throw new InvalidParameterValueException(String.format("Cluster with ID [%d] cannot be found.", clusterId)); } - if (cluster.getHypervisorType() != Hypervisor.HypervisorType.VMware) { - throw new InvalidParameterValueException(String.format("VM import is currently not supported for hypervisor [%s].", cluster.getHypervisorType().toString())); + + if (!UnmanagedVMsManager.isSupported(cluster.getHypervisorType())) { + throw new InvalidParameterValueException(String.format("VM import is currently not supported for hypervisor: %s", cluster.getHypervisorType().toString())); } return cluster; } @@ -1100,7 +1131,6 @@ public ListResponse listUnmanagedInstances(ListUnmana public UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd) { Long clusterId = cmd.getClusterId(); Cluster cluster = basicAccessChecks(clusterId); - final DataCenter zone = dataCenterDao.findById(cluster.getDataCenterId()); final String instanceName = cmd.getName(); if (StringUtils.isEmpty(instanceName)) { @@ -1118,6 +1148,7 @@ public UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd) { VMTemplateVO template; final Long templateId = cmd.getTemplateId(); if (templateId == null) { + template = templateDao.findByName(VM_IMPORT_DEFAULT_TEMPLATE_NAME); if (template == null) { template = createDefaultDummyVmImportTemplate(); @@ -1194,12 +1225,18 @@ public UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd) { if (unmanagedInstance == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve details for unmanaged VM: %s", name)); } + + if (template.getName().equals(VM_IMPORT_DEFAULT_TEMPLATE_NAME) && cluster.getHypervisorType().equals(Hypervisor.HypervisorType.KVM)) { + throw new InvalidParameterValueException("Template is needed and unable to use default template for hypervisor " + host.getHypervisorType().toString()); + } + if (template.getName().equals(VM_IMPORT_DEFAULT_TEMPLATE_NAME)) { String osName = unmanagedInstance.getOperatingSystem(); GuestOS guestOS = null; if (StringUtils.isNotEmpty(osName)) { guestOS = guestOSDao.findOneByDisplayName(osName); } + GuestOSHypervisor guestOSHypervisor = null; if (guestOS != null) { guestOSHypervisor = guestOSHypervisorDao.findByOsIdAndHypervisor(guestOS.getId(), host.getHypervisorType().toString(), host.getHypervisorVersion()); @@ -1213,6 +1250,7 @@ public UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd) { } throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve guest OS details for unmanaged VM: %s with OS name: %s, OS ID: %s for hypervisor: %s version: %s. templateid parameter can be used to assign template for VM", name, osName, unmanagedInstance.getOperatingSystemId(), host.getHypervisorType().toString(), host.getHypervisorVersion())); } + template.setGuestOSId(guestOSHypervisor.getGuestOsId()); } userVm = importVirtualMachineInternal(unmanagedInstance, instanceName, zone, cluster, host, @@ -1312,8 +1350,9 @@ public boolean unmanageVMInstance(long vmId) { throw new InvalidParameterValueException("Could not find VM to unmanage, it is either removed or not existing VM"); } else if (vmVO.getState() != VirtualMachine.State.Running && vmVO.getState() != VirtualMachine.State.Stopped) { throw new InvalidParameterValueException("VM with id = " + vmVO.getUuid() + " must be running or stopped to be unmanaged"); - } else if (vmVO.getHypervisorType() != Hypervisor.HypervisorType.VMware) { - throw new UnsupportedServiceException("Unmanage VM is currently allowed for VMware VMs only"); + } else if (!UnmanagedVMsManager.isSupported(vmVO.getHypervisorType())) { + throw new UnsupportedServiceException("Unmanage VM is currently not allowed for hypervisor " + + vmVO.getHypervisorType().toString()); } else if (vmVO.getType() != VirtualMachine.Type.User) { throw new UnsupportedServiceException("Unmanage VM is currently allowed for guest VMs only"); } @@ -1354,6 +1393,6 @@ public String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] { UnmanageVMPreserveNic }; + return new ConfigKey[]{UnmanageVMPreserveNic}; } } diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index 05b73c9dbb1b..9c3d558ab715 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -28,9 +28,12 @@ import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; import com.cloud.event.UsageEventUtils; +import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.UnsupportedServiceException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.Status; @@ -96,16 +99,14 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnitRunner; - -import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; - import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) @@ -119,6 +120,10 @@ public class UnmanagedVMsManagerImplTest { @Mock private ClusterDao clusterDao; @Mock + private ClusterVO clusterVO; + @Mock + private UserVmVO userVm; + @Mock private ResourceManager resourceManager; @Mock private VMTemplatePoolDao templatePoolDao; @@ -216,8 +221,7 @@ public void setUp() throws Exception { instance.setNics(instanceNics); instance.setPowerState(UnmanagedInstanceTO.PowerState.PowerOn); - ClusterVO clusterVO = new ClusterVO(1L, 1L, "Cluster"); - clusterVO.setHypervisorType(Hypervisor.HypervisorType.VMware.toString()); + clusterVO = new ClusterVO(1L, 1L, "Cluster"); when(clusterDao.findById(Mockito.anyLong())).thenReturn(clusterVO); when(configurationDao.getValue(Mockito.anyString())).thenReturn(null); doNothing().when(resourceLimitService).checkResourceLimit(Mockito.any(Account.class), Mockito.any(Resource.ResourceType.class), Mockito.anyLong()); @@ -255,7 +259,7 @@ public void setUp() throws Exception { when(serviceOfferingDao.findById(Mockito.anyLong())).thenReturn(serviceOffering); DiskOfferingVO diskOfferingVO = Mockito.mock(DiskOfferingVO.class); when(diskOfferingDao.findById(Mockito.anyLong())).thenReturn(diskOfferingVO); - UserVmVO userVm = Mockito.mock(UserVmVO.class); + userVm = Mockito.mock(UserVmVO.class); when(userVm.getAccountId()).thenReturn(1L); when(userVm.getDataCenterId()).thenReturn(1L); when(userVm.getHostName()).thenReturn(instance.getName()); @@ -278,7 +282,6 @@ public void setUp() throws Exception { nullable(String.class), nullable(Hypervisor.HypervisorType.class), nullable(Map.class), nullable(VirtualMachine.PowerState.class))).thenReturn(userVm); NetworkVO networkVO = Mockito.mock(NetworkVO.class); when(networkVO.getGuestType()).thenReturn(Network.GuestType.L2); - when(networkVO.getBroadcastUri()).thenReturn(URI.create(String.format("vlan://%d", instanceNic.getVlan()))); when(networkVO.getDataCenterId()).thenReturn(1L); when(networkDao.findById(Mockito.anyLong())).thenReturn(networkVO); List networks = new ArrayList<>(); @@ -295,7 +298,6 @@ public void setUp() throws Exception { userVmResponse.setInstanceName(instance.getName()); userVmResponses.add(userVmResponse); when(responseGenerator.createUserVmResponse(Mockito.any(ResponseObject.ResponseView.class), Mockito.anyString(), Mockito.any(UserVm.class))).thenReturn(userVmResponses); - when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine); when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Running); } @@ -308,6 +310,14 @@ public void tearDown() throws Exception { @Test public void listUnmanagedInstancesTest() { + testListUnmanagedInstancesTest(Hypervisor.HypervisorType.VMware); + testListUnmanagedInstancesTest(Hypervisor.HypervisorType.KVM); + } + + private void testListUnmanagedInstancesTest(Hypervisor.HypervisorType hypervisorType) { + clusterVO.setHypervisorType(hypervisorType.toString()); + when(virtualMachine.getHypervisorType()).thenReturn(hypervisorType); + when(userVm.getHypervisorType()).thenReturn(hypervisorType); ListUnmanagedInstancesCmd cmd = Mockito.mock(ListUnmanagedInstancesCmd.class); unmanagedVMsManager.listUnmanagedInstances(cmd); } @@ -316,14 +326,20 @@ public void listUnmanagedInstancesTest() { public void listUnmanagedInstancesInvalidHypervisorTest() { ListUnmanagedInstancesCmd cmd = Mockito.mock(ListUnmanagedInstancesCmd.class); ClusterVO cluster = new ClusterVO(1, 1, "Cluster"); - cluster.setHypervisorType(Hypervisor.HypervisorType.KVM.toString()); + cluster.setHypervisorType(Hypervisor.HypervisorType.XenServer.toString()); when(clusterDao.findById(Mockito.anyLong())).thenReturn(cluster); unmanagedVMsManager.listUnmanagedInstances(cmd); } @Test(expected = PermissionDeniedException.class) public void listUnmanagedInstancesInvalidCallerTest() { + testListUnmanagedInstancesInvalidCallerTest(Hypervisor.HypervisorType.VMware); + testListUnmanagedInstancesInvalidCallerTest(Hypervisor.HypervisorType.KVM); + } + + private void testListUnmanagedInstancesInvalidCallerTest(Hypervisor.HypervisorType hypervisorType) { CallContext.unregister(); + clusterVO.setHypervisorType(hypervisorType.toString()); AccountVO account = new AccountVO("user", 1L, "", Account.Type.NORMAL, "uuid"); UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN); CallContext.register(user, account); @@ -332,7 +348,15 @@ public void listUnmanagedInstancesInvalidCallerTest() { } @Test - public void importUnmanagedInstanceTest() { + public void importUnmanagedInstanceTest() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + testImportUnmanagedInstanceTest(Hypervisor.HypervisorType.VMware); + testImportUnmanagedInstanceTest(Hypervisor.HypervisorType.KVM); + } + + private void testImportUnmanagedInstanceTest(Hypervisor.HypervisorType hypervisorType) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + clusterVO.setHypervisorType(hypervisorType.toString()); + when(virtualMachine.getHypervisorType()).thenReturn(hypervisorType); + when(userVm.getHypervisorType()).thenReturn(hypervisorType); ImportUnmanagedInstanceCmd importUnmanageInstanceCmd = Mockito.mock(ImportUnmanagedInstanceCmd.class); when(importUnmanageInstanceCmd.getName()).thenReturn("TestInstance"); when(importUnmanageInstanceCmd.getDomainId()).thenReturn(null); @@ -342,7 +366,13 @@ public void importUnmanagedInstanceTest() { } @Test(expected = InvalidParameterValueException.class) - public void importUnmanagedInstanceInvalidHostnameTest() { + public void importUnmanagedInstanceInvalidHostnameTest() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + testImportUnmanagedInstanceInvalidHostnameTest(Hypervisor.HypervisorType.VMware); + testImportUnmanagedInstanceInvalidHostnameTest(Hypervisor.HypervisorType.KVM); + } + + private void testImportUnmanagedInstanceInvalidHostnameTest(Hypervisor.HypervisorType hypervisorType) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + clusterVO.setHypervisorType(hypervisorType.toString()); ImportUnmanagedInstanceCmd importUnmanageInstanceCmd = Mockito.mock(ImportUnmanagedInstanceCmd.class); when(importUnmanageInstanceCmd.getName()).thenReturn("TestInstance"); when(importUnmanageInstanceCmd.getName()).thenReturn("some name"); @@ -350,7 +380,13 @@ public void importUnmanagedInstanceInvalidHostnameTest() { } @Test(expected = ServerApiException.class) - public void importUnmanagedInstanceMissingInstanceTest() { + public void importUnmanagedInstanceMissingInstanceTest() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + testImportUnmanagedInstanceMissingInstanceTest(Hypervisor.HypervisorType.VMware); + testImportUnmanagedInstanceMissingInstanceTest(Hypervisor.HypervisorType.KVM); + } + + private void testImportUnmanagedInstanceMissingInstanceTest(Hypervisor.HypervisorType hypervisorType) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + clusterVO.setHypervisorType(hypervisorType.toString()); ImportUnmanagedInstanceCmd importUnmanageInstanceCmd = Mockito.mock(ImportUnmanagedInstanceCmd.class); when(importUnmanageInstanceCmd.getName()).thenReturn("SomeInstance"); when(importUnmanageInstanceCmd.getDomainId()).thenReturn(null); @@ -359,34 +395,75 @@ public void importUnmanagedInstanceMissingInstanceTest() { @Test(expected = InvalidParameterValueException.class) public void unmanageVMInstanceMissingInstanceTest() { + testUnmanageVMInstanceMissingInstanceTest(Hypervisor.HypervisorType.VMware); + testUnmanageVMInstanceMissingInstanceTest(Hypervisor.HypervisorType.KVM); + + } + + private void testUnmanageVMInstanceMissingInstanceTest(Hypervisor.HypervisorType hypervisorType) { + clusterVO.setHypervisorType(hypervisorType.toString()); long notExistingId = 10L; unmanagedVMsManager.unmanageVMInstance(notExistingId); } @Test(expected = InvalidParameterValueException.class) public void unmanageVMInstanceDestroyedInstanceTest() { + testUnmanageVMInstanceDestroyedInstanceTest(Hypervisor.HypervisorType.VMware); + testUnmanageVMInstanceDestroyedInstanceTest(Hypervisor.HypervisorType.KVM); + } + + private void testUnmanageVMInstanceDestroyedInstanceTest(Hypervisor.HypervisorType hypervisorType){ + clusterVO.setHypervisorType(hypervisorType.toString()); when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Destroyed); unmanagedVMsManager.unmanageVMInstance(virtualMachineId); } @Test(expected = InvalidParameterValueException.class) public void unmanageVMInstanceExpungedInstanceTest() { + testUnmanageVMInstanceExpungedInstanceTest(Hypervisor.HypervisorType.VMware); + testUnmanageVMInstanceExpungedInstanceTest(Hypervisor.HypervisorType.KVM); + } + + private void testUnmanageVMInstanceExpungedInstanceTest(Hypervisor.HypervisorType hypervisorType){ + clusterVO.setHypervisorType(hypervisorType.toString()); when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Expunging); unmanagedVMsManager.unmanageVMInstance(virtualMachineId); } @Test(expected = UnsupportedServiceException.class) public void unmanageVMInstanceExistingVMSnapshotsTest() { + testUnmanageVMInstanceExistingVMSnapshotsTest(Hypervisor.HypervisorType.VMware); + testUnmanageVMInstanceExistingVMSnapshotsTest(Hypervisor.HypervisorType.KVM); + } + + private void testUnmanageVMInstanceExistingVMSnapshotsTest(Hypervisor.HypervisorType hypervisorType) { + clusterVO.setHypervisorType(hypervisorType.toString()); + when(virtualMachine.getHypervisorType()).thenReturn(hypervisorType); unmanagedVMsManager.unmanageVMInstance(virtualMachineId); } @Test(expected = UnsupportedServiceException.class) public void unmanageVMInstanceExistingVolumeSnapshotsTest() { + testUnmanageVMInstanceExistingVolumeSnapshotsTest(Hypervisor.HypervisorType.VMware); + testUnmanageVMInstanceExistingVolumeSnapshotsTest(Hypervisor.HypervisorType.KVM); + } + + private void testUnmanageVMInstanceExistingVolumeSnapshotsTest(Hypervisor.HypervisorType hypervisorType){ + clusterVO.setHypervisorType(hypervisorType.toString()); + when(virtualMachine.getHypervisorType()).thenReturn(hypervisorType); unmanagedVMsManager.unmanageVMInstance(virtualMachineId); } @Test(expected = UnsupportedServiceException.class) public void unmanageVMInstanceExistingISOAttachedTest() { + testUnmanageVMInstanceExistingISOAttachedTest(Hypervisor.HypervisorType.VMware); + testUnmanageVMInstanceExistingISOAttachedTest(Hypervisor.HypervisorType.KVM); + } + + private void testUnmanageVMInstanceExistingISOAttachedTest(Hypervisor.HypervisorType hypervisorType) { + clusterVO.setHypervisorType(hypervisorType.toString()); + when(virtualMachine.getHypervisorType()).thenReturn(hypervisorType); + UserVmVO userVmVO = mock(UserVmVO.class); unmanagedVMsManager.unmanageVMInstance(virtualMachineId); } } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 0cec8f94e2d7..2d58bfccedc8 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -658,7 +658,7 @@ "label.deployasis": "Read VM settings from OVA", "label.deploymentplanner": "Deployment planner", "label.desc.db.stats": "Database Statistics", -"label.desc.importexportinstancewizard": "Import and export instances to/from an existing VMware cluster.", +"label.desc.importexportinstancewizard": "Import and export instances to/from an existing VMware or KVM cluster.", "label.desc.usage.stats": "Usage Server Statistics", "label.description": "Description", "label.destaddressgroupuuid": "Destination Address Group", @@ -2558,7 +2558,7 @@ "message.desc.create.ssh.key.pair": "Please fill in the following data to create or register a ssh key pair.

(1) If public key is set, CloudStack will register the public key. You can use it through your private key.

(2) If public key is not set, CloudStack will create a new SSH key pair. In this case, please copy and save the private key. CloudStack will not keep it.
", "message.desc.created.ssh.key.pair": "Created a SSH key pair.", "message.desc.host": "Each cluster must contain at least one host (computer) for guest VMs to run on. We will add the first host now. For a host to function in CloudStack, you must install hypervisor software on the host, assign an IP address to the host, and ensure the host is connected to the CloudStack management server.

Give the host's DNS or IP address, the user name (usually root) and password, and any labels you use to categorize hosts.", -"message.desc.importexportinstancewizard": "This feature only applies Cloudstack VMware clusters. By choosing to manage an instance, CloudStack takes over the orchestration of that instance. The instance is left running and not physically moved. Unmanaging instances, removes CloudStack ability to manage them (but they are left running and not destroyed).", +"message.desc.importexportinstancewizard": "This feature only applies Cloudstack VMware and KVM clusters. By choosing to manage an instance, CloudStack takes over the orchestration of that instance. The instance is left running and not physically moved. Unmanaging instances, removes CloudStack ability to manage them (but they are left running and not destroyed).", "message.desc.primary.storage": "Each cluster must contain one or more primary storage servers. We will add the first one now. Primary storage contains the disk volumes for all the VMs running on hosts in the cluster. Use any standards-compliant protocol that is supported by the underlying hypervisor.", "message.desc.reset.ssh.key.pair": "Please specify a ssh key pair that you would like to add to this VM.", "message.desc.secondary.storage": "Each zone must have at least one NFS or secondary storage server. We will add the first one now. Secondary storage stores VM templates, ISO images, and VM disk volume snapshots. This server must be available to all hosts in the zone.

Provide the IP address and exported path.", diff --git a/ui/src/views/compute/wizard/MultiNetworkSelection.vue b/ui/src/views/compute/wizard/MultiNetworkSelection.vue index f2397d5c48c0..203cf092286f 100644 --- a/ui/src/views/compute/wizard/MultiNetworkSelection.vue +++ b/ui/src/views/compute/wizard/MultiNetworkSelection.vue @@ -49,7 +49,8 @@ v-for="network in validNetworks[record.id]" :key="network.id" :label="network.displaytext + (network.broadcasturi ? ' (' + network.broadcasturi + ')' : '')"> - {{ network.displaytext + (network.broadcasturi ? ' (' + network.broadcasturi + ')' : '') }} +
{{ network.displaytext + ' - ' + (network.id.slice(0,8)) }}
+
{{ network.displaytext + (network.broadcasturi ? ' (' + network.broadcasturi + ')' : '') }}
@@ -63,7 +64,7 @@ :checkBoxLabel="$t('label.auto.assign.random.ip')" :defaultCheckBoxValue="true" :reversed="true" - :visible="(indexNum > 0 && ipAddressesEnabled[record.id])" + :visible="(ipAddressesEnabled[record.id])" @handle-checkinputpair-change="setIpAddress" /> @@ -101,6 +102,10 @@ export default { filterMatchKey: { type: String, default: null + }, + hypervisor: { + type: String, + default: null } }, data () { @@ -129,7 +134,6 @@ export default { values: {}, ipAddressesEnabled: {}, ipAddresses: {}, - indexNum: 1, sendValuesTimer: null } }, @@ -198,9 +202,9 @@ export default { for (const item of this.items) { this.validNetworks[item.id] = this.networks if (this.filterUnimplementedNetworks) { - this.validNetworks[item.id] = this.validNetworks[item.id].filter(x => (x.state === 'Implemented' || (x.state === 'Setup' && ['Shared', 'L2'].includes(x.type)))) + this.validNetworks[item.id] = this.validNetworks[item.id].filter(x => (x.state === 'Implemented' || (x.state === 'Allocated' && this.hypervisor === 'KVM') || (x.state === 'Setup' && ['Shared', 'L2'].includes(x.type)))) } - if (this.filterMatchKey) { + if (this.filterMatchKey && this.hypervisor === 'VMWare') { this.validNetworks[item.id] = this.validNetworks[item.id].filter(x => x[this.filterMatchKey] === item[this.filterMatchKey]) } } @@ -209,7 +213,10 @@ export default { }, setIpAddressEnabled (nic, network) { this.ipAddressesEnabled[nic.id] = network && network.type !== 'L2' - this.indexNum = (this.indexNum % 2) + 1 + this.values[nic.id] = network ? network.id : '' + if (!this.ipAddresses[nic.id] && this.ipAddressesEnabled[nic.id]) { + this.ipAddresses[nic.id] = 'auto' + } }, setIpAddress (nicId, autoAssign, ipAddress) { this.ipAddresses[nicId] = autoAssign ? 'auto' : ipAddress diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index 7e89d48abb87..2692a2014acc 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -114,7 +114,7 @@ :value="templateType" @change="changeTemplateType"> - + {{ $t('label.template.temporary.import') }} @@ -224,6 +224,7 @@ :zoneId="cluster.zoneid" :selectionEnabled="false" :filterUnimplementedNetworks="true" + :hypervisor="this.cluster.hypervisortype" filterMatchKey="broadcasturi" @select-multi-network="updateMultiNetworkOffering" /> @@ -314,7 +315,7 @@ export default { selectedDomainId: null, templates: [], templateLoading: false, - templateType: 'auto', + templateType: this.defaultTemplateType(), totalComputeOfferings: 0, computeOfferings: [], computeOfferingLoading: false, @@ -448,7 +449,11 @@ export default { nic.broadcasturi = 'pvlan://' + nic.vlanid + '-i' + nic.isolatedpvlan } } - nic.meta = this.getMeta(nic, { macaddress: 'mac', vlanid: 'vlan', networkname: 'network' }) + if (this.cluster.hypervisortype === 'VMWare') { + nic.meta = this.getMeta(nic, { macaddress: 'mac', vlanid: 'vlan', networkname: 'network' }) + } else { + nic.meta = this.getMeta(nic, { macaddress: 'mac', networkname: 'network' }) + } nics.push(nic) } } @@ -621,6 +626,12 @@ export default { updateMultiNetworkOffering (data) { this.nicsNetworksMapping = data }, + defaultTemplateType () { + if (this.cluster.hypervisortype === 'VMWare') { + return 'auto' + } + return 'custom' + }, changeTemplateType (e) { this.templateType = e.target.value if (this.templateType === 'auto') { @@ -767,17 +778,7 @@ export default { const name = this.resource.name api('importUnmanagedInstance', params).then(json => { const jobId = json.importunmanagedinstanceresponse.jobid - this.$pollJob({ - jobId, - title: this.$t('label.import.instance'), - description: name, - loadingMessage: `${this.$t('label.import.instance')} ${name} ${this.$t('label.in.progress')}`, - catchMessage: this.$t('error.fetching.async.job.result'), - successMessage: this.$t('message.success.import.instance') + ' ' + name, - successMethod: result => { - this.$emit('refresh-data') - } - }) + this.$emit('track-import-jobid', [jobId, name]) this.closeAction() }).catch(error => { this.$notifyError(error) @@ -797,7 +798,7 @@ export default { for (var field of fields) { this.updateFieldValue(field, undefined) } - this.templateType = 'auto' + this.templateType = this.defaultTemplateType() this.updateComputeOffering(undefined) this.switches = {} }, diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue index 460ff91b38b5..a3ed1534afe0 100644 --- a/ui/src/views/tools/ManageInstances.vue +++ b/ui/src/views/tools/ManageInstances.vue @@ -232,6 +232,7 @@
@@ -265,6 +266,7 @@ @refresh-data="fetchInstances" @close-action="closeImportUnmanagedInstanceForm" @loading-changed="updateManageInstanceActionLoading" + @track-import-jobid="trackImportJobId" />
@@ -628,7 +630,7 @@ export default { this.fetchInstances() }, fetchInstances () { - if (this.selectedCluster.hypervisortype === 'VMware') { + if (this.selectedCluster.hypervisortype === 'VMware' || this.selectedCluster.hypervisortype === 'KVM') { this.fetchUnmanagedInstances() this.fetchManagedInstances() } @@ -719,6 +721,9 @@ export default { }, updateManageInstanceActionLoading (value) { this.importUnmanagedInstanceLoading = value + if (!value) { + this.fetchInstances() + } }, onManageInstanceAction () { this.selectedUnmanagedInstance = {} @@ -755,6 +760,21 @@ export default { } }) }, + trackImportJobId (details) { + const jobId = details[0] + const name = details[1] + this.$pollJob({ + jobId, + title: this.$t('label.import.instance'), + description: this.$t('label.import.instance'), + loadingMessage: `${this.$t('label.import.instance')} ${name} ${this.$t('label.in.progress')}`, + catchMessage: this.$t('error.fetching.async.job.result'), + successMessage: this.$t('message.success.import.instance') + ' ' + name, + successMethod: (result) => { + this.fetchInstances() + } + }) + }, unmanageInstances () { for (var index of this.managedInstancesSelectedRowKeys) { const vm = this.managedInstances[index]