Skip to content

Commit 9e6ee7c

Browse files
Anurag Awasthiyadvr
authored andcommitted
server: Add support for new heuristics based VM Deployement for admins (#3454)
Currently an admin can choose which host a VM is to be started on. They should be able to 'override' the allocation algorthm to a greater or lesser extent at will, and be able to choose the pod, cluster or host that they wish a new VM to be deployed in. DeployVirtualMachine API has been extended with additional, optional parameters podid and clusterid that will be passed to and used in the deployment planner, when selecting a viable host. If the user supplies a pod, a suitable host in the given pod will be selected. If the user supplies a cluster, a suitable host in the given cluster will be selected. Based on the parameter supplied and on passing validation, the VM will then be deployed on the selected host, cluster or pod.
1 parent 97df529 commit 9e6ee7c

12 files changed

Lines changed: 891 additions & 83 deletions

File tree

api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVMCmdByAdmin.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.command.admin.vm;
1818

19+
import org.apache.cloudstack.api.ApiConstants;
20+
import org.apache.cloudstack.api.Parameter;
21+
import org.apache.cloudstack.api.response.ClusterResponse;
22+
import org.apache.cloudstack.api.response.PodResponse;
1923
import org.apache.log4j.Logger;
2024

2125
import org.apache.cloudstack.api.APICommand;
@@ -39,6 +43,19 @@
3943
public class DeployVMCmdByAdmin extends DeployVMCmd {
4044
public static final Logger s_logger = Logger.getLogger(DeployVMCmdByAdmin.class.getName());
4145

46+
@Parameter(name = ApiConstants.POD_ID, type = CommandType.UUID, entityType = PodResponse.class, description = "destination Pod ID to deploy the VM to - parameter available for root admin only", since = "4.13")
47+
private Long podId;
48+
49+
@Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID, entityType = ClusterResponse.class, description = "destination Cluster ID to deploy the VM to - parameter available for root admin only", since = "4.13")
50+
private Long clusterId;
51+
52+
public Long getPodId() {
53+
return podId;
54+
}
55+
56+
public Long getClusterId() {
57+
return clusterId;
58+
}
4259

4360
@Override
4461
public void execute(){

api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
import java.util.List;
2525
import java.util.Map;
2626

27-
import org.apache.log4j.Logger;
28-
2927
import org.apache.cloudstack.acl.RoleType;
3028
import org.apache.cloudstack.affinity.AffinityGroupResponse;
3129
import org.apache.cloudstack.api.ACL;
@@ -49,6 +47,7 @@
4947
import org.apache.cloudstack.api.response.ZoneResponse;
5048
import org.apache.cloudstack.context.CallContext;
5149
import org.apache.commons.collections.MapUtils;
50+
import org.apache.log4j.Logger;
5251

5352
import com.cloud.event.EventTypes;
5453
import com.cloud.exception.ConcurrentOperationException;

api/src/main/java/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.command.user.vm;
1818

19+
import org.apache.cloudstack.api.response.ClusterResponse;
20+
import org.apache.cloudstack.api.response.PodResponse;
1921
import org.apache.log4j.Logger;
2022

2123
import org.apache.cloudstack.acl.RoleType;
@@ -60,6 +62,18 @@ public class StartVMCmd extends BaseAsyncCmd {
6062
required = true, description = "The ID of the virtual machine")
6163
private Long id;
6264

65+
@Parameter(name = ApiConstants.POD_ID,
66+
type = CommandType.UUID,
67+
entityType = PodResponse.class,
68+
description = "destination Pod ID to deploy the VM to - parameter available for root admin only")
69+
private Long podId;
70+
71+
@Parameter(name = ApiConstants.CLUSTER_ID,
72+
type = CommandType.UUID,
73+
entityType = ClusterResponse.class,
74+
description = "destination Cluster ID to deploy the VM to - parameter available for root admin only")
75+
private Long clusterId;
76+
6377
@Parameter(name = ApiConstants.HOST_ID,
6478
type = CommandType.UUID,
6579
entityType = HostResponse.class,
@@ -82,6 +96,14 @@ public Long getHostId() {
8296
return hostId;
8397
}
8498

99+
public Long getPodId() {
100+
return podId;
101+
}
102+
103+
public Long getClusterId() {
104+
return clusterId;
105+
}
106+
85107
// ///////////////////////////////////////////////////
86108
// ///////////// API Implementation///////////////////
87109
// ///////////////////////////////////////////////////

server/src/main/java/com/cloud/vm/UserVmManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ public interface UserVmManager extends UserVmService {
9999
Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMachine(long vmId, Long hostId, Map<VirtualMachineProfile.Param, Object> additionalParams, String deploymentPlannerToUse)
100100
throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException;
101101

102+
Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMachine(long vmId, Long podId, Long clusterId, Long hostId, Map<VirtualMachineProfile.Param, Object> additionalParams, String deploymentPlannerToUse)
103+
throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException;
104+
102105
boolean upgradeVirtualMachine(Long id, Long serviceOfferingId, Map<String, String> customParameters) throws ResourceUnavailableException,
103106
ConcurrentOperationException, ManagementServerException,
104107
VirtualMachineMigrationException;

server/src/main/java/com/cloud/vm/UserVmManagerImpl.java

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.apache.cloudstack.api.ApiConstants;
5050
import org.apache.cloudstack.api.BaseCmd.HTTPMethod;
5151
import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd;
52+
import org.apache.cloudstack.api.command.admin.vm.DeployVMCmdByAdmin;
5253
import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd;
5354
import org.apache.cloudstack.api.command.user.vm.AddNicToVMCmd;
5455
import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
@@ -136,6 +137,7 @@
136137
import com.cloud.dc.DataCenterVO;
137138
import com.cloud.dc.DedicatedResourceVO;
138139
import com.cloud.dc.HostPodVO;
140+
import com.cloud.dc.Pod;
139141
import com.cloud.dc.Vlan;
140142
import com.cloud.dc.Vlan.VlanType;
141143
import com.cloud.dc.VlanVO;
@@ -2755,7 +2757,7 @@ protected boolean applyUserData(HypervisorType hyperVisorType, UserVm vm, Nic ni
27552757
@Override
27562758
@ActionEvent(eventType = EventTypes.EVENT_VM_START, eventDescription = "starting Vm", async = true)
27572759
public UserVm startVirtualMachine(StartVMCmd cmd) throws ExecutionException, ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
2758-
return startVirtualMachine(cmd.getId(), cmd.getHostId(), null, cmd.getDeploymentPlanner()).first();
2760+
return startVirtualMachine(cmd.getId(), cmd.getPodId(), cmd.getClusterId(), cmd.getHostId(), null, cmd.getDeploymentPlanner()).first();
27592761
}
27602762

27612763
@Override
@@ -4144,20 +4146,27 @@ protected String validateUserData(String userData, HTTPMethod httpmethod) {
41444146
@Override
41454147
@ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "starting Vm", async = true)
41464148
public UserVm startVirtualMachine(DeployVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ConcurrentOperationException {
4147-
return startVirtualMachine(cmd, null, cmd.getDeploymentPlanner());
4149+
long vmId = cmd.getEntityId();
4150+
Long podId = null;
4151+
Long clusterId = null;
4152+
Long hostId = cmd.getHostId();
4153+
Map<Long, DiskOffering> diskOfferingMap = cmd.getDataDiskTemplateToDiskOfferingMap();
4154+
if (cmd instanceof DeployVMCmdByAdmin) {
4155+
DeployVMCmdByAdmin adminCmd = (DeployVMCmdByAdmin)cmd;
4156+
podId = adminCmd.getPodId();
4157+
clusterId = adminCmd.getClusterId();
4158+
}
4159+
return startVirtualMachine(vmId, podId, clusterId, hostId, diskOfferingMap, null, cmd.getDeploymentPlanner());
41484160
}
41494161

4150-
private UserVm startVirtualMachine(DeployVMCmd cmd, Map<VirtualMachineProfile.Param, Object> additonalParams, String deploymentPlannerToUse)
4162+
private UserVm startVirtualMachine(long vmId, Long podId, Long clusterId, Long hostId, Map<Long, DiskOffering> diskOfferingMap, Map<VirtualMachineProfile.Param, Object> additonalParams, String deploymentPlannerToUse)
41514163
throws ResourceUnavailableException,
41524164
InsufficientCapacityException, ConcurrentOperationException {
4153-
4154-
long vmId = cmd.getEntityId();
4155-
Long hostId = cmd.getHostId();
41564165
UserVmVO vm = _vmDao.findById(vmId);
4157-
41584166
Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> vmParamPair = null;
4167+
41594168
try {
4160-
vmParamPair = startVirtualMachine(vmId, hostId, additonalParams, deploymentPlannerToUse);
4169+
vmParamPair = startVirtualMachine(vmId, podId, clusterId, hostId, additonalParams, deploymentPlannerToUse);
41614170
vm = vmParamPair.first();
41624171

41634172
// At this point VM should be in "Running" state
@@ -4169,7 +4178,7 @@ private UserVm startVirtualMachine(DeployVMCmd cmd, Map<VirtualMachineProfile.Pa
41694178
}
41704179

41714180
try {
4172-
if (!cmd.getDataDiskTemplateToDiskOfferingMap().isEmpty()) {
4181+
if (!diskOfferingMap.isEmpty()) {
41734182
List<VolumeVO> vols = _volsDao.findByInstance(tmpVm.getId());
41744183
for (VolumeVO vol : vols) {
41754184
if (vol.getVolumeType() == Volume.Type.DATADISK) {
@@ -4488,8 +4497,14 @@ public void finalizeStop(VirtualMachineProfile profile, Answer answer) {
44884497
@Override
44894498
public Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMachine(long vmId, Long hostId, Map<VirtualMachineProfile.Param, Object> additionalParams, String deploymentPlannerToUse)
44904499
throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
4500+
return startVirtualMachine(vmId, null, null, hostId, additionalParams, deploymentPlannerToUse);
4501+
}
4502+
4503+
@Override
4504+
public Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMachine(long vmId, Long podId, Long clusterId, Long hostId, Map<VirtualMachineProfile.Param, Object> additionalParams, String deploymentPlannerToUse)
4505+
throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
44914506
// Input validation
4492-
Account callerAccount = CallContext.current().getCallingAccount();
4507+
final Account callerAccount = CallContext.current().getCallingAccount();
44934508
UserVO callerUser = _userDao.findById(CallContext.current().getCallingUserId());
44944509

44954510
// if account is removed, return error
@@ -4514,19 +4529,6 @@ public Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMach
45144529
throw new PermissionDeniedException("The owner of " + vm + " is disabled: " + vm.getAccountId());
45154530
}
45164531

4517-
Host destinationHost = null;
4518-
if (hostId != null) {
4519-
Account account = CallContext.current().getCallingAccount();
4520-
if (!_accountService.isRootAdmin(account.getId())) {
4521-
throw new PermissionDeniedException(
4522-
"Parameter hostid can only be specified by a Root Admin, permission denied");
4523-
}
4524-
destinationHost = _hostDao.findById(hostId);
4525-
if (destinationHost == null) {
4526-
throw new InvalidParameterValueException("Unable to find the host to deploy the VM, host id=" + hostId);
4527-
}
4528-
}
4529-
45304532
// check if vm is security group enabled
45314533
if (_securityGroupMgr.isVmSecurityGroupEnabled(vmId) && _securityGroupMgr.getSecurityGroupsForVm(vmId).isEmpty()
45324534
&& !_securityGroupMgr.isVmMappedToDefaultSecurityGroup(vmId) && _networkModel.canAddDefaultSecurityGroup()) {
@@ -4542,7 +4544,13 @@ public Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMach
45424544
_securityGroupMgr.addInstanceToGroups(vmId, groupList);
45434545
}
45444546
}
4545-
4547+
// Choose deployment planner
4548+
// Host takes 1st preference, Cluster takes 2nd preference and Pod takes 3rd
4549+
// Default behaviour is invoked when host, cluster or pod are not specified
4550+
boolean isRootAdmin = _accountService.isRootAdmin(callerAccount.getId());
4551+
Pod destinationPod = getDestinationPod(podId, isRootAdmin);
4552+
Cluster destinationCluster = getDestinationCluster(clusterId, isRootAdmin);
4553+
Host destinationHost = getDestinationHost(hostId, isRootAdmin);
45464554
DataCenterDeployment plan = null;
45474555
boolean deployOnGivenHost = false;
45484556
if (destinationHost != null) {
@@ -4551,6 +4559,18 @@ public Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMach
45514559
if (!AllowDeployVmIfGivenHostFails.value()) {
45524560
deployOnGivenHost = true;
45534561
}
4562+
} else if (destinationCluster != null) {
4563+
s_logger.debug("Destination Cluster to deploy the VM is specified, specifying a deployment plan to deploy the VM");
4564+
plan = new DataCenterDeployment(vm.getDataCenterId(), destinationCluster.getPodId(), destinationCluster.getId(), null, null, null);
4565+
if (!AllowDeployVmIfGivenHostFails.value()) {
4566+
deployOnGivenHost = true;
4567+
}
4568+
} else if (destinationPod != null) {
4569+
s_logger.debug("Destination Pod to deploy the VM is specified, specifying a deployment plan to deploy the VM");
4570+
plan = new DataCenterDeployment(vm.getDataCenterId(), destinationPod.getId(), null, null, null, null);
4571+
if (!AllowDeployVmIfGivenHostFails.value()) {
4572+
deployOnGivenHost = true;
4573+
}
45544574
}
45554575

45564576
// Set parameters
@@ -4617,6 +4637,51 @@ public Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMach
46174637
return vmParamPair;
46184638
}
46194639

4640+
private Pod getDestinationPod(Long podId, boolean isRootAdmin) {
4641+
Pod destinationPod = null;
4642+
if (podId != null) {
4643+
if (!isRootAdmin) {
4644+
throw new PermissionDeniedException(
4645+
"Parameter " + ApiConstants.POD_ID + " can only be specified by a Root Admin, permission denied");
4646+
}
4647+
destinationPod = _podDao.findById(podId);
4648+
if (destinationPod == null) {
4649+
throw new InvalidParameterValueException("Unable to find the pod to deploy the VM, pod id=" + podId);
4650+
}
4651+
}
4652+
return destinationPod;
4653+
}
4654+
4655+
private Cluster getDestinationCluster(Long clusterId, boolean isRootAdmin) {
4656+
Cluster destinationCluster = null;
4657+
if (clusterId != null) {
4658+
if (!isRootAdmin) {
4659+
throw new PermissionDeniedException(
4660+
"Parameter " + ApiConstants.CLUSTER_ID + " can only be specified by a Root Admin, permission denied");
4661+
}
4662+
destinationCluster = _clusterDao.findById(clusterId);
4663+
if (destinationCluster == null) {
4664+
throw new InvalidParameterValueException("Unable to find the cluster to deploy the VM, cluster id=" + clusterId);
4665+
}
4666+
}
4667+
return destinationCluster;
4668+
}
4669+
4670+
private Host getDestinationHost(Long hostId, boolean isRootAdmin) {
4671+
Host destinationHost = null;
4672+
if (hostId != null) {
4673+
if (!isRootAdmin) {
4674+
throw new PermissionDeniedException(
4675+
"Parameter " + ApiConstants.HOST_ID + " can only be specified by a Root Admin, permission denied");
4676+
}
4677+
destinationHost = _hostDao.findById(hostId);
4678+
if (destinationHost == null) {
4679+
throw new InvalidParameterValueException("Unable to find the host to deploy the VM, host id=" + hostId);
4680+
}
4681+
}
4682+
return destinationHost;
4683+
}
4684+
46204685
@Override
46214686
public UserVm destroyVm(long vmId, boolean expunge) throws ResourceUnavailableException, ConcurrentOperationException {
46224687
// Account caller = CallContext.current().getCallingAccount();

0 commit comments

Comments
 (0)