diff --git a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineMetadataTO.java b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineMetadataTO.java new file mode 100644 index 000000000000..5b22afdedd53 --- /dev/null +++ b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineMetadataTO.java @@ -0,0 +1,182 @@ +// 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.agent.api.to; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class VirtualMachineMetadataTO { + // VM details + private final String name; + private final String internalName; + private final String displayName; + private final String instanceUuid; + private final Integer cpuCores; + private final Integer memory; + private final Long created; + private final Long started; + + // Owner details + private final String ownerDomainUuid; + private final String ownerDomainName; + private final String ownerAccountUuid; + private final String ownerAccountName; + private final String ownerProjectUuid; + private final String ownerProjectName; + + // Host and service offering + private final String serviceOfferingName; + private final List serviceOfferingHostTags; + + // zone, pod, and cluster details + private final String zoneName; + private final String zoneUuid; + private final String podName; + private final String podUuid; + private final String clusterName; + private final String clusterUuid; + + // resource tags + private final Map resourceTags; + + public VirtualMachineMetadataTO( + String name, String internalName, String displayName, String instanceUuid, Integer cpuCores, Integer memory, Long created, Long started, + String ownerDomainUuid, String ownerDomainName, String ownerAccountUuid, String ownerAccountName, String ownerProjectUuid, String ownerProjectName, + String serviceOfferingName, List serviceOfferingHostTags, + String zoneName, String zoneUuid, String podName, String podUuid, String clusterName, String clusterUuid, Map resourceTags) { + /* + * Something failed in the metadata shall not be a fatal error, the VM can still be started + * Thus, the unknown fields just get an explicit "unknown" value so it can be fixed in case + * there are bugs on some execution paths. + * */ + + this.name = (name != null) ? name : "unknown"; + this.internalName = (internalName != null) ? internalName : "unknown"; + this.displayName = (displayName != null) ? displayName : "unknown"; + this.instanceUuid = (instanceUuid != null) ? instanceUuid : "unknown"; + this.cpuCores = (cpuCores != null) ? cpuCores : -1; + this.memory = (memory != null) ? memory : -1; + this.created = (created != null) ? created : 0; + this.started = (started != null) ? started : 0; + this.ownerDomainUuid = (ownerDomainUuid != null) ? ownerDomainUuid : "unknown"; + this.ownerDomainName = (ownerDomainName != null) ? ownerDomainName : "unknown"; + this.ownerAccountUuid = (ownerAccountUuid != null) ? ownerAccountUuid : "unknown"; + this.ownerAccountName = (ownerAccountName != null) ? ownerAccountName : "unknown"; + this.ownerProjectUuid = (ownerProjectUuid != null) ? ownerProjectUuid : "unknown"; + this.ownerProjectName = (ownerProjectName != null) ? ownerProjectName : "unknown"; + this.serviceOfferingName = (serviceOfferingName != null) ? serviceOfferingName : "unknown"; + this.serviceOfferingHostTags = (serviceOfferingHostTags != null) ? serviceOfferingHostTags : new ArrayList<>(); + this.zoneName = (zoneName != null) ? zoneName : "unknown"; + this.zoneUuid = (zoneUuid != null) ? zoneUuid : "unknown"; + this.podName = (podName != null) ? podName : "unknown"; + this.podUuid = (podUuid != null) ? podUuid : "unknown"; + this.clusterName = (clusterName != null) ? clusterName : "unknown"; + this.clusterUuid = (clusterUuid != null) ? clusterUuid : "unknown"; + + this.resourceTags = (resourceTags != null) ? resourceTags : new HashMap<>(); + } + + public String getName() { + return name; + } + + public String getInternalName() { + return internalName; + } + + public String getDisplayName() { + return displayName; + } + + public String getInstanceUuid() { + return instanceUuid; + } + + public Integer getCpuCores() { + return cpuCores; + } + + public Integer getMemory() { + return memory; + } + + public Long getCreated() { return created; } + + public Long getStarted() { + return started; + } + + public String getOwnerDomainUuid() { + return ownerDomainUuid; + } + + public String getOwnerDomainName() { + return ownerDomainName; + } + + public String getOwnerAccountUuid() { + return ownerAccountUuid; + } + + public String getOwnerAccountName() { + return ownerAccountName; + } + + public String getOwnerProjectUuid() { + return ownerProjectUuid; + } + + public String getOwnerProjectName() { + return ownerProjectName; + } + + public String getserviceOfferingName() { + return serviceOfferingName; + } + + public List getserviceOfferingHostTags() { + return serviceOfferingHostTags; + } + + public String getZoneName() { + return zoneName; + } + + public String getZoneUuid() { + return zoneUuid; + } + + public String getPodName() { + return podName; + } + + public String getPodUuid() { + return podUuid; + } + + public String getClusterName() { + return clusterName; + } + + public String getClusterUuid() { + return clusterUuid; + } + + public Map getResourceTags() { return resourceTags; } +} 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 6f24b1cd6ca8..59f14723cdf7 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 @@ -86,6 +86,7 @@ public class VirtualMachineTO { DeployAsIsInfoTO deployAsIsInfo; String metadataManufacturer; String metadataProductName; + VirtualMachineMetadataTO metadata; public VirtualMachineTO(long id, String instanceName, VirtualMachine.Type type, int cpus, Integer speed, long minRam, long maxRam, BootloaderType bootloader, String os, boolean enableHA, boolean limitCpuUse, String vncPassword) { @@ -447,6 +448,14 @@ public void setMetadataProductName(String metadataProductName) { this.metadataProductName = metadataProductName; } + public VirtualMachineMetadataTO getMetadata() { + return metadata; + } + + public void setMetadata(VirtualMachineMetadataTO metadata) { + this.metadata = metadata; + } + @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 13518de5cb3c..eafa8e13558c 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 @@ -51,6 +51,7 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; +import com.cloud.agent.api.to.VirtualMachineMetadataTO; import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand; @@ -171,6 +172,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogAction; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogModel; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.MetadataDef; import com.cloud.hypervisor.kvm.resource.rolling.maintenance.RollingMaintenanceAgentExecutor; import com.cloud.hypervisor.kvm.resource.rolling.maintenance.RollingMaintenanceExecutor; import com.cloud.hypervisor.kvm.resource.rolling.maintenance.RollingMaintenanceServiceExecutor; @@ -2628,9 +2630,19 @@ private void configureVM(VirtualMachineTO vmTO, LibvirtVMDef vm, Map components = new HashMap(); private static final int NUMBER_OF_IOTHREADS = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.IOTHREADS); + public static class MetadataDef { + private VirtualMachineMetadataTO metaTO; + + public MetadataDef(VirtualMachineMetadataTO data) { + metaTO = data; + } + + public VirtualMachineMetadataTO getMetadata() { + return metaTO; + } + + @Override + public String toString() { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + docFactory.setNamespaceAware(true); + Document doc = null; + try { + doc = docFactory.newDocumentBuilder().newDocument(); + } catch (ParserConfigurationException e) { + LOGGER.warn("Could not create a new DOM XML document. The metadata will not be included in the libvirt domain XML: {}", e.getMessage()); + return ""; + } + Element metadata = doc.createElement("metadata"); // + Element instance = doc.createElementNS("http://cloudstack.apache.org/instance", "cloudstack:instance"); // + + Element zone = doc.createElement("cloudstack:zone"); + zone.setAttribute("uuid", metaTO.getZoneUuid()); + zone.setTextContent(metaTO.getZoneName()); + instance.appendChild(zone); + + Element pod = doc.createElement("cloudstack:pod"); + pod.setAttribute("uuid", metaTO.getPodUuid()); + pod.setTextContent(metaTO.getPodName()); + instance.appendChild(pod); + + Element cluster = doc.createElement("cloudstack:cluster"); + cluster.setAttribute("uuid", metaTO.getClusterUuid()); + cluster.setTextContent(metaTO.getClusterName()); + instance.appendChild(cluster); + + Element instanceName = doc.createElement("cloudstack:name"); + instanceName.setTextContent(metaTO.getName()); + instance.appendChild(instanceName); + + Element instanceInternalName = doc.createElement("cloudstack:internal_name"); + instanceInternalName.setTextContent(metaTO.getInternalName()); + instance.appendChild(instanceInternalName); + + Element instanceDisplayName = doc.createElement("cloudstack:display_name"); + instanceDisplayName.setTextContent(metaTO.getDisplayName()); + instance.appendChild(instanceDisplayName); + + Element instanceUuid = doc.createElement("cloudstack:uuid"); + instanceUuid.setTextContent(metaTO.getInstanceUuid()); + instance.appendChild(instanceUuid); + + Element serviceOffering = doc.createElement("cloudstack:service_offering"); // + + Element computeOfferingName = doc.createElement("cloudstack:name"); + computeOfferingName.setTextContent(metaTO.getserviceOfferingName()); + serviceOffering.appendChild(computeOfferingName); + + Element cpu = doc.createElement("cloudstack:cpu"); + cpu.setTextContent(metaTO.getCpuCores().toString()); + serviceOffering.appendChild(cpu); + + Element memory = doc.createElement("cloudstack:memory"); + memory.setTextContent(metaTO.getMemory().toString()); + serviceOffering.appendChild(memory); + + Element hostTags = doc.createElement("cloudstack:host_tags"); + List tags = metaTO.getserviceOfferingHostTags(); + if (tags != null) { + for (String i : metaTO.getserviceOfferingHostTags()) { + Element tag = doc.createElement("cloudstack:tag"); + tag.setTextContent(i); + hostTags.appendChild(tag); + } + } + serviceOffering.appendChild(hostTags); + + instance.appendChild(serviceOffering); // + + Element creationTime = doc.createElement("cloudstack:created_at"); + creationTime.setTextContent( + LocalDateTime.ofInstant(Instant.ofEpochSecond(metaTO.getCreated()), ZoneOffset.UTC).format(ISO_LOCAL_DATE_TIME) + ); + instance.appendChild(creationTime); + + Element startedTime = doc.createElement("cloudstack:started_at"); + startedTime.setTextContent( + LocalDateTime.ofInstant(Instant.ofEpochSecond(metaTO.getStarted()), ZoneOffset.UTC).format(ISO_LOCAL_DATE_TIME) + ); + instance.appendChild(startedTime); + + Element owner = doc.createElement("cloudstack:owner"); // + + Element domain = doc.createElement("cloudstack:domain"); + domain.setAttribute("uuid", metaTO.getOwnerDomainUuid()); + domain.setTextContent(metaTO.getOwnerDomainName()); + owner.appendChild(domain); + + Element account = doc.createElement("cloudstack:account"); + account.setAttribute("uuid", metaTO.getOwnerAccountUuid()); + account.setTextContent(metaTO.getOwnerAccountName()); + owner.appendChild(account); + + Element project = doc.createElement("cloudstack:project"); + project.setAttribute("uuid", metaTO.getOwnerProjectUuid()); + project.setTextContent(metaTO.getOwnerProjectName()); + owner.appendChild(project); + + instance.appendChild(owner); // + + Element resourceTags = doc.createElement("cloudstack:resource_tags"); // + for (Map.Entry entry : metaTO.getResourceTags().entrySet()) { + Element tag = doc.createElement("cloudstack:resource_tag"); // + tag.setAttribute("key", entry.getKey()); + tag.setTextContent(entry.getValue()); + resourceTags.appendChild(tag); // + } + instance.appendChild(resourceTags); // + + metadata.appendChild(instance); // + doc.appendChild(metadata); // + + Transformer transformer = null; + try { + transformer = TransformerFactory.newInstance().newTransformer(); + } catch (TransformerConfigurationException e) { + LOGGER.warn("Could not create an XML transformer. The metadata will not be included in the libvirt domain XML: {}", e.getMessage()); + return ""; + } + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + + DOMSource domSource = new DOMSource(doc); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + try { + transformer.transform(domSource, result); + } catch (TransformerException e) { + LOGGER.warn("Could not generate metadata XML. The metadata will not be included in the libvirt domain XML: {}", e.getMessage()); + return ""; + } + + return writer.toString(); + } + } + public static class GuestDef { enum GuestType { KVM, XEN, EXE, LXC @@ -2165,34 +2336,6 @@ public String toString() { } } - public class MetadataDef { - Map customNodes = new HashMap<>(); - - public T getMetadataNode(Class fieldClass) { - T field = (T) customNodes.get(fieldClass.getName()); - if (field == null) { - try { - field = fieldClass.newInstance(); - customNodes.put(field.getClass().getName(), field); - } catch (InstantiationException | IllegalAccessException e) { - LOGGER.debug("No default constructor available in class " + fieldClass.getName() + ", ignoring exception", e); - } - } - return field; - } - - @Override - public String toString() { - StringBuilder fsBuilder = new StringBuilder(); - fsBuilder.append("\n"); - for (Object field : customNodes.values()) { - fsBuilder.append(field.toString()); - } - fsBuilder.append("\n"); - return fsBuilder.toString(); - } - } - public static class RngDef { enum RngModel { VIRTIO("virtio"); @@ -2495,15 +2638,6 @@ public DevicesDef getDevices() { return null; } - public MetadataDef getMetaData() { - MetadataDef o = (MetadataDef) components.get(MetadataDef.class.toString()); - if (o == null) { - o = new MetadataDef(); - addComp(o); - } - return o; - } - @Override public String toString() { StringBuilder vmBuilder = new StringBuilder(); @@ -2515,6 +2649,7 @@ public String toString() { if (_desc != null) { vmBuilder.append("" + _desc + "\n"); } + for (Object o : components.values()) { vmBuilder.append(o.toString()); } diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java index c510502f5f9c..d2d775972eae 100644 --- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java +++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java @@ -16,6 +16,9 @@ // under the License. package com.cloud.hypervisor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -23,15 +26,28 @@ import javax.inject.Inject; +import com.cloud.agent.api.to.VirtualMachineMetadataTO; +import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.HostPodVO; +import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.HostPodDao; import com.cloud.domain.Domain; +import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; import com.cloud.network.vpc.VpcVO; import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.projects.ProjectVO; +import com.cloud.projects.dao.ProjectDao; +import com.cloud.server.ResourceTag; +import com.cloud.tags.dao.ResourceTagDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.dao.UserVmDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; @@ -93,7 +109,7 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis @Inject protected AccountManager accountManager; @Inject - private DomainDao domainDao; + protected DomainDao domainDao; @Inject private DataCenterDao dcDao; @Inject @@ -119,7 +135,19 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis @Inject private UserVmManager userVmManager; @Inject + protected UserVmDao userVmDao; + @Inject + protected ProjectDao projectDao; + @Inject + protected ClusterDao clusterDao; + @Inject + protected DataCenterDao dataCenterDao; + @Inject + protected HostPodDao hostPodDao; + @Inject private ConfigurationManager configurationManager; + @Inject + ResourceTagDao tagsDao; public static ConfigKey VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor = new ConfigKey("Advanced", Boolean.class, "vm.min.memory.equals.memory.divided.by.mem.overprovisioning.factor", "true", "If we set this to 'true', a minimum memory (memory/ mem.overprovisioning.factor) will be set to the VM, independent of using a scalable service offering or not.", true, ConfigKey.Scope.Cluster); @@ -443,4 +471,144 @@ public boolean removeVMTemplateOutOfBand(DataStoreTO templateLocation, String te logger.error("Unsupported operation: cannot remove template file"); return false; } + + /** + * Generates VirtualMachineMetadataTO object from VirtualMachineProfile + * It is a helper function to be used in the inherited classes to avoid repetition + * while generating metadata for multiple Guru implementations + * + * @param vmProfile virtual machine profile object + * @return A VirtualMachineMetadataTO ready to be appended to VirtualMachineTO object + * @see KVMGuru + */ + protected VirtualMachineMetadataTO makeVirtualMachineMetadata(VirtualMachineProfile vmProfile) { + String vmName = "unknown", + instanceName = "unknown", + displayName = "unknown", + instanceUuid = "unknown", + clusterName = "unknown", + clusterUuid = "unknown", + zoneUuid = "unknown", + zoneName = "unknown", + podUuid = "unknown", + podName = "unknown", + domainUuid = "unknown", + domainName = "unknown", + accountUuid = "unknown", + accountName = "unknown", + projectName = "", // the project can be empty + projectUuid = "", // the project can be empty + serviceOfferingName = "unknown"; + long created = 0L; + Integer cpuCores = -1, memory = -1; + List serviceOfferingTags = new ArrayList<>(); + HashMap resourceTags = new HashMap<>(); + + UserVmVO vmVO = userVmDao.findById(vmProfile.getVirtualMachine().getId()); + if (vmVO != null) { + instanceUuid = vmVO.getUuid(); + vmName = vmVO.getHostName(); // this returns the VM name field + instanceName = vmVO.getInstanceName(); + displayName = vmVO.getDisplayName(); + created = vmVO.getCreated().getTime() / 1000L; + + HostVO host = hostDao.findById(vmVO.getHostId()); + if (host != null) { + // Find zone and cluster + Long clusterId = host.getClusterId(); + ClusterVO cluster = clusterDao.findById(clusterId); + + if (cluster != null) { + clusterName = cluster.getName(); + clusterUuid = cluster.getUuid(); + + DataCenterVO zone = dataCenterDao.findById(cluster.getDataCenterId()); + if (zone != null) { + zoneUuid = zone.getUuid(); + zoneName = zone.getName(); + } + + HostPodVO pod = hostPodDao.findById(cluster.getPodId()); + if (pod != null) { + podUuid = pod.getUuid(); + podName = pod.getName(); + } + } + } else { + logger.warn("Could not find the Host object for the virtual machine (null value returned). Libvirt metadata for cluster, pod, zone will not be populated."); + } + + DomainVO domain = domainDao.findById(vmVO.getDomainId()); + if (domain != null) { + domainUuid = domain.getUuid(); + domainName = domain.getName(); + } else { + logger.warn("Could not find the Domain object for the virtual machine (null value returned). Libvirt metadata for domain will not be populated."); + } + + Account account = accountManager.getAccount(vmVO.getAccountId()); + if (account != null) { + accountUuid = account.getUuid(); + accountName = account.getName(); + + ProjectVO project = projectDao.findByProjectAccountId(account.getId()); + if (project != null) { + projectName = project.getName(); + projectUuid = project.getUuid(); + } + } else { + logger.warn("Could not find the Account object for the virtual machine (null value returned). Libvirt metadata for account and project will not be populated."); + } + + List resourceTagsList = tagsDao.listBy(vmVO.getId(), ResourceTag.ResourceObjectType.UserVm); + if (resourceTagsList != null) { + for (ResourceTag tag : resourceTagsList) { + resourceTags.put(tag.getKey(), tag.getValue()); + } + } + } else { + logger.warn("Could not find the VirtualMachine object by its profile (null value returned). Libvirt metadata will not be populated."); + } + + ServiceOffering serviceOffering = vmProfile.getServiceOffering(); + if (serviceOffering != null) { + serviceOfferingName = serviceOffering.getName(); + cpuCores = serviceOffering.getCpu(); + memory = serviceOffering.getRamSize(); + + String hostTagsCommaSeparated = serviceOffering.getHostTag(); + if (hostTagsCommaSeparated != null) { // when service offering has no host tags, this value is null + serviceOfferingTags = Arrays.asList(hostTagsCommaSeparated.split(",")); + } + } else { + logger.warn("Could not find the ServiceOffering object by its profile (null value returned). Libvirt metadata for service offering will not be populated."); + } + + + return new VirtualMachineMetadataTO( + vmName, // name + instanceName, // internalName + displayName, // displayName + instanceUuid , // instanceUUID + cpuCores, // cpuCores + memory, // memory + created, // created, unix epoch in seconds + System.currentTimeMillis() / 1000L, // started, unix epoch in seconds + domainUuid, // ownerDomainUUID + domainName, // ownerDomainName + accountUuid, // ownerAccountUUID + accountName, // ownerAccountName + projectUuid, + projectName, + serviceOfferingName, + serviceOfferingTags, // serviceOfferingTags + zoneName, + zoneUuid, + podName, + podUuid, + clusterName, + clusterUuid, + resourceTags + ); + } } diff --git a/server/src/main/java/com/cloud/hypervisor/KVMGuru.java b/server/src/main/java/com/cloud/hypervisor/KVMGuru.java index 9edaa5e6d646..6407896c65ff 100644 --- a/server/src/main/java/com/cloud/hypervisor/KVMGuru.java +++ b/server/src/main/java/com/cloud/hypervisor/KVMGuru.java @@ -155,7 +155,6 @@ protected void setVmQuotaPercentage(VirtualMachineTO to, VirtualMachineProfile v } @Override - public VirtualMachineTO implement(VirtualMachineProfile vm) { VirtualMachineTO to = toVirtualMachineTO(vm); setVmQuotaPercentage(to, vm); @@ -170,6 +169,9 @@ public VirtualMachineTO implement(VirtualMachineProfile vm) { configureVmOsDescription(virtualMachine, to, host); configureVmMemoryAndCpuCores(to, host, virtualMachine, vm); + + to.setMetadata(makeVirtualMachineMetadata(vm)); + return to; }