api,server: add params for updatehypervisorcapabilities API (#5473)

* api,server: add params for updatehypervisorcapabilities API

Allows updating following capabilities for a hypervisor, version:
- Max DATA volumes limit
- Storage motion supported
- Max hosts per cluster
- VM snapshot enabled

* added test

* changes

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* Update test/integration/smoke/test_hypervisor_capabilities.py

Co-authored-by: dahn <daan.hoogland@gmail.com>
This commit is contained in:
Abhishek Kumar 2022-02-03 10:21:23 +05:30 committed by GitHub
parent 8adb8df2fe
commit 62b806ac13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 378 additions and 16 deletions

View File

@ -52,4 +52,6 @@ public interface HypervisorCapabilities extends Identity, InternalIdentity {
boolean isStorageMotionSupported();
Boolean isVmSnapshotEnabled();
}

View File

@ -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<List<? extends HypervisorCapabilities>, 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

View File

@ -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";

View File

@ -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());

View File

@ -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;
}
}

View File

@ -189,7 +189,8 @@ public class HypervisorCapabilitiesVO implements HypervisorCapabilities {
this.maxHostsPerCluster = maxHostsPerCluster;
}
public Boolean getVmSnapshotEnabled() {
@Override
public Boolean isVmSnapshotEnabled() {
return vmSnapshotEnabled;
}

View File

@ -105,7 +105,7 @@ public class HypervisorCapabilitiesDaoImpl extends GenericDaoBase<HypervisorCapa
@Override
public Boolean isVmSnapshotEnabled(HypervisorType hypervisorType, String hypervisorVersion) {
HypervisorCapabilitiesVO result = getCapabilities(hypervisorType, hypervisorVersion);
return result.getVmSnapshotEnabled();
return result.isVmSnapshotEnabled();
}
@Override

View File

@ -34,7 +34,6 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.server.ResourceIcon;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.affinity.AffinityGroup;
@ -304,6 +303,7 @@ import com.cloud.projects.ProjectAccount;
import com.cloud.projects.ProjectInvitation;
import com.cloud.region.ha.GlobalLoadBalancerRule;
import com.cloud.resource.RollingMaintenanceManager;
import com.cloud.server.ResourceIcon;
import com.cloud.server.ResourceTag;
import com.cloud.server.ResourceTag.ResourceObjectType;
import com.cloud.service.ServiceOfferingVO;
@ -2614,6 +2614,7 @@ public class ApiResponseHelper implements ResponseGenerator {
hpvCapabilitiesResponse.setMaxDataVolumesLimit(hpvCapabilities.getMaxDataVolumesLimit());
hpvCapabilitiesResponse.setMaxHostsPerCluster(hpvCapabilities.getMaxHostsPerCluster());
hpvCapabilitiesResponse.setIsStorageMotionSupported(hpvCapabilities.isStorageMotionSupported());
hpvCapabilitiesResponse.setVmSnapshotEnabled(hpvCapabilities.isVmSnapshotEnabled());
return hpvCapabilitiesResponse;
}

View File

@ -35,8 +35,6 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.crypto.Mac;
@ -44,8 +42,6 @@ import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.dc.DomainVlanMapVO;
import com.cloud.dc.dao.DomainVlanMapDao;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.affinity.AffinityGroupProcessor;
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
@ -605,6 +601,7 @@ import com.cloud.consoleproxy.ConsoleProxyManager;
import com.cloud.dc.AccountVlanMapVO;
import com.cloud.dc.ClusterVO;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.DomainVlanMapVO;
import com.cloud.dc.HostPodVO;
import com.cloud.dc.Pod;
import com.cloud.dc.PodVlanMapVO;
@ -614,6 +611,7 @@ import com.cloud.dc.VlanVO;
import com.cloud.dc.dao.AccountVlanMapDao;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.dc.dao.DomainVlanMapDao;
import com.cloud.dc.dao.HostPodDao;
import com.cloud.dc.dao.PodVlanMapDao;
import com.cloud.dc.dao.VlanDao;
@ -663,9 +661,9 @@ import com.cloud.network.dao.LoadBalancerDao;
import com.cloud.network.dao.LoadBalancerVO;
import com.cloud.network.dao.NetworkAccountDao;
import com.cloud.network.dao.NetworkAccountVO;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkDomainDao;
import com.cloud.network.dao.NetworkDomainVO;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.vpc.dao.VpcDao;
import com.cloud.org.Cluster;
@ -4459,7 +4457,14 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
}
@Override
public HypervisorCapabilities updateHypervisorCapabilities(final Long id, final Long maxGuestsLimit, final Boolean securityGroupEnabled) {
public HypervisorCapabilities updateHypervisorCapabilities(UpdateHypervisorCapabilitiesCmd cmd) {
final Long id = cmd.getId();
final Boolean securityGroupEnabled = cmd.getSecurityGroupEnabled();
final Long maxGuestsLimit = cmd.getMaxGuestsLimit();
final Integer maxDataVolumesLimit = cmd.getMaxDataVolumesLimit();
final Boolean storageMotionSupported = cmd.getStorageMotionSupported();
final Integer maxHostsPerClusterLimit = cmd.getMaxHostsPerClusterLimit();
final Boolean vmSnapshotEnabled = cmd.getVmSnapshotEnabled();
HypervisorCapabilitiesVO hpvCapabilities = _hypervisorCapabilitiesDao.findById(id, true);
if (hpvCapabilities == null) {
@ -4468,19 +4473,37 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
throw ex;
}
final boolean updateNeeded = maxGuestsLimit != null || securityGroupEnabled != null;
final boolean updateNeeded = securityGroupEnabled != null || maxGuestsLimit != null ||
maxDataVolumesLimit != null || storageMotionSupported != null || maxHostsPerClusterLimit != null ||
vmSnapshotEnabled != null;
if (!updateNeeded) {
return hpvCapabilities;
}
hpvCapabilities = _hypervisorCapabilitiesDao.createForUpdate(id);
if (securityGroupEnabled != null) {
hpvCapabilities.setSecurityGroupEnabled(securityGroupEnabled);
}
if (maxGuestsLimit != null) {
hpvCapabilities.setMaxGuestsLimit(maxGuestsLimit);
}
if (securityGroupEnabled != null) {
hpvCapabilities.setSecurityGroupEnabled(securityGroupEnabled);
if (maxDataVolumesLimit != null) {
hpvCapabilities.setMaxDataVolumesLimit(maxDataVolumesLimit);
}
if (storageMotionSupported != null) {
hpvCapabilities.setStorageMotionSupported(storageMotionSupported);
}
if (maxHostsPerClusterLimit != null) {
hpvCapabilities.setMaxHostsPerCluster(maxHostsPerClusterLimit);
}
if (vmSnapshotEnabled != null) {
hpvCapabilities.setVmSnapshotEnabled(vmSnapshotEnabled);
}
if (_hypervisorCapabilitiesDao.update(id, hpvCapabilities)) {

View File

@ -0,0 +1,289 @@
# 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.
""" Test cases for Testing Hypervisor Capabilities
"""
from nose.plugins.attrib import attr
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.lib.utils import (cleanup_resources,
validateList)
from marvin.lib.base import (Account,
ServiceOffering,
DiskOffering,
VirtualMachine,
Volume,
Host,
VmSnapshot)
from marvin.lib.common import (get_domain,
get_zone,
get_template,
list_virtual_machines,
list_ssvms,
list_routers)
from marvin.lib.decoratorGenerators import skipTestIf
from marvin.cloudstackAPI import (updateHypervisorCapabilities,
listHypervisorCapabilities)
from marvin.codes import PASS
class TestHypervisorCapabilities(cloudstackTestCase):
@classmethod
def setUpClass(cls):
testClient = super(TestHypervisorCapabilities, cls).getClsTestClient()
cls.apiclient = testClient.getApiClient()
cls.services = testClient.getParsedTestDataConfig()
cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
cls.domain = get_domain(cls.apiclient)
cls.hypervisor = cls.testClient.getHypervisorInfo()
# Get Zone, Domain and templates
cls.notSupported = True
cls._cleanup = []
cls.hosts = Host.list(cls.apiclient, zoneid=cls.zone.id, type='Routing')
if isinstance(cls.hosts, list) and len(cls.hosts) > 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)

View File

@ -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 """