From ad2b332747961022e806790c0487d2f4bdc2b310 Mon Sep 17 00:00:00 2001 From: henko holtzhausen Date: Tue, 25 Sep 2018 15:06:16 +0200 Subject: [PATCH 1/2] back end and start vm front end --- .../api/command/user/vm/DeployVMCmd.java | 16 ++ .../api/command/user/vm/StartVMCmd.java | 22 ++ .../main/java/com/cloud/vm/UserVmManager.java | 3 + .../java/com/cloud/vm/UserVmManagerImpl.java | 94 ++++++-- ui/scripts/instances.js | 209 ++++++++++++++---- 5 files changed, 290 insertions(+), 54 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 29d4c97ce0d2..adc702135277 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -24,6 +24,8 @@ import java.util.List; import java.util.Map; +import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.PodResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.acl.RoleType; @@ -138,6 +140,12 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG @Parameter(name = ApiConstants.SSH_KEYPAIR, type = CommandType.STRING, description = "name of the ssh key pair used to login to the virtual machine") private String sshKeyPairName; + @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") + private Long podId; + + @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") + private Long clusterId; + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "destination Host ID to deploy the VM to - parameter available for root admin only") private Long hostId; @@ -317,6 +325,14 @@ public String getSSHKeyPairName() { return sshKeyPairName; } + public Long getPodId() { + return podId; + } + + public Long getClusterId() { + return clusterId; + } + public Long getHostId() { return hostId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java index b87c7de01879..5b3db8565d48 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.api.command.user.vm; +import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.PodResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.acl.RoleType; @@ -60,6 +62,18 @@ public class StartVMCmd extends BaseAsyncCmd { required = true, description = "The ID of the virtual machine") private Long id; + @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") + private Long podId; + + @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") + private Long clusterId; + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, @@ -82,6 +96,14 @@ public Long getHostId() { return hostId; } + public Long getPodId() { + return podId; + } + + public Long getClusterId() { + return clusterId; + } + // /////////////////////////////////////////////////// // ///////////// API Implementation/////////////////// // /////////////////////////////////////////////////// diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java b/server/src/main/java/com/cloud/vm/UserVmManager.java index 6ffc28e44032..fc2f558797e2 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManager.java +++ b/server/src/main/java/com/cloud/vm/UserVmManager.java @@ -99,6 +99,9 @@ public interface UserVmManager extends UserVmService { Pair> startVirtualMachine(long vmId, Long hostId, Map additionalParams, String deploymentPlannerToUse) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; + Pair> startVirtualMachine(long vmId, Long podId, Long clusterId, Long hostId, Map additionalParams, String deploymentPlannerToUse) + throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; + boolean upgradeVirtualMachine(Long id, Long serviceOfferingId, Map customParameters) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException; diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index a06b595fa891..ba6f8ace9381 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -134,6 +134,7 @@ import com.cloud.dc.DataCenterVO; import com.cloud.dc.DedicatedResourceVO; import com.cloud.dc.HostPodVO; +import com.cloud.dc.Pod; import com.cloud.dc.Vlan; import com.cloud.dc.Vlan.VlanType; import com.cloud.dc.VlanVO; @@ -2685,7 +2686,7 @@ protected boolean applyUserData(HypervisorType hyperVisorType, UserVm vm, Nic ni @Override @ActionEvent(eventType = EventTypes.EVENT_VM_START, eventDescription = "starting Vm", async = true) public UserVm startVirtualMachine(StartVMCmd cmd) throws ExecutionException, ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - return startVirtualMachine(cmd.getId(), cmd.getHostId(), null, cmd.getDeploymentPlanner()).first(); + return startVirtualMachine(cmd.getId(), cmd.getPodId(), cmd.getClusterId(), cmd.getHostId(), null, cmd.getDeploymentPlanner()).first(); } @Override @@ -4051,12 +4052,14 @@ private UserVm startVirtualMachine(DeployVMCmd cmd, Map> vmParamPair = null; try { - vmParamPair = startVirtualMachine(vmId, hostId, additonalParams, deploymentPlannerToUse); + vmParamPair = startVirtualMachine(vmId, podId, clusterId, hostId, additonalParams, deploymentPlannerToUse); vm = vmParamPair.first(); // At this point VM should be in "Running" state @@ -4381,6 +4384,12 @@ public void finalizeStop(VirtualMachineProfile profile, Answer answer) { @Override public Pair> startVirtualMachine(long vmId, Long hostId, Map additionalParams, String deploymentPlannerToUse) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { + return startVirtualMachine(vmId, null, null, hostId, additionalParams, deploymentPlannerToUse); + } + + @Override + public Pair> startVirtualMachine(long vmId, Long podId, Long clusterId, Long hostId, Map additionalParams, String deploymentPlannerToUse) + throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { // Input validation Account callerAccount = CallContext.current().getCallingAccount(); UserVO callerUser = _userDao.findById(CallContext.current().getCallingUserId()); @@ -4407,18 +4416,14 @@ public Pair> startVirtualMach throw new PermissionDeniedException("The owner of " + vm + " is disabled: " + vm.getAccountId()); } - Host destinationHost = null; - if (hostId != null) { - Account account = CallContext.current().getCallingAccount(); - if (!_accountService.isRootAdmin(account.getId())) { - throw new PermissionDeniedException( - "Parameter hostid can only be specified by a Root Admin, permission denied"); - } - destinationHost = _hostDao.findById(hostId); - if (destinationHost == null) { - throw new InvalidParameterValueException("Unable to find the host to deploy the VM, host id=" + hostId); - } - } + Account account = CallContext.current().getCallingAccount(); + boolean isRootAdmin = _accountService.isRootAdmin(account.getId()); + + //TODO : parameter validation - 1 only??? + + Pod destinationPod = getDestinationPod(podId, isRootAdmin); + Cluster destinationCluster = getDestinationCluster(clusterId, isRootAdmin); + Host destinationHost = getDestinationHost(hostId, isRootAdmin); // check if vm is security group enabled if (_securityGroupMgr.isVmSecurityGroupEnabled(vmId) && _securityGroupMgr.getSecurityGroupsForVm(vmId).isEmpty() @@ -4444,6 +4449,18 @@ public Pair> startVirtualMach if (!AllowDeployVmIfGivenHostFails.value()) { deployOnGivenHost = true; } + } else if (destinationCluster != null) { + s_logger.debug("Destination Cluster to deploy the VM is specified, specifying a deployment plan to deploy the VM"); + plan = new DataCenterDeployment(vm.getDataCenterId(), destinationCluster.getPodId(), destinationCluster.getId(), null, null, null); + if (!AllowDeployVmIfGivenHostFails.value()) { + deployOnGivenHost = true; + } + } else if (destinationPod != null) { + s_logger.debug("Destination Pod to deploy the VM is specified, specifying a deployment plan to deploy the VM"); + plan = new DataCenterDeployment(vm.getDataCenterId(), destinationPod.getId(), null, null, null, null); + if (!AllowDeployVmIfGivenHostFails.value()) { + deployOnGivenHost = true; + } } // Set parameters @@ -4510,6 +4527,55 @@ public Pair> startVirtualMach return vmParamPair; } + private Pod getDestinationPod(Long podId, boolean isRootAdmin) { + + Pod destinationPod = null; + if (podId != null) { + if (!isRootAdmin) { + throw new PermissionDeniedException( + "Parameter " + ApiConstants.POD_ID + " can only be specified by a Root Admin, permission denied"); + } + destinationPod = _podDao.findById(podId); + if (destinationPod == null) { + throw new InvalidParameterValueException("Unable to find the pod to deploy the VM, pod id=" + podId); + } + } + return destinationPod; + } + + private Cluster getDestinationCluster(Long clusterId, boolean isRootAdmin) { + + Cluster destinationCluster = null; + if (clusterId != null) { + if (!isRootAdmin) { + throw new PermissionDeniedException( + "Parameter " + ApiConstants.CLUSTER_ID + " can only be specified by a Root Admin, permission denied"); + } + destinationCluster = _clusterDao.findById(clusterId); + if (destinationCluster == null) { + throw new InvalidParameterValueException("Unable to find the cluster to deploy the VM, cluster id=" + clusterId); + } + } + return destinationCluster; + } + + private Host getDestinationHost(Long hostId, boolean isRootAdmin) { + + Host destinationHost = null; + if (hostId != null) { + + if (!isRootAdmin) { + throw new PermissionDeniedException( + "Parameter " + ApiConstants.HOST_ID + " can only be specified by a Root Admin, permission denied"); + } + destinationHost = _hostDao.findById(hostId); + if (destinationHost == null) { + throw new InvalidParameterValueException("Unable to find the host to deploy the VM, host id=" + hostId); + } + } + return destinationHost; + } + @Override public UserVm destroyVm(long vmId, boolean expunge) throws ResourceUnavailableException, ConcurrentOperationException { // Account caller = CallContext.current().getCallingAccount(); diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index 6dc45c067cc2..a4e4eb19bb53 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -741,49 +741,168 @@ title: 'label.action.start.instance', desc: 'message.action.start.instance', fields: { - hostId: { - label: 'label.host', - isHidden: function(args) { - if (isAdmin()) - return false; - else - return true; - }, - select: function(args) { - if (isAdmin()) { - $.ajax({ - url: createURL("listHosts&state=Up&type=Routing&zoneid=" + args.context.instances[0].zoneid), - dataType: "json", - async: true, - success: function(json) { - if (json.listhostsresponse.host != undefined) { - hostObjs = json.listhostsresponse.host; - var items = [{ - id: -1, - description: 'Default' - }]; - $(hostObjs).each(function() { - items.push({ - id: this.id, - description: this.name - }); - }); - args.response.success({ - data: items - }); - } else { - cloudStack.dialog.notice({ - message: _l('No Hosts are avaialble') + podId: { + label: 'label.pod', + isHidden: function(args) { + if (isAdmin()) + return false; + else + return true; + }, + select: function(args) { + if (isAdmin()) { + $.ajax({ + url: createURL("listPods&zoneid=" + args.context.instances[0].zoneid), + dataType: "json", + async: true, + success: function(json) { + if (json.listpodsresponse.pod != undefined) { + podObjs = json.listpodsresponse.pod; + var items = [{ + id: -1, + description: 'Default' + }]; + $(podObjs).each(function() { + items.push({ + id: this.id, + description: this.name }); - } + }); + args.response.success({ + data: items + }); + } else { + cloudStack.dialog.notice({ + message: _l('No Pods are available') + }); } - }); - } else { - args.response.success({ - data: null - }); - } + } + }); + + args.$select.change(function() { + var podId = this.value; + if (podId == '' || podId == -1 || podId == null) { + $(this).closest('form').find('.form-item[rel=\"clusterId\"]').hide(); + } + else { + $(this).closest('form').find('.form-item[rel=\"clusterId\"]').css('display', 'inline-block'); + } + }); + } else { + args.response.success({ + data: null + }); } + } + }, + clusterId: { + label: 'label.cluster', + isHidden: true, //function(args) { +// if (isAdmin()) +// return false; +// else +// return true; +// }, + dependsOn: 'podId', + select: function(args) { + if (isAdmin()) { + var urlString = "listClusters&zoneid=" + args.context.instances[0].zoneid; + if (args.podId != -1) { + urlString += '&podid=' + args.podId; + } + $.ajax({ + url: createURL(urlString), + dataType: "json", + async: true, + success: function(json) { + if (json.listclustersresponse.cluster != undefined) { + clusterObjs = json.listclustersresponse.cluster; + var items = [{ + id: -1, + description: 'Default' + }]; + $(clusterObjs).each(function() { + items.push({ + id: this.id, + description: this.name + }); + }); + args.response.success({ + data: items + }); + } else { + cloudStack.dialog.notice({ + message: _l('No Clusters are avaialble') + }); + } + } + }); + + args.$select.change(function() { + var clusterId = this.value; + if (clusterId == '' || clusterId == -1 || clusterId == null) { + $(this).closest('form').find('.form-item[rel=\"hostId\"]').hide(); + } + else { + $(this).closest('form').find('.form-item[rel=\"hostId\"]').css('display', 'inline-block'); + } + }); + + } else { + args.response.success({ + data: null + }); + } + } + }, + hostId: { + label: 'label.host', + isHidden: true, //function(args) { +// if (isAdmin()) +// return false; +// else +// return true; +// }, + dependsOn: 'clusterId', + select: function(args) { + var urlString = "listHosts&state=Up&type=Routing&zoneid=" + args.context.instances[0].zoneid; + if (args.clusterId != -1) { + urlString += "&clusterid=" + args.clusterId; + } + if (isAdmin()) { + $.ajax({ + url: createURL(urlString), + dataType: "json", + async: true, + success: function(json) { + if (json.listhostsresponse.host != undefined) { + hostObjs = json.listhostsresponse.host; + var items = [{ + id: -1, + description: 'Default' + }]; + $(hostObjs).each(function() { + items.push({ + id: this.id, + description: this.name + }); + }); + args.response.success({ + data: items + }); + } else { + cloudStack.dialog.notice({ + message: _l('No Hosts are avaialble') + }); + } + } + }); + } else { + args.response.success({ + data: null + }); + } + } } } }, @@ -791,6 +910,16 @@ var data = { id: args.context.instances[0].id } + if (args.$form.find('.form-item[rel=podId]').css("display") != "none" && args.data.podId != -1) { + $.extend(data, { + podid: args.data.podId + }); + } + if (args.$form.find('.form-item[rel=clusterId]').css("display") != "none" && args.data.clusterId != -1) { + $.extend(data, { + clusterid: args.data.clusterId + }); + } if (args.$form.find('.form-item[rel=hostId]').css("display") != "none" && args.data.hostId != -1) { $.extend(data, { hostid: args.data.hostId From 03056dcfb85b35af04fc70dc6c5d2e5f5e2051e9 Mon Sep 17 00:00:00 2001 From: henko holtzhausen Date: Thu, 18 Oct 2018 15:33:16 +0200 Subject: [PATCH 2/2] validation, imports --- .../java/com/cloud/vm/UserVmManagerImpl.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index ba6f8ace9381..da8db717060e 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -39,11 +39,6 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.collections.MapUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; - import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.affinity.AffinityGroupService; @@ -96,6 +91,10 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; @@ -4419,12 +4418,12 @@ public Pair> startVirtualMach Account account = CallContext.current().getCallingAccount(); boolean isRootAdmin = _accountService.isRootAdmin(account.getId()); - //TODO : parameter validation - 1 only??? - Pod destinationPod = getDestinationPod(podId, isRootAdmin); Cluster destinationCluster = getDestinationCluster(clusterId, isRootAdmin); Host destinationHost = getDestinationHost(hostId, isRootAdmin); + validateDeploymentDestination(destinationPod, destinationCluster, destinationHost); + // check if vm is security group enabled if (_securityGroupMgr.isVmSecurityGroupEnabled(vmId) && _securityGroupMgr.getSecurityGroupsForVm(vmId).isEmpty() && !_securityGroupMgr.isVmMappedToDefaultSecurityGroup(vmId) && _networkModel.canAddDefaultSecurityGroup()) { @@ -4527,6 +4526,22 @@ public Pair> startVirtualMach return vmParamPair; } + private void validateDeploymentDestination(Pod destinationPod, Cluster destinationCluster, Host destinationHost) { + + if (destinationHost != null) { + if (destinationCluster != null) { + if (destinationHost.getClusterId() != destinationCluster.getId()) { + throw new InvalidParameterValueException("Host " + destinationHost.getId() + " is not a child of provided cluster " + destinationCluster.getId()); + } + if (destinationPod != null) { + if (destinationCluster.getPodId() != destinationPod.getId()) { + throw new InvalidParameterValueException("Cluster " + destinationCluster.getId() + " is not a child of provided pod " + destinationPod.getId()); + } + } + } + } + } + private Pod getDestinationPod(Long podId, boolean isRootAdmin) { Pod destinationPod = null;