From 1e12a802104937228847f8cf5f4fa199530370e7 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 22 Aug 2024 20:49:04 +0530 Subject: [PATCH] orchestration,hypervisor: allow custom manufacture, product for vm (#9163) Signed-off-by: Abhishek Kumar --- .../cloud/agent/api/to/VirtualMachineTO.java | 18 ++++ .../com/cloud/vm/VirtualMachineManager.java | 14 ++++ .../cloud/vm/VirtualMachineManagerImpl.java | 34 +++++--- .../vm/VirtualMachineManagerImplTest.java | 83 ++++++++++++++++--- .../resource/LibvirtComputingResource.java | 2 + .../hypervisor/kvm/resource/LibvirtVMDef.java | 22 ++++- 6 files changed, 149 insertions(+), 24 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 b4f4619be9a..6f24b1cd6ca 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 @@ -84,6 +84,8 @@ public class VirtualMachineTO { Map extraConfig = new HashMap<>(); Map networkIdToNetworkNameMap = new HashMap<>(); DeployAsIsInfoTO deployAsIsInfo; + String metadataManufacturer; + String metadataProductName; 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) { @@ -429,6 +431,22 @@ public class VirtualMachineTO { this.deployAsIsInfo = deployAsIsInfo; } + public String getMetadataManufacturer() { + return metadataManufacturer; + } + + public void setMetadataManufacturer(String metadataManufacturer) { + this.metadataManufacturer = metadataManufacturer; + } + + public String getMetadataProductName() { + return metadataProductName; + } + + public void setMetadataProductName(String metadataProductName) { + this.metadataProductName = metadataProductName; + } + @Override public String toString() { return String.format("VM {id: \"%s\", name: \"%s\", uuid: \"%s\", type: \"%s\"}", id, name, uuid, type); diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index 04ba9a483e1..75211df5291 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -87,6 +87,20 @@ public interface VirtualMachineManager extends Manager { ConfigKey MetadataCustomCloudName = new ConfigKey<>("Advanced", String.class, "metadata.custom.cloud.name", "", "If provided, a custom cloud-name in cloud-init metadata", true, ConfigKey.Scope.Zone); + ConfigKey VmMetadataManufacturer = new ConfigKey<>("Advanced", String.class, + "vm.metadata.manufacturer", "Apache Software Foundation", + "If provided, a custom manufacturer will be used in the instance metadata. When an empty" + + "value is set then default manufacturer will be 'Apache Software Foundation'. " + + "A custom manufacturer may break cloud-init functionality with CloudStack datasource. Please " + + "refer documentation", true, ConfigKey.Scope.Zone); + ConfigKey VmMetadataProductName = new ConfigKey<>("Advanced", String.class, + "vm.metadata.product", "", + "If provided, a custom product name will be used in the instance metadata. When an empty" + + "value is set then default product name will be 'CloudStack Hypervisor'. " + + "A custom product name may break cloud-init functionality with CloudStack datasource. Please " + + "refer documentation", + true, ConfigKey.Scope.Zone); + interface Topics { String VM_POWER_STATE = "vm.powerstate"; } diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index d207ebc41ce..3fa27e52c09 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -49,15 +49,6 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import javax.persistence.EntityExistsException; -import com.cloud.configuration.Resource; -import com.cloud.domain.Domain; -import com.cloud.domain.dao.DomainDao; -import com.cloud.exception.ResourceAllocationException; -import com.cloud.network.vpc.VpcVO; -import com.cloud.network.vpc.dao.VpcDao; -import com.cloud.user.dao.AccountDao; -import com.cloud.event.ActionEventUtils; -import com.google.gson.Gson; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -160,6 +151,7 @@ import com.cloud.api.query.dao.UserVmJoinDao; import com.cloud.api.query.vo.DomainRouterJoinVO; import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.capacity.CapacityManager; +import com.cloud.configuration.Resource; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; import com.cloud.dc.ClusterVO; @@ -178,6 +170,9 @@ import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.deploy.DeploymentPlanningManager; import com.cloud.deploy.DeploymentPlanningManagerImpl; import com.cloud.deployasis.dao.UserVmDeployAsIsDetailsDao; +import com.cloud.domain.Domain; +import com.cloud.domain.dao.DomainDao; +import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; import com.cloud.event.UsageEventVO; @@ -189,6 +184,7 @@ import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InsufficientServerCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.StorageAccessException; import com.cloud.exception.StorageUnavailableException; @@ -211,6 +207,8 @@ import com.cloud.network.dao.NetworkDetailsDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.router.VirtualRouter; import com.cloud.network.security.SecurityGroupManager; +import com.cloud.network.vpc.VpcVO; +import com.cloud.network.vpc.dao.VpcDao; import com.cloud.offering.DiskOffering; import com.cloud.offering.DiskOfferingInfo; import com.cloud.offering.NetworkOffering; @@ -246,6 +244,7 @@ import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.user.ResourceLimitService; import com.cloud.user.User; +import com.cloud.user.dao.AccountDao; import com.cloud.uservm.UserVm; import com.cloud.utils.DateUtil; import com.cloud.utils.Journal; @@ -281,6 +280,7 @@ import com.cloud.vm.dao.VMInstanceDao; import com.cloud.vm.snapshot.VMSnapshotManager; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import com.google.gson.Gson; public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable { @@ -1101,6 +1101,19 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac markVolumesInPool(vm, answer); } + protected void updateVmMetadataManufacturerAndProduct(VirtualMachineTO vmTO, VMInstanceVO vm) { + String metadataManufacturer = VmMetadataManufacturer.valueIn(vm.getDataCenterId()); + if (StringUtils.isBlank(metadataManufacturer)) { + metadataManufacturer = VmMetadataManufacturer.defaultValue(); + } + vmTO.setMetadataManufacturer(metadataManufacturer); + String metadataProduct = VmMetadataProductName.valueIn(vm.getDataCenterId()); + if (StringUtils.isBlank(metadataManufacturer)) { + metadataProduct = String.format("CloudStack %s Hypervisor", vm.getHypervisorType().toString()); + } + vmTO.setMetadataProductName(metadataProduct); + } + @Override public void orchestrateStart(final String vmUuid, final Map params, final DeploymentPlan planToDeploy, final DeploymentPlanner planner) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { @@ -1259,6 +1272,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac vmGuru.finalizeVirtualMachineProfile(vmProfile, dest, ctx); final VirtualMachineTO vmTO = hvGuru.implement(vmProfile); + updateVmMetadataManufacturerAndProduct(vmTO, vm); checkAndSetEnterSetupMode(vmTO, params); @@ -4742,7 +4756,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac VmOpLockStateRetry, VmOpWaitInterval, ExecuteInSequence, VmJobCheckInterval, VmJobTimeout, VmJobStateReportInterval, VmConfigDriveLabel, VmConfigDriveOnPrimaryPool, VmConfigDriveForceHostCacheUse, VmConfigDriveUseHostCacheOnUnsupportedPool, HaVmRestartHostUp, ResourceCountRunningVMsonly, AllowExposeHypervisorHostname, AllowExposeHypervisorHostnameAccountLevel, SystemVmRootDiskSize, - AllowExposeDomainInMetadata, MetadataCustomCloudName + AllowExposeDomainInMetadata, MetadataCustomCloudName, VmMetadataManufacturer, VmMetadataProductName }; } diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java index 9b32980087c..f2bd1095c81 100644 --- a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java +++ b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java @@ -37,26 +37,18 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.UUID; import java.util.stream.Collectors; -import com.cloud.agent.api.to.VirtualMachineTO; -import com.cloud.api.query.vo.UserVmJoinVO; -import com.cloud.dc.DataCenterVO; -import com.cloud.dc.dao.DataCenterDao; -import com.cloud.domain.DomainVO; -import com.cloud.domain.dao.DomainDao; -import com.cloud.network.dao.NetworkDao; -import com.cloud.network.dao.NetworkVO; -import com.cloud.network.vpc.VpcVO; -import com.cloud.network.vpc.dao.VpcDao; -import com.cloud.user.AccountVO; -import com.cloud.user.dao.AccountDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.ScopedConfigStorage; +import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.commons.collections.MapUtils; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -76,24 +68,34 @@ import com.cloud.agent.api.Command; import com.cloud.agent.api.StopAnswer; import com.cloud.agent.api.StopCommand; import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.api.query.dao.UserVmJoinDao; +import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenterVO; import com.cloud.dc.Pod; import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; import com.cloud.deploy.DataCenterDeployment; import com.cloud.deploy.DeployDestination; import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.deploy.DeploymentPlanningManager; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; import com.cloud.exception.InvalidParameterValueException; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.vpc.VpcVO; +import com.cloud.network.vpc.dao.VpcDao; import com.cloud.offering.ServiceOffering; import com.cloud.org.Cluster; import com.cloud.service.ServiceOfferingVO; @@ -115,7 +117,9 @@ import com.cloud.storage.dao.VMTemplateZoneDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; +import com.cloud.user.AccountVO; import com.cloud.user.User; +import com.cloud.user.dao.AccountDao; import com.cloud.utils.Journal; import com.cloud.utils.Pair; import com.cloud.utils.Ternary; @@ -220,8 +224,12 @@ public class VirtualMachineManagerImplTest { @Mock protected StateMachine2 _stateMachine; + private ConfigDepotImpl configDepotImpl; + private boolean updatedConfigKeyDepot = false; + @Before public void setup() { + ReflectionTestUtils.getField(VirtualMachineManager.VmMetadataManufacturer, "s_depot"); virtualMachineManagerImpl.setHostAllocators(new ArrayList<>()); when(vmInstanceMock.getId()).thenReturn(vmInstanceVoMockId); @@ -251,6 +259,13 @@ public class VirtualMachineManagerImplTest { virtualMachineManagerImpl.setStoragePoolAllocators(storagePoolAllocators); } + @After + public void cleanup() { + if (updatedConfigKeyDepot) { + ReflectionTestUtils.setField(VirtualMachineManager.VmMetadataManufacturer, "s_depot", configDepotImpl); + } + } + @Test public void testaddHostIpToCertDetailsIfConfigAllows() { Host vmHost = mock(Host.class); @@ -1236,4 +1251,48 @@ public class VirtualMachineManagerImplTest { assertFalse(result.get(1L)); assertTrue(result.get(2L)); } + + private void overrideVmMetadataConfigValue(final String manufacturer, final String product) { + ConfigKey configKey = VirtualMachineManager.VmMetadataManufacturer; + this.configDepotImpl = (ConfigDepotImpl)ReflectionTestUtils.getField(configKey, "s_depot"); + ConfigDepotImpl configDepot = Mockito.mock(ConfigDepotImpl.class); + ScopedConfigStorage storage = Mockito.mock(ScopedConfigStorage.class); + Mockito.when(storage.getConfigValue(Mockito.anyLong(), Mockito.eq(configKey))).thenReturn(manufacturer); + Mockito.when(storage.getConfigValue(Mockito.anyLong(), Mockito.eq(VirtualMachineManager.VmMetadataProductName))) + .thenReturn(product); + Mockito.when(configDepot.findScopedConfigStorage(configKey)).thenReturn(storage); + Mockito.when(configDepot.findScopedConfigStorage(VirtualMachineManager.VmMetadataProductName)).thenReturn(storage); + ReflectionTestUtils.setField(configKey, "s_depot", configDepot); + updatedConfigKeyDepot = true; + } + + private Pair getDummyVmTOAndVm() { + VirtualMachineTO virtualMachineTO = new VirtualMachineTO(1L, "VM", VirtualMachine.Type.User, 1, + 1000, 256, 512, VirtualMachineTemplate.BootloaderType.HVM, "OS", + false, false, "Pass"); + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + Mockito.when(vm.getDataCenterId()).thenReturn(1L); + return new Pair<>(virtualMachineTO, vm); + } + + @Test + public void testUpdateVmMetadataManufacturerAndProductDefaultManufacturer() { + overrideVmMetadataConfigValue("", ""); + Pair pair = getDummyVmTOAndVm(); + VirtualMachineTO to = pair.first(); + virtualMachineManagerImpl.updateVmMetadataManufacturerAndProduct(to, pair.second()); + Assert.assertEquals(VirtualMachineManager.VmMetadataManufacturer.defaultValue(), to.getMetadataManufacturer()); + } + + @Test + public void testUpdateVmMetadataManufacturerAndProductCustomManufacturer() { + String manufacturer = UUID.randomUUID().toString(); + String product = UUID.randomUUID().toString(); + overrideVmMetadataConfigValue(manufacturer, product); + Pair pair = getDummyVmTOAndVm(); + VirtualMachineTO to = pair.first(); + virtualMachineManagerImpl.updateVmMetadataManufacturerAndProduct(to, pair.second()); + Assert.assertEquals(manufacturer, to.getMetadataManufacturer()); + Assert.assertEquals(product, to.getMetadataProductName()); + } } 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 dec7f70e62f..5d964509215 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 @@ -2838,6 +2838,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv GuestDef guest = new GuestDef(); configureGuestAndVMHypervisorType(vmTO, vm, guest); + guest.setManufacturer(vmTO.getMetadataManufacturer()); + guest.setProduct(vmTO.getMetadataProductName()); guest.setGuestArch(guestCpuArch != null ? guestCpuArch : vmTO.getArch()); guest.setMachineType(isGuestAarch64() ? VIRT : PC); guest.setBootType(GuestDef.BootType.BIOS); 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 a0394430643..c1ea3e99717 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 @@ -95,6 +95,8 @@ public class LibvirtVMDef { } private GuestType _type; + private String manufacturer; + private String product; private BootType _boottype; private BootMode _bootmode; private String _arch; @@ -124,6 +126,22 @@ public class LibvirtVMDef { return _type; } + public String getManufacturer() { + return manufacturer; + } + + public void setManufacturer(String manufacturer) { + this.manufacturer = manufacturer; + } + + public String getProduct() { + return product; + } + + public void setProduct(String product) { + this.product = product; + } + public void setNvram(String nvram) { _nvram = nvram; } public void setNvramTemplate(String nvramTemplate) { _nvramTemplate = nvramTemplate; } @@ -182,8 +200,8 @@ public class LibvirtVMDef { guestDef.append("\n"); guestDef.append("\n"); - guestDef.append("Apache Software Foundation\n"); - guestDef.append("CloudStack " + _type.toString() + " Hypervisor\n"); + guestDef.append("" + getManufacturer() +"\n"); + guestDef.append("" + getProduct() + "\n"); guestDef.append("" + _uuid + "\n"); guestDef.append("\n"); guestDef.append("\n");