diff --git a/api/src/main/java/com/cloud/hypervisor/HypervisorCapabilities.java b/api/src/main/java/com/cloud/hypervisor/HypervisorCapabilities.java index 368ac8423eb..03521888f9d 100644 --- a/api/src/main/java/com/cloud/hypervisor/HypervisorCapabilities.java +++ b/api/src/main/java/com/cloud/hypervisor/HypervisorCapabilities.java @@ -52,4 +52,6 @@ public interface HypervisorCapabilities extends Identity, InternalIdentity { boolean isStorageMotionSupported(); + Boolean isVmSnapshotEnabled(); + } diff --git a/api/src/main/java/com/cloud/server/ManagementService.java b/api/src/main/java/com/cloud/server/ManagementService.java index 56f36a8d90c..868e606a333 100644 --- a/api/src/main/java/com/cloud/server/ManagementService.java +++ b/api/src/main/java/com/cloud/server/ManagementService.java @@ -22,6 +22,7 @@ import java.util.Map; import org.apache.cloudstack.api.command.admin.cluster.ListClustersCmd; import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd; +import org.apache.cloudstack.api.command.admin.config.UpdateHypervisorCapabilitiesCmd; import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.AddGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.guest.ListGuestOsMappingCmd; @@ -407,7 +408,7 @@ public interface ManagementService { Pair, Integer> listHypervisorCapabilities(Long id, HypervisorType hypervisorType, String keyword, Long startIndex, Long pageSizeVal); - HypervisorCapabilities updateHypervisorCapabilities(Long id, Long maxGuestsLimit, Boolean securityGroupEnabled); + HypervisorCapabilities updateHypervisorCapabilities(UpdateHypervisorCapabilitiesCmd cmd); /** * list all the top consumed resources across different capacity types diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index b9a809f9956..b2afa1d6f86 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -590,6 +590,7 @@ public class ApiConstants { public static final String MIGRATE_ALLOWED = "migrateallowed"; public static final String MIGRATE_TO = "migrateto"; public static final String GUID = "guid"; + public static final String VM_SNAPSHOT_ENABELD = "vmsnapshotenabled"; public static final String VSWITCH_TYPE_GUEST_TRAFFIC = "guestvswitchtype"; public static final String VSWITCH_TYPE_PUBLIC_TRAFFIC = "publicvswitchtype"; public static final String VSWITCH_NAME_GUEST_TRAFFIC = "guestvswitchname"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateHypervisorCapabilitiesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateHypervisorCapabilitiesCmd.java index 6550c89240b..8eb844614f7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateHypervisorCapabilitiesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateHypervisorCapabilitiesCmd.java @@ -52,6 +52,18 @@ public class UpdateHypervisorCapabilitiesCmd extends BaseCmd { @Parameter(name = ApiConstants.MAX_GUESTS_LIMIT, type = CommandType.LONG, description = "the max number of Guest VMs per host for this hypervisor.") private Long maxGuestsLimit; + @Parameter(name = ApiConstants.MAX_DATA_VOLUMES_LIMIT, type = CommandType.INTEGER, description = "the maximum number of Data Volumes that can be attached to a VM for this hypervisor.", since = "4.16.0") + private Integer maxDataVolumesLimit; + + @Parameter(name = ApiConstants.STORAGE_MOTION_ENABLED, type = CommandType.BOOLEAN, description = "set true to enable storage motion support for this hypervisor", since = "4.16.0") + private Boolean storageMotionSupported; + + @Parameter(name = ApiConstants.MAX_HOSTS_PER_CLUSTER, type = CommandType.INTEGER, description = "the maximum number of the hypervisor hosts per cluster ", since = "4.16.0") + private Integer maxHostsPerClusterLimit; + + @Parameter(name = ApiConstants.VM_SNAPSHOT_ENABELD, type = CommandType.BOOLEAN, description = "set true to enable VM snapshots for this hypervisor", since = "4.16.0") + private Boolean vmSnapshotEnabled; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -68,6 +80,22 @@ public class UpdateHypervisorCapabilitiesCmd extends BaseCmd { return maxGuestsLimit; } + public Integer getMaxDataVolumesLimit() { + return maxDataVolumesLimit; + } + + public Boolean getStorageMotionSupported() { + return storageMotionSupported; + } + + public Integer getMaxHostsPerClusterLimit() { + return maxHostsPerClusterLimit; + } + + public Boolean getVmSnapshotEnabled() { + return vmSnapshotEnabled; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -84,7 +112,7 @@ public class UpdateHypervisorCapabilitiesCmd extends BaseCmd { @Override public void execute() { - HypervisorCapabilities result = _mgr.updateHypervisorCapabilities(getId(), getMaxGuestsLimit(), getSecurityGroupEnabled()); + HypervisorCapabilities result = _mgr.updateHypervisorCapabilities(this); if (result != null) { HypervisorCapabilitiesResponse response = _responseGenerator.createHypervisorCapabilitiesResponse(result); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/HypervisorCapabilitiesResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/HypervisorCapabilitiesResponse.java index d6f7c2e2e44..0a7c03de86a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/HypervisorCapabilitiesResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/HypervisorCapabilitiesResponse.java @@ -16,8 +16,6 @@ // under the License. package org.apache.cloudstack.api.response; -import com.google.gson.annotations.SerializedName; - import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; @@ -25,6 +23,7 @@ import org.apache.cloudstack.api.EntityReference; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.HypervisorCapabilities; import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; @EntityReference(value = HypervisorCapabilities.class) public class HypervisorCapabilitiesResponse extends BaseResponse { @@ -60,6 +59,10 @@ public class HypervisorCapabilitiesResponse extends BaseResponse { @Param(description = "true if storage motion is supported") private boolean isStorageMotionSupported; + @SerializedName(ApiConstants.VM_SNAPSHOT_ENABELD) + @Param(description = "true if VM snapshots are enabled for this hypervisor") + private boolean isVmSnapshotEnabled; + public String getId() { return id; } @@ -123,4 +126,12 @@ public class HypervisorCapabilitiesResponse extends BaseResponse { public void setMaxHostsPerCluster(Integer maxHostsPerCluster) { this.maxHostsPerCluster = maxHostsPerCluster; } + + public boolean isVmSnapshotEnabled() { + return isVmSnapshotEnabled; + } + + public void setVmSnapshotEnabled(boolean vmSnapshotEnabled) { + isVmSnapshotEnabled = vmSnapshotEnabled; + } } diff --git a/engine/schema/src/main/java/com/cloud/hypervisor/HypervisorCapabilitiesVO.java b/engine/schema/src/main/java/com/cloud/hypervisor/HypervisorCapabilitiesVO.java index 2deeb2e2e34..5ab684c1100 100644 --- a/engine/schema/src/main/java/com/cloud/hypervisor/HypervisorCapabilitiesVO.java +++ b/engine/schema/src/main/java/com/cloud/hypervisor/HypervisorCapabilitiesVO.java @@ -189,7 +189,8 @@ public class HypervisorCapabilitiesVO implements HypervisorCapabilities { this.maxHostsPerCluster = maxHostsPerCluster; } - public Boolean getVmSnapshotEnabled() { + @Override + public Boolean isVmSnapshotEnabled() { return vmSnapshotEnabled; } diff --git a/engine/schema/src/main/java/com/cloud/hypervisor/dao/HypervisorCapabilitiesDaoImpl.java b/engine/schema/src/main/java/com/cloud/hypervisor/dao/HypervisorCapabilitiesDaoImpl.java index 35f765b554a..9a1f29014f9 100644 --- a/engine/schema/src/main/java/com/cloud/hypervisor/dao/HypervisorCapabilitiesDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/hypervisor/dao/HypervisorCapabilitiesDaoImpl.java @@ -105,7 +105,7 @@ public class HypervisorCapabilitiesDaoImpl extends GenericDaoBase 0: + cls.host = cls.hosts[0] + cls.notSupported = False + + cls.hypervisorversion = "default" + if hasattr(cls.host, 'hypervisorversion'): + cls.hypervisorversion = cls.host.hypervisorversion + + if cls.notSupported == False: + cls.notSupported = True + cmdList = listHypervisorCapabilities.listHypervisorCapabilitiesCmd() + cmdList.hypervisor = cls.hypervisor + capabilities = cls.apiclient.listHypervisorCapabilities(cmdList) + for capability in capabilities: + if capability.hypervisorversion == cls.hypervisorversion: + cls.hostCapability = capability + cls.notSupported = False + break + + if cls.notSupported == True: + cls.hypervisorversion = "default" + for capability in capabilities: + if capability.hypervisorversion == cls.hypervisorversion: + cls.hostCapability = capability + cls.notSupported = False + break + + if cls.notSupported == False: + cls.template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"]) + + # Create an account + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + + # Create user api client of the account + cls.userapiclient = testClient.getUserApiClient( + UserName=cls.account.name, + DomainName=cls.account.domain + ) + # Create Service offering + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offering"], + hosttags="host1" + ) + cls._cleanup.append(cls.service_offering) + + cls.disk_offering = DiskOffering.create( + cls.apiclient, + cls.services["disk_offering"] + ) + cls._cleanup.append(cls.disk_offering) + + return + + @classmethod + def tearDownClass(cls): + super(TestHypervisorCapabilities, cls).tearDownClass() + + def setUp(self): + self.cleanup = [] + + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + + def tearDown(self): + try: + if self.notSupported == False: + Host.update(self.apiclient, id=self.host.id, hosttags="") + self.updateHostHypervisorCapability(self.hostCapability.id, + self.hostCapability.maxdatavolumeslimit, + self.hostCapability.vmsnapshotenabled) + + super(TestHypervisorCapabilities, self).tearDown() + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @skipTestIf("notSupported") + @attr(tags=["advanced", "basic"], required_hardware="false") + def test_01_check_hypervisor_max_data_volume_limit(self): + """ Test hypervisor maxdatavolumeslimit effect + + # 1. Set maxdatavolumeslimit to 1 for hypervisor + # 2. List capabilities and verify value + # 3. Deploy a VM and attach a volume to it + # 4. Try attach another volume, it should fail + # 5. Set maxdatavolumeslimit to 32 for hypervisor + # 6. Try attach second volume, it should succeed + """ + self.updateHostHypervisorCapability(self.hostCapability.id, 1) + capabilities = self.listHostHypervisorCapabilities(self.hostCapability.id) + self.assertTrue(isinstance(capabilities, list), "listHypervisorCapabilities response not a valid list") + self.assertEqual(len(capabilities), 1, "listHypervisorCapabilities response not valid") + self.assertEqual(capabilities[0].maxdatavolumeslimit, + 1, + "listHypervisorCapabilities response maxdatavolumeslimit value not 1") + + Host.update(self.apiclient, id=self.host.id, hosttags="host1") + volume_created1 = Volume.create( + self.userapiclient, + self.services["volume"], + zoneid=self.zone.id, + diskofferingid=self.disk_offering.id + ) + self.cleanup.append(volume_created1) + volume_created2 = Volume.create( + self.userapiclient, + self.services["volume"], + zoneid=self.zone.id, + diskofferingid=self.disk_offering.id + ) + self.cleanup.append(volume_created2) + vm = VirtualMachine.create( + self.userapiclient, + self.services["small"], + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + zoneid=self.zone.id + ) + self.cleanup.append(vm) + + vm.attach_volume( + self.userapiclient, + volume_created1 + ) + + try: + vm.attach_volume( + self.userapiclient, + volume_created2 + ) + vm.detach_volume(self.userapiclient, volume_created1) + vm.detach_volume(self.userapiclient, volume_created2) + self.fail("Successful to attach 2 DATA disks when max DATA disk limit was set to 1") + except Exception as e: + self.debug("Failed to attach 2nd DATA disk when max DATA disk limit was set to 1: %s" % e) + + self.updateHostHypervisorCapability(self.hostCapability.id, 32) + + vm.attach_volume( + self.userapiclient, + volume_created2 + ) + vm.stop(self.userapiclient, forced=True) + vm.detach_volume(self.userapiclient, volume_created1) + vm.detach_volume(self.userapiclient, volume_created2) + + @skipTestIf("notSupported") + @attr(tags=["advanced", "basic"], required_hardware="false") + def test_02_check_hypervisor_vm_snapshot(self): + """ Test hypervisor vmsnapshotenabled effect + + # 1. Set vmsnapshotenabled to false for hypervisor + # 2. List capabilities and verify value + # 3. Deploy a VM + # 4. Try VM snapshot, it should fail + # 5. Set vmsnapshotenabled to true for hypervisor + # 6. Try VM snapshot again, it should succeed + """ + if self.hypervisor == "KVM": + self.skipTest("Skipping test: Reason - VM Snapshot of running VM is not supported for KVM") + self.updateHostHypervisorCapability(self.hostCapability.id, None, False) + capabilities = self.listHostHypervisorCapabilities(self.hostCapability.id) + self.assertTrue(isinstance(capabilities, list), "listHypervisorCapabilities response not a valid list") + self.assertEqual(len(capabilities), 1, "listHypervisorCapabilities response not valid") + self.assertEqual(capabilities[0].vmsnapshotenabled, + False, + "listHypervisorCapabilities response vmsnapshotenabled value not False") + + Host.update(self.apiclient, id=self.host.id, hosttags="host1") + vm = VirtualMachine.create( + self.userapiclient, + self.services["small"], + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + zoneid=self.zone.id + ) + self.cleanup.append(vm) + + try: + fail_snapshot = VmSnapshot.create( + self.userapiclient, + vmid=vm.id, + snapshotmemory="false", + name="Test Snapshot", + description="Test Snapshot Desc" + ) + self.cleanup.append(fail_snapshot) + self.fail("Successful to take VM snapshot even when vmsnapshotenabled was set to False") + except Exception as e: + self.debug("Failed to take VM snapshot even vmsnapshotenabled was set to False: %s" % e) + + self.updateHostHypervisorCapability(self.hostCapability.id, None, True) + + vm_snapshot = VmSnapshot.create( + self.userapiclient, + vmid=vm.id, + snapshotmemory="false", + name="Test Snapshot", + description="Test Snapshot Desc" + ) + self.cleanup.append(vm_snapshot) + + def updateHostHypervisorCapability(self, id, maxDataVolumes, vmSnapshotEnabled=None): + cmd = updateHypervisorCapabilities.updateHypervisorCapabilitiesCmd() + cmd.id = id + if maxDataVolumes != None: + cmd.maxdatavolumeslimit = maxDataVolumes + if vmSnapshotEnabled != None: + cmd.vmsnapshotenabled = vmSnapshotEnabled + self.apiclient.updateHypervisorCapabilities(cmd) + + def listHostHypervisorCapabilities(self, id, hypervisor=None): + cmd = listHypervisorCapabilities.listHypervisorCapabilitiesCmd() + cmd.id = id + return self.apiclient.listHypervisorCapabilities(cmd) + diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 910e8bd7c04..9096a3b9d6b 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -5097,6 +5097,11 @@ class VmSnapshot: cmd.vmsnapshotid = vmsnapshotid return apiclient.deleteVMSnapshot(cmd) + def delete(self, apiclient): + cmd = deleteVMSnapshot.deleteVMSnapshotCmd() + cmd.vmsnapshotid = self.id + return apiclient.deleteVMSnapshot(cmd) + class Region: """ Regions related Api """