diff --git a/api/src/org/apache/cloudstack/api/command/admin/vm/AssignVMCmd.java b/api/src/org/apache/cloudstack/api/command/admin/vm/AssignVMCmd.java index 435b7f18484..96ded2680f3 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/vm/AssignVMCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/vm/AssignVMCmd.java @@ -29,6 +29,7 @@ import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.NetworkResponse; +import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.UserVmResponse; @@ -58,12 +59,15 @@ public class AssignVMCmd extends BaseCmd { description = "id of the VM to be moved") private Long virtualMachineId; - @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = true, description = "account name of the new VM owner.") + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "account name of the new VM owner.") private String accountName; - @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, required = true, description = "domain id of the new VM owner.") + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "domain id of the new VM owner.") private Long domainId; + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "an optional project for the new VM owner.") + private Long projectId; + //Network information @Parameter(name = ApiConstants.NETWORK_IDS, type = CommandType.LIST, @@ -98,6 +102,10 @@ public class AssignVMCmd extends BaseCmd { return domainId; } + public Long getProjectId() { + return projectId; + } + public List getNetworkIds() { return networkIds; } diff --git a/api/src/org/apache/cloudstack/api/response/ProjectResponse.java b/api/src/org/apache/cloudstack/api/response/ProjectResponse.java index ad29d2b18cb..0ae9e18612e 100644 --- a/api/src/org/apache/cloudstack/api/response/ProjectResponse.java +++ b/api/src/org/apache/cloudstack/api/response/ProjectResponse.java @@ -56,6 +56,10 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou @Param(description = "the account name of the project's owner") private String ownerName; + @SerializedName("projectaccountname") + @Param(description="the project account name of the project") + private String projectAccountName; + @SerializedName(ApiConstants.STATE) @Param(description = "the state of the project") private String state; @@ -228,6 +232,10 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou ownerName = owner; } + public void setProjectAccountName(String projectAccountName) { + this.projectAccountName = projectAccountName; + } + public void setState(String state) { this.state = state; } diff --git a/server/src/com/cloud/api/query/dao/ProjectJoinDaoImpl.java b/server/src/com/cloud/api/query/dao/ProjectJoinDaoImpl.java index 65330ac29da..25598b35b27 100644 --- a/server/src/com/cloud/api/query/dao/ProjectJoinDaoImpl.java +++ b/server/src/com/cloud/api/query/dao/ProjectJoinDaoImpl.java @@ -93,6 +93,7 @@ public class ProjectJoinDaoImpl extends GenericDaoBase impl Account account = _accountDao.findByIdIncludingRemoved(proj.getProjectAccountId()); AccountJoinVO accountJn = ApiDBUtils.newAccountView(account); _accountJoinDao.setResourceLimits(accountJn, false, response); + response.setProjectAccountName(accountJn.getAccountName()); response.setObjectName("project"); return response; diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 453800db31f..6f0d4984502 100644 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -5048,14 +5048,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (oldAccount == null) { throw new InvalidParameterValueException("Invalid account for VM " + vm.getAccountId() + " in domain."); } - // don't allow to move the vm from the project - if (oldAccount.getType() == Account.ACCOUNT_TYPE_PROJECT) { - InvalidParameterValueException ex = new InvalidParameterValueException("Specified Vm id belongs to the project and can't be moved"); - ex.addProxyObject(vm.getUuid(), "vmId"); - throw ex; - } - final Account newAccount = _accountService.getActiveAccountByName(cmd.getAccountName(), cmd.getDomainId()); - if (newAccount == null || newAccount.getType() == Account.ACCOUNT_TYPE_PROJECT) { + final Account newAccount = _accountMgr.finalizeOwner(caller, cmd.getAccountName(), cmd.getDomainId(), cmd.getProjectId()); + if (newAccount == null) { throw new InvalidParameterValueException("Invalid accountid=" + cmd.getAccountName() + " in domain " + cmd.getDomainId()); } @@ -5356,7 +5350,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir s_logger.debug("AssignVM: Advance virtual, adding networks no " + networks.size() + " to " + vm.getInstanceName()); } // END IF NON SEC GRP ENABLED } // END IF ADVANCED - s_logger.info("AssignVM: vm " + vm.getInstanceName() + " now belongs to account " + cmd.getAccountName()); + s_logger.info("AssignVM: vm " + vm.getInstanceName() + " now belongs to account " + newAccount.getAccountName()); return vm; } diff --git a/server/test/com/cloud/vm/UserVmManagerTest.java b/server/test/com/cloud/vm/UserVmManagerTest.java index 9df3f689714..294a93a168b 100644 --- a/server/test/com/cloud/vm/UserVmManagerTest.java +++ b/server/test/com/cloud/vm/UserVmManagerTest.java @@ -690,7 +690,7 @@ public class UserVmManagerTest { when(_accountService.getActiveAccountById(anyLong())).thenReturn(oldAccount); - when(_accountService.getActiveAccountByName(anyString(), anyLong())).thenReturn(newAccount); + when(_accountMgr.finalizeOwner(any(Account.class), anyString(), anyLong(), anyLong())).thenReturn(newAccount); doThrow(new PermissionDeniedException("Access check failed")).when(_accountMgr).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class)); diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index 19db2570296..132801d368e 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -1905,7 +1905,32 @@ label: 'label.assign.instance.another', createForm: { title: 'label.assign.instance.another', + desc: 'Please specify the account type, domain, account name and network (optional) of the new account.
If the default nic of the vm is on a shared network, CloudStack will check if the network can be used by the new account if you do not specify one network.
If the default nic of the vm is on a isolated network, and the new account has more one isolated networks, you should specify one.', fields: { + accountType: { + label: 'Account Type', + select: function(args) { + var items = []; + items.push({id: 'account', description: 'Account'}); + items.push({id: 'project', description: 'Project'}); + args.response.success({data: items}); + + args.$select.change(function() { + var $form = $(this).closest('form'); + var $account = $form.find('.form-item[rel=account]'); + var $project = $form.find('.form-item[rel=project]'); + + var accountType = $(this).val(); + if (accountType == 'account') { // Account + $account.css('display', 'inline-block'); + $project.hide(); + } else if (accountType == 'project') { // Project + $project.css('display', 'inline-block'); + $account.hide(); + } + }); + } + }, domainid: { label: 'label.domain', validation: { @@ -1941,20 +1966,203 @@ }, account: { label: 'label.account', + dependsOn: 'domainid', validation: { required: true - } - } + }, + select: function(args) { + var dataObj = { + domainId: args.domainid, + state: 'Enabled', + listAll: true, + }; + $.ajax({ + url: createURL('listAccounts', { + ignoreProject: true + }), + data: dataObj, + success: function(json) { + accountObjs = json.listaccountsresponse.account; + var items = [{ + id: null, + description: '' + }]; + $(accountObjs).each(function() { + items.push({ + id: this.name, + description: this.name + }); + }) + + args.response.success({ + data: items + }); + } + }); + }, + }, + project: { + label: 'label.project', + dependsOn: 'domainid', + validation: { + required: true + }, + select: function(args) { + var dataObj = { + domainId: args.domainid, + state: 'Active', + listAll: true, + }; + $.ajax({ + url: createURL('listProjects', { + ignoreProject: true + }), + data: dataObj, + success: function(json) { + projectObjs = json.listprojectsresponse.project; + var items = [{ + id: null, + description: '' + }]; + $(projectObjs).each(function() { + items.push({ + id: this.id, + description: this.name + }); + }) + + args.response.success({ + data: items + }); + } + }); + }, + }, + network: { + label: 'label.network', + dependsOn: ['accountType', 'domainid', 'account', 'project'], + select: function(args) { + var dataObj = { + domainId: args.domainid, + listAll: true, + isrecursive: false + }; + if (args.data.accountType == 'account' && args.data.account != null && args.data.account != '') { + $.extend(dataObj, { + account: args.data.account + }); + } else if (args.data.accountType == 'project' && args.data.project != null && args.data.project != '') { + $.extend(dataObj, { + projectid: args.data.project + }); + } else { + args.response.success({ + data: null + }); + return; + } + $.ajax({ + url: createURL('listNetworks', { + ignoreProject: true + }), + data: dataObj, + success: function(json) { + var networkObjs = json.listnetworksresponse.network; + var items = [{ + id: null, + description: '' + }]; + $(networkObjs).each(function() { + items.push({ + id: this.id, + description: this.name + }); + }) + + args.response.success({ + data: items + }); + } + }); + }, + }, + securitygroup: { + label: 'label.security.group', + dependsOn: ['accountType', 'domainid', 'account', 'project'], + select: function(args) { + var dataObj = { + domainId: args.domainid, + listAll: true, + isrecursive: false + }; + if (args.data.accountType == 'account' && args.data.account != null && args.data.account != '') { + $.extend(dataObj, { + account: args.data.account + }); + } else if (args.data.accountType == 'project' && args.data.project != null && args.data.project != '') { + $.extend(dataObj, { + projectid: args.data.project + }); + } else { + args.response.success({ + data: null + }); + return; + } + $.ajax({ + url: createURL('listSecurityGroups', { + ignoreProject: true + }), + data: dataObj, + success: function(json) { + var sgObjs = json.listsecuritygroupsresponse.securitygroup; + var items = [{ + id: null, + description: '' + }]; + $(sgObjs).each(function() { + items.push({ + id: this.id, + description: this.name + }); + }) + + args.response.success({ + data: items + }); + } + }); + }, + }, } }, action: function(args) { - $.ajax({ - url: createURL('assignVirtualMachine'), - data: { - virtualmachineid: args.context.instances[0].id, - domainid: args.data.domainid, + var dataObj = { + virtualmachineid: args.context.instances[0].id, + domainid: args.data.domainid, + }; + var ignoreProject = false; + if (args.data.accountType == 'account') { + ignoreProject = true; + $.extend(dataObj, { account: args.data.account - }, + }); + } else if (args.data.accountType == 'project') { + $.extend(dataObj, { + projectid: args.data.project + }); + } + if (args.data.network != null && args.data.network != '') { + $.extend(dataObj, { + networkIds: args.data.network + }); + } + + $.ajax({ + url: createURL('assignVirtualMachine', { + ignoreProject: ignoreProject + }), + data: dataObj, success: function(json) { var item = json.assignvirtualmachineresponse.virtualmachine; args.response.success({