Merge pull request #844 from ustcweizhou/assignvm-master

[4.10] CLOUDSTACK-7985: assignVM in Advanced zone with Security GroupsThis commit contains the following changes:
(1) implementation of assignVM in Advanced zone with Security Groups
(2) keep the default nic on shared network when assignVM
(3) allow migrate vm from/to project;
(4) UI change for selecting account/project/network

* pr/844:
  CLOUDSTACK-7985: assignVM in Advanced zone with Security Groups
  CLOUDSTACK-7985: keep the default nic on shared network when assignVM
  CLOUDSTACK-7985: (1) allow migrate vm from/to project; (2) UI change for selecting account/project/network

Signed-off-by: Rajani Karuturi <rajani.karuturi@accelerite.com>
This commit is contained in:
Rajani Karuturi 2017-02-09 11:54:39 +05:30
commit e02003d653
7 changed files with 414 additions and 26 deletions

View File

@ -29,9 +29,11 @@ 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;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.Account;
import com.cloud.uservm.UserVm;
import com.cloud.vm.VirtualMachine;
@ -58,12 +60,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 +103,10 @@ public class AssignVMCmd extends BaseCmd {
return domainId;
}
public Long getProjectId() {
return projectId;
}
public List<Long> getNetworkIds() {
return networkIds;
}
@ -125,6 +134,9 @@ public class AssignVMCmd extends BaseCmd {
UserVmResponse response = _responseGenerator.createUserVmResponse(ResponseView.Full, "virtualmachine", userVm).get(0);
response.setResponseName(getCommandName());
setResponseObject(response);
} catch (InvalidParameterValueException e){
e.printStackTrace();
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
} catch (Exception e) {
e.printStackTrace();
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to move vm " + e.getMessage());

View File

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

View File

@ -93,6 +93,7 @@ public class ProjectJoinDaoImpl extends GenericDaoBase<ProjectJoinVO, Long> 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;

View File

@ -5065,14 +5065,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());
}
@ -5263,17 +5257,155 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
s_logger.debug("AssignVM: Basic zone, adding security groups no " + securityGroupIdList.size() + " to " + vm.getInstanceName());
} else {
if (zone.isSecurityGroupEnabled()) {
throw new InvalidParameterValueException("Not yet implemented for SecurityGroupEnabled advanced networks.");
} else {
if (securityGroupIdList != null && !securityGroupIdList.isEmpty()) {
throw new InvalidParameterValueException("Can't move vm with security groups; security group feature is not enabled in this zone");
if (zone.isSecurityGroupEnabled()) { // advanced zone with security groups
// cleanup the old security groups
_securityGroupMgr.removeInstanceFromGroups(cmd.getVmId());
Set<NetworkVO> applicableNetworks = new HashSet<NetworkVO>();
String requestedIPv4ForDefaultNic = null;
String requestedIPv6ForDefaultNic = null;
// if networkIdList is null and the first network of vm is shared network, then keep it if possible
if (networkIdList == null || networkIdList.isEmpty()) {
NicVO defaultNicOld = _nicDao.findDefaultNicForVM(vm.getId());
if (defaultNicOld != null) {
NetworkVO defaultNetworkOld = _networkDao.findById(defaultNicOld.getNetworkId());
if (defaultNetworkOld != null && defaultNetworkOld.getGuestType() == Network.GuestType.Shared && defaultNetworkOld.getAclType() == ACLType.Domain) {
try {
_networkModel.checkNetworkPermissions(newAccount, defaultNetworkOld);
applicableNetworks.add(defaultNetworkOld);
requestedIPv4ForDefaultNic = defaultNicOld.getIPv4Address();
requestedIPv6ForDefaultNic = defaultNicOld.getIPv6Address();
s_logger.debug("AssignVM: use old shared network " + defaultNetworkOld.getName() + " with old ip " + requestedIPv4ForDefaultNic + " on default nic of vm:" + vm.getInstanceName());
} catch (PermissionDeniedException e) {
s_logger.debug("AssignVM: the shared network on old default nic can not be applied to new account");
}
}
}
}
// cleanup the network for the oldOwner
_networkMgr.cleanupNics(vmOldProfile);
_networkMgr.expungeNics(vmOldProfile);
if (networkIdList != null && !networkIdList.isEmpty()) {
// add any additional networks
for (Long networkId : networkIdList) {
NetworkVO network = _networkDao.findById(networkId);
if (network == null) {
InvalidParameterValueException ex = new InvalidParameterValueException(
"Unable to find specified network id");
ex.addProxyObject(networkId.toString(), "networkId");
throw ex;
}
_networkModel.checkNetworkPermissions(newAccount, network);
// don't allow to use system networks
NetworkOffering networkOffering = _entityMgr.findById(NetworkOffering.class, network.getNetworkOfferingId());
if (networkOffering.isSystemOnly()) {
InvalidParameterValueException ex = new InvalidParameterValueException(
"Specified Network id is system only and can't be used for vm deployment");
ex.addProxyObject(network.getUuid(), "networkId");
throw ex;
}
applicableNetworks.add(network);
}
}
// add the new nics
LinkedHashMap<Network, List<? extends NicProfile>> networks = new LinkedHashMap<Network, List<? extends NicProfile>>();
int toggle = 0;
NetworkVO defaultNetwork = null;
for (NetworkVO appNet : applicableNetworks) {
NicProfile defaultNic = new NicProfile();
if (toggle == 0) {
defaultNic.setDefaultNic(true);
defaultNic.setRequestedIPv4(requestedIPv4ForDefaultNic);
defaultNic.setRequestedIPv6(requestedIPv6ForDefaultNic);
defaultNetwork = appNet;
toggle++;
}
networks.put(appNet, new ArrayList<NicProfile>(Arrays.asList(defaultNic)));
}
boolean isVmWare = (template.getHypervisorType() == HypervisorType.VMware);
if (securityGroupIdList != null && isVmWare) {
throw new InvalidParameterValueException("Security group feature is not supported for vmWare hypervisor");
} else if (!isVmWare && (defaultNetwork == null || _networkModel.isSecurityGroupSupportedInNetwork(defaultNetwork)) && _networkModel.canAddDefaultSecurityGroup()) {
if (securityGroupIdList == null) {
securityGroupIdList = new ArrayList<Long>();
}
SecurityGroup defaultGroup = _securityGroupMgr
.getDefaultSecurityGroup(newAccount.getId());
if (defaultGroup != null) {
// check if security group id list already contains Default
// security group, and if not - add it
boolean defaultGroupPresent = false;
for (Long securityGroupId : securityGroupIdList) {
if (securityGroupId.longValue() == defaultGroup.getId()) {
defaultGroupPresent = true;
break;
}
}
if (!defaultGroupPresent) {
securityGroupIdList.add(defaultGroup.getId());
}
} else {
// create default security group for the account
if (s_logger.isDebugEnabled()) {
s_logger.debug("Couldn't find default security group for the account "
+ newAccount + " so creating a new one");
}
defaultGroup = _securityGroupMgr.createSecurityGroup(
SecurityGroupManager.DEFAULT_GROUP_NAME,
SecurityGroupManager.DEFAULT_GROUP_DESCRIPTION,
newAccount.getDomainId(), newAccount.getId(),
newAccount.getAccountName());
securityGroupIdList.add(defaultGroup.getId());
}
}
VirtualMachine vmi = _itMgr.findById(vm.getId());
VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmi);
if (applicableNetworks.isEmpty()) {
throw new InvalidParameterValueException("No network is specified, please specify one when you move the vm. For now, please add a network to VM on NICs tab.");
} else {
_networkMgr.allocate(vmProfile, networks);
}
_securityGroupMgr.addInstanceToGroups(vm.getId(),
securityGroupIdList);
s_logger.debug("AssignVM: Advanced zone, adding security groups no "
+ securityGroupIdList.size() + " to "
+ vm.getInstanceName());
} else {
if (securityGroupIdList != null && !securityGroupIdList.isEmpty()) {
throw new InvalidParameterValueException("Can't move vm with security groups; security group feature is not enabled in this zone");
}
Set<NetworkVO> applicableNetworks = new HashSet<NetworkVO>();
// if networkIdList is null and the first network of vm is shared network, then keep it if possible
if (networkIdList == null || networkIdList.isEmpty()) {
NicVO defaultNicOld = _nicDao.findDefaultNicForVM(vm.getId());
if (defaultNicOld != null) {
NetworkVO defaultNetworkOld = _networkDao.findById(defaultNicOld.getNetworkId());
if (defaultNetworkOld != null && defaultNetworkOld.getGuestType() == Network.GuestType.Shared && defaultNetworkOld.getAclType() == ACLType.Domain) {
try {
_networkModel.checkNetworkPermissions(newAccount, defaultNetworkOld);
applicableNetworks.add(defaultNetworkOld);
} catch (PermissionDeniedException e) {
s_logger.debug("AssignVM: the shared network on old default nic can not be applied to new account");
}
}
}
}
// cleanup the network for the oldOwner
_networkMgr.cleanupNics(vmOldProfile);
_networkMgr.expungeNics(vmOldProfile);
if (networkIdList != null && !networkIdList.isEmpty()) {
// add any additional networks
@ -5296,7 +5428,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
applicableNetworks.add(network);
}
} else {
} else if (applicableNetworks.isEmpty()) {
NetworkVO defaultNetwork = null;
List<NetworkOfferingVO> requiredOfferings = _networkOfferingDao.listByAvailability(Availability.Required, false);
if (requiredOfferings.size() < 1) {
@ -5373,7 +5505,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;
}

View File

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

View File

@ -1909,6 +1909,7 @@ var dictionary = {"ICMP.code":"ICMP Code",
"message.alert.state.detected":"Alert state detected",
"message.allow.vpn.access":"Please enter a username and password of the user that you want to allow VPN access.",
"message.apply.snapshot.policy":"You have successfully updated your current snapshot policy.",
"message.assign.instance.another":"Please specify the account type, domain, account name and network (optional) of the new account. <br> 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. <br> 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.",
"message.attach.iso.confirm":"Please confirm that you want to attach the ISO to this virtual instance.",
"message.attach.volume":"Please fill in the following data to attach a new volume. If you are attaching a disk volume to a Windows based virtual machine, you will need to reboot the instance to see the attached disk.",
"message.basic.mode.desc":"Choose this network model if you do <b>*<u>not</u>*</b> want to enable any VLAN support. All virtual instances created under this network model will be assigned an IP directly from the network and security groups are used to provide security and segregation.",

View File

@ -1910,7 +1910,50 @@
label: 'label.assign.instance.another',
createForm: {
title: 'label.assign.instance.another',
desc: 'message.assign.instance.another',
preFilter: function(args) {
var zone;
$.ajax({
url: createURL('listZones'),
data: {
id: args.context.instances[0].zoneid
},
async: false,
success: function(json) {
zone = json.listzonesresponse.zone[0];
}
});
if (zone.securitygroupsenabled == true) {
args.$form.find('.form-item[rel=securitygroup]').css('display', 'inline-block');
} else {
args.$form.find('.form-item[rel=securitygroup]').hide();
}
},
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: {
@ -1946,25 +1989,216 @@
},
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
});
}
if (args.data.securitygroup != null && args.data.securitygroup != '') {
$.extend(dataObj, {
securitygroupIds: args.data.securitygroup
});
}
$.ajax({
url: createURL('assignVirtualMachine', {
ignoreProject: ignoreProject
}),
data: dataObj,
success: function(json) {
var item = json.assignvirtualmachineresponse.virtualmachine;
args.response.success({
data: item
});
},
error: function(data) {
args.response.error(parseXMLHttpResponse(data));
}
});
},