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..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; @@ -134,6 +133,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 +2685,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 +4051,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 +4383,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 +4415,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()); + + 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() @@ -4444,6 +4448,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 +4526,71 @@ 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; + 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/index.html b/ui/index.html index 7a8e3da0177d..0a672080b556 100644 --- a/ui/index.html +++ b/ui/index.html @@ -97,15 +97,48 @@
-
+

- + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

diff --git a/ui/scripts/instanceWizard.js b/ui/scripts/instanceWizard.js index 351ca7b30208..f9ff38f737b8 100644 --- a/ui/scripts/instanceWizard.js +++ b/ui/scripts/instanceWizard.js @@ -22,6 +22,46 @@ var step6ContainerType = 'nothing-to-select'; //'nothing-to-select', 'select-network', 'select-security-group', 'select-advanced-sg'(advanced sg-enabled zone) cloudStack.instanceWizard = { + + fetchHostList: function(args, parentId) { + + $.ajax({ + url: createURL("listHosts&state=Up&type=Routing&clusterid="+parentId), + dataType: "json", + async: false, + success: function(json) { + if (json.listhostsresponse.host != undefined) { + hostObjs = json.listhostsresponse.host; + hosts = [{ + id: -1, + description: 'Default', + parentId: -1 + }]; + $(hostObjs).each(function() { + hosts.push({ + id: this.id, + description: this.name, + parentId: this.clusterid + }); + }); + args.hostcallback(hosts); + } + } + }); + + args.response.success({ + data: { + hosts: hosts + } + }); + + }, + + //explain it + parentFilter: function(data, parentId) { + return parentId != -1 && data != undefined ? data.parentId == parentId || data.parentId == -1 : true; + }, + //min disk offering size when custom disk size is used minDiskOfferingSize: function() { return g_capabilities.customdiskofferingminsize; @@ -94,19 +134,104 @@ } //in all other cases (as well as from instance page) all zones are populated to dropdown else { + console.log('zone etc calls to follow'); + console.log('args') + console.log(args); $.ajax({ url: createURL("listZones&available=true"), dataType: "json", async: false, success: function(json) { zoneObjs = json.listzonesresponse.zone; - args.response.success({ - data: { - zones: zoneObjs - } + zones = [{ + id: -1, + description: 'Default' + }]; + $(zoneObjs).each(function() { + zones.push({ + id: this.id, + name: this.name + }); }); } }); + $.ajax({ + url: createURL("listPods"), + dataType: "json", + async: false, + success: function(json) { + if (json.listpodsresponse.pod != undefined) { + podObjs = json.listpodsresponse.pod; + pods = [{ + id: -1, + description: 'Default' + }]; + $(podObjs).each(function() { + pods.push({ + id: this.id, + description: this.name, + parentId: this.zoneid + }); + }); + } + } + }); + $.ajax({ + url: createURL("listClusters"), + dataType: "json", + async: false, + success: function(json) { + if (json.listclustersresponse.cluster != undefined) { + clusterObjs = json.listclustersresponse.cluster; + clusters = [{ + id: -1, + description: 'Default' + }]; + $(clusterObjs).each(function() { + clusters.push({ + id: this.id, + description: this.name, + parentId: this.podid + }); + }); + } + } + }); + $.ajax({ + url: createURL("listHosts&state=Up&type=Routing"), + dataType: "json", + async: false, + success: function(json) { + if (json.listhostsresponse.host != undefined) { + hostObjs = json.listhostsresponse.host; + hosts = [{ + id: -1, + description: 'Default', + parentId: -1 + }]; + $(hostObjs).each(function() { + hosts.push({ + id: this.id, + description: this.name, + parentId: this.clusterid + }); + }); + } + } + }); + console.log('creating response'); + console.log(zoneObjs); + console.log(pods); + console.log(clusters); + console.log(hosts); + args.response.success({ + data: { + zones: zones, + pods: pods, + clusters: clusters, + hosts: hosts + } + }); } }, 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 diff --git a/ui/scripts/ui-custom/instanceWizard.js b/ui/scripts/ui-custom/instanceWizard.js index 443e2371378c..22f3512587a3 100644 --- a/ui/scripts/ui-custom/instanceWizard.js +++ b/ui/scripts/ui-custom/instanceWizard.js @@ -278,19 +278,209 @@ }).click(); }; + var filterPodList = function(zoneId) { + var $selects = $step.find('.select-deployment .podid'); + + var $visibleSelects = $($.grep($selects, function(select) { + var $select = $(select); + console.log('---grep pod list---') + console.log('zoneId') + console.log(zoneId); + console.log('$select.data("json-obj")') + console.log($select.data('json-obj')); + console.log('$select') + console.log($select); + console.log('$select.data()') + console.log($select.data()); + console.log('$select.val()') + console.log($select.val()); + console.log('attr json-attr'); + console.log($select.attr('json-attr')); + return args.parentFilter($select.data('json-obj'), zoneId); + })); + }; + + var filterClusterList = function(podId) { + var $selects = $step.find('.select-deployment .clusterid'); + var $visibleSelects = $($.grep($selects, function(select) { + console.log('---grep cluster list---') + var $select = $(select); + console.log('$select.data("json-obj")') + console.log($select.data('json-obj')); + return args.parentFilter($select.data('json-obj'), podId); + })); + }; + + var filterHostList = function(clusterId) { + + hosts = args.fetchHostList(this, clusterId); + + var hostcallback = function(data) { + console.log('host callback'); + }; + + return { + response: { + success: function(args) { + $(args.data.hosts).each(function() { + $step.find('.select-deployment .hostid').append( + $('