CLOUDSTACK-4168 Root Admin should be able to create 'ExplicitDedication' affinity group at domain level and make it available for all accounts in the domain

Changes:
- 'ExcplicitDedication' type of group can be created/deleted by Root admin only
- Users can no longer create this type of affinity group
- RootAdmin can create this type of affinitygroup at domain level. Such a domain level group is available for all accounts in that domain for listing and for use during deployVM.
- The domain level affinitygroup should be visible to the users in that domain, domain admins and Root admin.

Conflicts:

	server/src/com/cloud/api/query/QueryManagerImpl.java
	server/src/org/apache/cloudstack/affinity/AffinityGroupServiceImpl.java
	server/test/org/apache/cloudstack/affinity/AffinityApiUnitTest.java
This commit is contained in:
Prachi Damle 2013-09-03 13:06:56 -07:00
parent 495259132c
commit a06bd9fa2b
21 changed files with 634 additions and 46 deletions

View File

@ -28,4 +28,6 @@ public interface AffinityGroup extends ControlledEntity, InternalIdentity, Ident
String getType();
ACLType getAclType();
}

View File

@ -58,4 +58,23 @@ public interface AffinityGroupProcessor extends Adapter {
*/
boolean check(VirtualMachineProfile vm, DeployDestination plannedDestination)
throws AffinityConflictException;
/**
* isAdminControlledGroup() should return true if the affinity/anti-affinity
* group can only be operated on[create/delete/modify] by the Admin
*
* @return boolean true/false
*/
boolean isAdminControlledGroup();
/**
* canBeSharedDomainWide() should return true if the affinity/anti-affinity
* group can be created for a domain and shared by all accounts under the
* domain.
*
* @return boolean true/false
*/
boolean canBeSharedDomainWide();
}

View File

@ -75,4 +75,8 @@ public interface AffinityGroupService {
boolean isAffinityGroupProcessorAvailable(String affinityGroupType);
boolean isAdminControlledGroup(AffinityGroup group);
boolean isAffinityGroupAvailableInDomain(long affinityGroupId, long domainId);
}

View File

@ -47,4 +47,14 @@ public class AffinityProcessorBase extends AdapterBase implements AffinityGroupP
throws AffinityConflictException {
return true;
}
@Override
public boolean isAdminControlledGroup() {
return false;
}
@Override
public boolean canBeSharedDomainWide() {
return false;
}
}

View File

@ -377,6 +377,7 @@
<bean id="StaticRoleBasedAPIAccessChecker" class="org.apache.cloudstack.acl.StaticRoleBasedAPIAccessChecker"/>
<bean id="databaseIntegrityChecker" class="com.cloud.upgrade.DatabaseIntegrityChecker" />
<bean id="domainChecker" class="com.cloud.acl.DomainChecker" />
<bean id="affinityGroupAccessChecker" class="com.cloud.acl.AffinityGroupAccessChecker" />
<!--
Authenticators
@ -929,6 +930,8 @@
</bean>
<bean id="AffinityGroupVMMapDaoImpl" class="org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDaoImpl">
</bean>
<bean id="AffinityGroupDomainMapDaoImpl" class="org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDaoImpl">
</bean>
<bean id="PlannerHostReservationDaoImpl" class="com.cloud.deploy.dao.PlannerHostReservationDaoImpl">
</bean>

View File

@ -146,6 +146,7 @@
<bean id="securityCheckers" class="com.cloud.utils.component.AdapterList">
<property name="Adapters">
<list>
<ref bean="affinityGroupAccessChecker"/>
<ref bean="domainChecker"/>
</list>
</property>

View File

@ -65,7 +65,7 @@ public class AccountVO implements Account {
@Column(name="default_zone_id")
private Long defaultZoneId = null;
@Column(name = "default")
boolean isDefault;
@ -77,7 +77,7 @@ public class AccountVO implements Account {
this.id = id;
this.uuid = UUID.randomUUID().toString();
}
public AccountVO(String accountName, long domainId, String networkDomain, short type, String uuid) {
this.accountName = accountName;
this.domainId = domainId;
@ -161,7 +161,7 @@ public class AccountVO implements Account {
@Override
public String toString() {
return new StringBuilder("Acct[").append(id).append("-").append(accountName).append("]").toString();
return new StringBuilder("Acct[").append(uuid).append("-").append(accountName).append("]").toString();
}
@Override

View File

@ -0,0 +1,73 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.affinity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import com.cloud.domain.PartOf;
import org.apache.cloudstack.api.InternalIdentity;
@Entity
@Table(name = "affinity_group_domain_map")
public class AffinityGroupDomainMapVO implements PartOf, InternalIdentity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long id;
@Column(name = "domain_id")
long domainId;
@Column(name = "affinity_group_id")
private long affinityGroupId;
@Column(name = "subdomain_access")
public Boolean subdomainAccess;
protected AffinityGroupDomainMapVO() {
}
public AffinityGroupDomainMapVO(long affinityGroupId, long domainId, Boolean subdomainAccess) {
this.affinityGroupId = affinityGroupId;
this.domainId = domainId;
this.subdomainAccess = subdomainAccess;
}
@Override
public long getId() {
return id;
}
@Override
public long getDomainId() {
return domainId;
}
public long getAffinityGroupId() {
return affinityGroupId;
}
public Boolean isSubdomainAccess() {
return subdomainAccess;
}
}

View File

@ -20,11 +20,14 @@ import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.cloudstack.acl.ControlledEntity;
@Entity
@Table(name = ("affinity_group"))
@ -52,17 +55,22 @@ public class AffinityGroupVO implements AffinityGroup {
@Column(name = "uuid")
private String uuid;
@Column(name = "acl_type")
@Enumerated(value = EnumType.STRING)
ControlledEntity.ACLType aclType;
public AffinityGroupVO() {
this.uuid = UUID.randomUUID().toString();
}
public AffinityGroupVO(String name, String type, String description, long domainId, long accountId) {
public AffinityGroupVO(String name, String type, String description, long domainId, long accountId, ACLType aclType) {
this.name = name;
this.description = description;
this.domainId = domainId;
this.accountId = accountId;
this.uuid = UUID.randomUUID().toString();
this.type = type;
this.aclType = aclType;
}
@Override
@ -104,6 +112,11 @@ public class AffinityGroupVO implements AffinityGroup {
return type;
}
@Override
public ControlledEntity.ACLType getAclType() {
return aclType;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder("AffinityGroup[");

View File

@ -0,0 +1,31 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.affinity.dao;
import java.util.List;
import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
import com.cloud.utils.db.GenericDao;
public interface AffinityGroupDomainMapDao extends GenericDao<AffinityGroupDomainMapVO, Long> {
AffinityGroupDomainMapVO findByAffinityGroup(long affinityGroupId);
List<AffinityGroupDomainMapVO> listByDomain(Object... domainId);
}

View File

@ -0,0 +1,67 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.affinity.dao;
import java.util.List;
import javax.annotation.PostConstruct;
import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.SearchCriteria.Op;
public class AffinityGroupDomainMapDaoImpl extends GenericDaoBase<AffinityGroupDomainMapVO, Long> implements
AffinityGroupDomainMapDao {
private SearchBuilder<AffinityGroupDomainMapVO> ListByAffinityGroup;
private SearchBuilder<AffinityGroupDomainMapVO> DomainsSearch;
public AffinityGroupDomainMapDaoImpl() {
}
@PostConstruct
protected void init() {
ListByAffinityGroup = createSearchBuilder();
ListByAffinityGroup.and("affinityGroupId", ListByAffinityGroup.entity().getAffinityGroupId(),
SearchCriteria.Op.EQ);
ListByAffinityGroup.done();
DomainsSearch = createSearchBuilder();
DomainsSearch.and("domainId", DomainsSearch.entity().getDomainId(), Op.IN);
DomainsSearch.done();
}
@Override
public AffinityGroupDomainMapVO findByAffinityGroup(long affinityGroupId) {
SearchCriteria<AffinityGroupDomainMapVO> sc = ListByAffinityGroup.create();
sc.setParameters("affinityGroupId", affinityGroupId);
return findOneBy(sc);
}
@Override
public List<AffinityGroupDomainMapVO> listByDomain(Object... domainId) {
SearchCriteria<AffinityGroupDomainMapVO> sc = DomainsSearch.create();
sc.setParameters("domainId", (Object[]) domainId);
return listBy(sc);
}
}

View File

@ -381,4 +381,13 @@ public class ExplicitDedicationProcessor extends AffinityProcessorBase implement
return domainIds;
}
@Override
public boolean isAdminControlledGroup() {
return true;
}
@Override
public boolean canBeSharedDomainWide() {
return true;
}
}

View File

@ -0,0 +1,80 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package com.cloud.acl;
import javax.ejb.Local;
import javax.inject.Inject;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupService;
import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao;
import org.springframework.stereotype.Component;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.DomainManager;
@Component
@Local(value = SecurityChecker.class)
public class AffinityGroupAccessChecker extends DomainChecker {
@Inject
AffinityGroupService _affinityGroupService;
@Inject
AccountManager _accountMgr;
@Inject
AffinityGroupDomainMapDao _affinityGroupDomainMapDao;
@Override
public boolean checkAccess(Account caller, ControlledEntity entity, AccessType accessType)
throws PermissionDeniedException {
if (entity instanceof AffinityGroup) {
AffinityGroup group = (AffinityGroup) entity;
if (_affinityGroupService.isAdminControlledGroup(group)) {
if (accessType != null && accessType == AccessType.ModifyEntry
&& !_accountMgr.isRootAdmin(caller.getType())) {
throw new PermissionDeniedException(caller + " does not have permission to operate with resource "
+ entity);
}
}
if (group.getAclType() == ACLType.Domain) {
if (!_affinityGroupService.isAffinityGroupAvailableInDomain(group.getId(), caller.getDomainId())) {
throw new PermissionDeniedException("Affinity group is not available in domain id="
+ caller.getDomainId());
} else {
return true;
}
} else {
//acl_type account
if (caller.getId() != group.getAccountId()) {
throw new PermissionDeniedException(caller
+ " does not have permission to operate with resource " + entity);
}
}
}
return false;
}
}

View File

@ -21,6 +21,7 @@ import javax.inject.Inject;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.api.BaseCmd;
import org.springframework.stereotype.Component;
@ -121,6 +122,8 @@ public class DomainChecker extends AdapterBase implements SecurityChecker {
return true;
} else if (entity instanceof Network && accessType != null && accessType == AccessType.UseNetwork) {
_networkMgr.checkNetworkPermissions(caller, (Network) entity);
} else if (entity instanceof AffinityGroup) {
return false;
} else {
if (caller.getType() == Account.ACCOUNT_TYPE_NORMAL) {
Account account = _accountDao.findById(entity.getAccountId());

View File

@ -29,9 +29,11 @@ import javax.inject.Inject;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.affinity.AffinityGroupVMMapVO;
import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao;
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd;
import org.apache.cloudstack.api.command.admin.host.ListHostsCmd;
@ -137,6 +139,8 @@ import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.ha.HighAvailabilityManager;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.network.dao.NetworkDomainVO;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.security.SecurityGroupVMMapVO;
import com.cloud.network.security.dao.SecurityGroupVMMapDao;
import com.cloud.org.Grouping;
@ -168,6 +172,8 @@ import com.cloud.template.VirtualMachineTemplate.TemplateFilter;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.DomainManager;
import com.cloud.user.UserContext;
import com.cloud.user.dao.AccountDao;
import com.cloud.utils.DateUtil;
import com.cloud.utils.Pair;
@ -316,6 +322,12 @@ public class QueryManagerImpl extends ManagerBase implements QueryService {
@Inject
private DedicatedResourceDao _dedicatedDao;
@Inject
DomainManager _domainMgr;
@Inject
AffinityGroupDomainMapDao _affinityGroupDomainMapDao;
/*
* (non-Javadoc)
*
@ -3019,6 +3031,58 @@ public class QueryManagerImpl extends ManagerBase implements QueryService {
ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
Filter searchFilter = new Filter(AffinityGroupJoinVO.class, "id", true, startIndex, pageSize);
SearchCriteria<AffinityGroupJoinVO> sc = buildAffinityGroupSearchCriteria(domainId, isRecursive,
permittedAccounts, listProjectResourcesCriteria, affinityGroupId, affinityGroupName, affinityGroupType);
Pair<List<AffinityGroupJoinVO>, Integer> uniqueGroupsPair = _affinityGroupJoinDao.searchAndCount(sc,
searchFilter);
// search group details by ids
List<AffinityGroupJoinVO> vrs = new ArrayList<AffinityGroupJoinVO>();
Integer count = uniqueGroupsPair.second();
if (count.intValue() != 0) {
List<AffinityGroupJoinVO> uniqueGroups = uniqueGroupsPair.first();
Long[] vrIds = new Long[uniqueGroups.size()];
int i = 0;
for (AffinityGroupJoinVO v : uniqueGroups) {
vrIds[i++] = v.getId();
}
vrs = _affinityGroupJoinDao.searchByIds(vrIds);
}
if (!permittedAccounts.isEmpty()) {
// add domain level affinity groups
if (domainId != null) {
SearchCriteria<AffinityGroupJoinVO> scDomain = buildAffinityGroupSearchCriteria(null, isRecursive,
new ArrayList<Long>(), listProjectResourcesCriteria, affinityGroupId, affinityGroupName,
affinityGroupType);
vrs.addAll(listDomainLevelAffinityGroups(scDomain, searchFilter, domainId));
} else {
for (Long permAcctId : permittedAccounts) {
Account permittedAcct = _accountDao.findById(permAcctId);
SearchCriteria<AffinityGroupJoinVO> scDomain = buildAffinityGroupSearchCriteria(
null, isRecursive, new ArrayList<Long>(),
listProjectResourcesCriteria, affinityGroupId, affinityGroupName, affinityGroupType);
vrs.addAll(listDomainLevelAffinityGroups(scDomain, searchFilter, permittedAcct.getDomainId()));
}
}
} else if (((permittedAccounts.isEmpty()) && (domainId != null) && isRecursive)) {
// list all domain level affinity groups for the domain admin case
SearchCriteria<AffinityGroupJoinVO> scDomain = buildAffinityGroupSearchCriteria(null, isRecursive,
new ArrayList<Long>(), listProjectResourcesCriteria, affinityGroupId, affinityGroupName,
affinityGroupType);
vrs.addAll(listDomainLevelAffinityGroups(scDomain, searchFilter, domainId));
}
return new Pair<List<AffinityGroupJoinVO>, Integer>(vrs, vrs.size());
}
private SearchCriteria<AffinityGroupJoinVO> buildAffinityGroupSearchCriteria(Long domainId, boolean isRecursive,
List<Long> permittedAccounts, ListProjectResourcesCriteria listProjectResourcesCriteria,
Long affinityGroupId, String affinityGroupName, String affinityGroupType) {
SearchBuilder<AffinityGroupJoinVO> groupSearch = _affinityGroupJoinDao.createSearchBuilder();
_accountMgr.buildACLViewSearchBuilder(groupSearch, domainId, isRecursive, permittedAccounts,
listProjectResourcesCriteria);
@ -3042,22 +3106,7 @@ public class QueryManagerImpl extends ManagerBase implements QueryService {
sc.addAnd("type", SearchCriteria.Op.EQ, affinityGroupType);
}
Pair<List<AffinityGroupJoinVO>, Integer> uniqueGroupsPair = _affinityGroupJoinDao.searchAndCount(sc,
searchFilter);
// search group details by ids
Integer count = uniqueGroupsPair.second();
if (count.intValue() == 0) {
// empty result
return uniqueGroupsPair;
}
List<AffinityGroupJoinVO> uniqueGroups = uniqueGroupsPair.first();
Long[] vrIds = new Long[uniqueGroups.size()];
int i = 0;
for (AffinityGroupJoinVO v : uniqueGroups) {
vrIds[i++] = v.getId();
}
List<AffinityGroupJoinVO> vrs = _affinityGroupJoinDao.searchByIds(vrIds);
return new Pair<List<AffinityGroupJoinVO>, Integer>(vrs, count);
return sc;
}
@ -3079,6 +3128,47 @@ public class QueryManagerImpl extends ManagerBase implements QueryService {
return new Pair<List<AffinityGroupJoinVO>, Integer>(ags, count);
}
private List<AffinityGroupJoinVO> listDomainLevelAffinityGroups(
SearchCriteria<AffinityGroupJoinVO> sc, Filter searchFilter, long domainId) {
List<Long> affinityGroupIds = new ArrayList<Long>();
Set<Long> allowedDomains = _domainMgr.getDomainParentIds(domainId);
List<AffinityGroupDomainMapVO> maps = _affinityGroupDomainMapDao.listByDomain(allowedDomains.toArray());
for (AffinityGroupDomainMapVO map : maps) {
boolean subdomainAccess = map.isSubdomainAccess();
if (map.getDomainId() == domainId || subdomainAccess) {
affinityGroupIds.add(map.getAffinityGroupId());
}
}
if (!affinityGroupIds.isEmpty()) {
SearchCriteria<AffinityGroupJoinVO> domainSC = _affinityGroupJoinDao.createSearchCriteria();
domainSC.addAnd("id", SearchCriteria.Op.IN, affinityGroupIds.toArray());
domainSC.addAnd("aclType", SearchCriteria.Op.EQ, ACLType.Domain.toString());
sc.addAnd("id", SearchCriteria.Op.SC, domainSC);
Pair<List<AffinityGroupJoinVO>, Integer> uniqueGroupsPair = _affinityGroupJoinDao.searchAndCount(sc,
searchFilter);
// search group by ids
Integer count = uniqueGroupsPair.second();
if (count.intValue() == 0) {
// empty result
return new ArrayList<AffinityGroupJoinVO>();
}
List<AffinityGroupJoinVO> uniqueGroups = uniqueGroupsPair.first();
Long[] vrIds = new Long[uniqueGroups.size()];
int i = 0;
for (AffinityGroupJoinVO v : uniqueGroups) {
vrIds[i++] = v.getId();
}
List<AffinityGroupJoinVO> vrs = _affinityGroupJoinDao.searchByIds(vrIds);
return vrs;
} else {
return new ArrayList<AffinityGroupJoinVO>();
}
}
@Override
public List<ResourceDetailResponse> listResource(ListResourceDetailsCmd cmd) {

View File

@ -23,6 +23,8 @@ import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.cloudstack.acl.ControlledEntity;
import com.cloud.vm.VirtualMachine;
@Entity
@ -85,6 +87,10 @@ public class AffinityGroupJoinVO extends BaseViewVO implements ControlledViewEnt
@Enumerated(value = EnumType.STRING)
protected VirtualMachine.State vmState = null;
@Column(name = "acl_type")
@Enumerated(value = EnumType.STRING)
ControlledEntity.ACLType aclType;
public AffinityGroupJoinVO() {
}
@ -256,4 +262,14 @@ public class AffinityGroupJoinVO extends BaseViewVO implements ControlledViewEnt
// TODO Auto-generated method stub
return null;
}
public ControlledEntity.ACLType getAclType() {
return aclType;
}
public void setAclType(ControlledEntity.ACLType aclType) {
this.aclType = aclType;
}
}

View File

@ -44,6 +44,7 @@ import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
@ -395,7 +396,9 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
Account account = ApiDBUtils.findAccountById(entity.getAccountId());
domainId = account != null ? account.getDomainId() : -1;
}
if (entity.getAccountId() != -1 && domainId != -1 && !(entity instanceof VirtualMachineTemplate) && !(accessType != null && accessType == AccessType.UseNetwork)) {
if (entity.getAccountId() != -1 && domainId != -1 && !(entity instanceof VirtualMachineTemplate)
&& !(accessType != null && accessType == AccessType.UseNetwork)
&& !(entity instanceof AffinityGroup)) {
List<ControlledEntity> toBeChecked = domains.get(entity.getDomainId());
// for templates, we don't have to do cross domains check
if (toBeChecked == null) {

View File

@ -2572,13 +2572,27 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
+ " ,type: " + ag.getType() + " , Please try again after removing the affinity group");
} else {
// verify permissions
_accountMgr.checkAccess(caller, null, true, owner, ag);
// Root admin has access to both VM and AG by default, but
// make sure the owner of these entities is same
if (caller.getId() == Account.ACCOUNT_ID_SYSTEM || _accountMgr.isRootAdmin(caller.getType())) {
if (ag.getAccountId() != owner.getAccountId()) {
throw new PermissionDeniedException("Affinity Group " + ag
+ " does not belong to the VM's account");
if (ag.getAclType() == ACLType.Domain) {
_accountMgr.checkAccess(caller, null, false, owner, ag);
// Root admin has access to both VM and AG by default,
// but
// make sure the owner of these entities is same
if (caller.getId() == Account.ACCOUNT_ID_SYSTEM || _accountMgr.isRootAdmin(caller.getType())) {
if (!_affinityGroupService.isAffinityGroupAvailableInDomain(ag.getId(), owner.getDomainId())) {
throw new PermissionDeniedException("Affinity Group " + ag
+ " does not belong to the VM's domain");
}
}
} else {
_accountMgr.checkAccess(caller, null, true, owner, ag);
// Root admin has access to both VM and AG by default,
// but
// make sure the owner of these entities is same
if (caller.getId() == Account.ACCOUNT_ID_SYSTEM || _accountMgr.isRootAdmin(caller.getType())) {
if (ag.getAccountId() != owner.getAccountId()) {
throw new PermissionDeniedException("Affinity Group " + ag
+ " does not belong to the VM's account");
}
}
}
}

View File

@ -20,13 +20,18 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javax.ejb.Local;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao;
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
import org.apache.cloudstack.context.CallContext;
@ -36,14 +41,20 @@ import org.springframework.context.annotation.Primary;
import com.cloud.deploy.DeploymentPlanner;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.ResourceInUseException;
import com.cloud.network.Network;
import com.cloud.network.dao.NetworkDomainVO;
import com.cloud.network.security.SecurityGroup;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.DomainManager;
import com.cloud.user.UserContext;
import com.cloud.uservm.UserVm;
import com.cloud.utils.Pair;
import com.cloud.utils.component.ComponentContext;
@ -79,9 +90,18 @@ public class AffinityGroupServiceImpl extends ManagerBase implements AffinityGro
@Inject
AffinityGroupVMMapDao _affinityGroupVMMapDao;
@Inject
AffinityGroupDomainMapDao _affinityGroupDomainMapDao;
@Inject
private UserVmDao _userVmDao;
@Inject
DomainDao _domainDao;
@Inject
DomainManager _domainMgr;
protected List<AffinityGroupProcessor> _affinityProcessors;
public List<AffinityGroupProcessor> getAffinityGroupProcessors() {
@ -92,20 +112,13 @@ public class AffinityGroupServiceImpl extends ManagerBase implements AffinityGro
this._affinityProcessors = affinityProcessors;
}
@DB
@Override
@ActionEvent(eventType = EventTypes.EVENT_AFFINITY_GROUP_CREATE, eventDescription = "Creating Affinity Group", create = true)
public AffinityGroup createAffinityGroup(String account, Long domainId, String affinityGroupName,
String affinityGroupType, String description) {
Account caller = CallContext.current().getCallingAccount();
Account owner = _accountMgr.finalizeOwner(caller, account, domainId, null);
if (_affinityGroupDao.isNameInUse(owner.getAccountId(), owner.getDomainId(), affinityGroupName)) {
throw new InvalidParameterValueException("Unable to create affinity group, a group with name "
+ affinityGroupName
+ " already exisits.");
}
Account caller = UserContext.current().getCaller();
//validate the affinityGroupType
Map<String, AffinityGroupProcessor> typeProcessorMap = getAffinityTypeToProcessorMap();
@ -119,14 +132,65 @@ public class AffinityGroupServiceImpl extends ManagerBase implements AffinityGro
"Unable to create affinity group, no Affinity Group Types configured");
}
if (domainId == null) {
domainId = owner.getDomainId();
AffinityGroupProcessor processor = typeProcessorMap.get(affinityGroupType);
if (processor.isAdminControlledGroup() && !_accountMgr.isRootAdmin(caller.getType())) {
throw new PermissionDeniedException("Cannot create the affinity group");
}
AffinityGroupVO group = new AffinityGroupVO(affinityGroupName, affinityGroupType, description, domainId,
owner.getId());
ControlledEntity.ACLType aclType = null;
Account owner = null;
if (account != null && domainId != null) {
owner = _accountMgr.finalizeOwner(caller, account, domainId, null);
aclType = ControlledEntity.ACLType.Account;
} else if (domainId != null && account == null) {
if (!_accountMgr.isRootAdmin(caller.getType())) {
// non root admin need to pass both account and domain
throw new InvalidParameterValueException(
"Unable to create affinity group, account name must be passed with the domainId");
} else if (!processor.canBeSharedDomainWide()) {
// cannot be domain level
throw new InvalidParameterValueException("Unable to create affinity group, account name is needed");
}
DomainVO domain = _domainDao.findById(domainId);
if (domain == null) {
throw new InvalidParameterValueException("Unable to find domain by specified id");
}
_accountMgr.checkAccess(caller, domain);
// domain level group, owner is SYSTEM.
owner = _accountMgr.getAccount(Account.ACCOUNT_ID_SYSTEM);
aclType = ControlledEntity.ACLType.Domain;
} else {
owner = caller;
aclType = ControlledEntity.ACLType.Account;
}
if (_affinityGroupDao.isNameInUse(owner.getAccountId(), owner.getDomainId(), affinityGroupName)) {
throw new InvalidParameterValueException("Unable to create affinity group, a group with name "
+ affinityGroupName + " already exisits.");
}
Transaction txn = Transaction.currentTxn();
txn.start();
AffinityGroupVO group = new AffinityGroupVO(affinityGroupName, affinityGroupType, description, owner.getDomainId(),
owner.getId(), aclType);
_affinityGroupDao.persist(group);
if (domainId != null && aclType == ACLType.Domain) {
AffinityGroupDomainMapVO domainMap = new AffinityGroupDomainMapVO(group.getId(), domainId, false);
_affinityGroupDomainMapDao.persist(domainMap);
}
txn.commit();
if (s_logger.isDebugEnabled()) {
s_logger.debug("Created affinity group =" + affinityGroupName);
}
@ -134,6 +198,7 @@ public class AffinityGroupServiceImpl extends ManagerBase implements AffinityGro
return group;
}
@DB
@Override
@ActionEvent(eventType = EventTypes.EVENT_AFFINITY_GROUP_DELETE, eventDescription = "Deleting affinity group")
@ -163,7 +228,7 @@ public class AffinityGroupServiceImpl extends ManagerBase implements AffinityGro
affinityGroupId = group.getId();
}
// check permissions
_accountMgr.checkAccess(caller, null, true, group);
_accountMgr.checkAccess(caller, AccessType.ModifyEntry, true, group);
final Transaction txn = Transaction.currentTxn();
txn.start();
@ -251,12 +316,18 @@ public class AffinityGroupServiceImpl extends ManagerBase implements AffinityGro
@Override
public List<String> listAffinityGroupTypes() {
Account caller = UserContext.current().getCaller();
List<String> types = new ArrayList<String>();
Map<String, AffinityGroupProcessor> componentMap = ComponentContext.getComponentsOfType(AffinityGroupProcessor.class);
if (componentMap.size() > 0) {
for (Entry<String, AffinityGroupProcessor> entry : componentMap.entrySet()) {
types.add(entry.getValue().getType());
AffinityGroupProcessor processor = entry.getValue();
if (processor.isAdminControlledGroup() && !_accountMgr.isRootAdmin(caller.getType())) {
continue;
}
types.add(processor.getType());
}
}
@ -276,6 +347,23 @@ public class AffinityGroupServiceImpl extends ManagerBase implements AffinityGro
return typeProcessorMap;
}
@Override
public boolean isAdminControlledGroup(AffinityGroup group) {
if (group != null) {
String affinityGroupType = group.getType();
Map<String, AffinityGroupProcessor> typeProcessorMap = getAffinityTypeToProcessorMap();
if (typeProcessorMap != null && !typeProcessorMap.isEmpty()) {
AffinityGroupProcessor processor = typeProcessorMap.get(affinityGroupType);
if (processor != null) {
return processor.isAdminControlledGroup();
}
}
}
return false;
}
@Override
public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
_name = name;
@ -381,4 +469,29 @@ public class AffinityGroupServiceImpl extends ManagerBase implements AffinityGro
return false;
}
@Override
public boolean isAffinityGroupAvailableInDomain(long affinityGroupId, long domainId) {
Long groupDomainId = null;
AffinityGroupDomainMapVO domainMap = _affinityGroupDomainMapDao.findByAffinityGroup(affinityGroupId);
if (domainMap == null) {
return false;
} else {
groupDomainId = domainMap.getDomainId();
}
if (domainId == groupDomainId.longValue()) {
return true;
}
if (domainMap.subdomainAccess) {
Set<Long> parentDomains = _domainMgr.getDomainParentIds(domainId);
if (parentDomains.contains(groupDomainId)) {
return true;
}
}
return false;
}
}

View File

@ -33,6 +33,11 @@ import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.junit.After;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao;
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
import org.apache.cloudstack.test.utils.SpringUtils;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@ -57,6 +62,9 @@ import org.apache.cloudstack.test.utils.SpringUtils;
import com.cloud.dc.dao.DedicatedResourceDao;
import com.cloud.event.ActionEventUtils;
import com.cloud.dc.dao.DedicatedResourceDao;
import com.cloud.domain.dao.DomainDao;
import com.cloud.event.ActionEventUtils;
import com.cloud.event.EventVO;
import com.cloud.event.dao.EventDao;
import com.cloud.exception.InvalidParameterValueException;
@ -67,6 +75,8 @@ import com.cloud.user.AccountManager;
import com.cloud.user.AccountService;
import com.cloud.user.AccountVO;
import com.cloud.user.UserVO;
import com.cloud.user.DomainManager;
import com.cloud.user.UserContext;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.UserDao;
import com.cloud.utils.component.ComponentContext;
@ -135,7 +145,8 @@ public class AffinityApiUnitTest {
when(_processor.getType()).thenReturn("mock");
when(_accountDao.findByIdIncludingRemoved(0L)).thenReturn(acct);
AffinityGroupVO group = new AffinityGroupVO("group1", "mock", "mock group", domainId, 200L);
AffinityGroupVO group = new AffinityGroupVO("group1", "mock", "mock group", domainId, 200L,
ControlledEntity.ACLType.Account);
Mockito.when(_affinityGroupDao.persist(Mockito.any(AffinityGroupVO.class))).thenReturn(group);
Mockito.when(_affinityGroupDao.findById(Mockito.anyLong())).thenReturn(group);
Mockito.when(_affinityGroupDao.findByAccountAndName(Mockito.anyLong(), Mockito.anyString())).thenReturn(group);
@ -242,6 +253,11 @@ public class AffinityApiUnitTest {
return Mockito.mock(AccountManager.class);
}
@Bean
public DomainManager domainManager() {
return Mockito.mock(DomainManager.class);
}
@Bean
public EventDao eventDao() {
return Mockito.mock(EventDao.class);
@ -255,6 +271,16 @@ public class AffinityApiUnitTest {
@Bean
public UserDao userDao() {
return Mockito.mock(UserDao.class);
}
@Bean
public AffinityGroupDomainMapDao affinityGroupDomainMapDao() {
return Mockito.mock(AffinityGroupDomainMapDao.class);
}
@Bean
public DomainDao domainDao() {
return Mockito.mock(DomainDao.class);
}
public static class Library implements TypeFilter {

View File

@ -410,6 +410,7 @@ CREATE TABLE `cloud`.`affinity_group` (
`description` varchar(4096) NULL,
`domain_id` bigint unsigned NOT NULL,
`account_id` bigint unsigned NOT NULL,
`acl_type` varchar(15) NOT NULL COMMENT 'ACL access type. can be Account/Domain',
UNIQUE (`name`, `account_id`),
PRIMARY KEY (`id`),
CONSTRAINT `fk_affinity_group__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`),
@ -417,6 +418,7 @@ CREATE TABLE `cloud`.`affinity_group` (
CONSTRAINT `uc_affinity_group__uuid` UNIQUE (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `cloud`.`affinity_group_vm_map` (
`id` bigint unsigned NOT NULL auto_increment,
`affinity_group_id` bigint unsigned NOT NULL,
@ -426,7 +428,15 @@ CREATE TABLE `cloud`.`affinity_group_vm_map` (
CONSTRAINT `fk_affinity_group_vm_map___instance_id` FOREIGN KEY(`instance_id`) REFERENCES `user_vm` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `cloud`.`affinity_group_domain_map` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`domain_id` bigint unsigned NOT NULL COMMENT 'domain id',
`affinity_group_id` bigint unsigned NOT NULL COMMENT 'affinity group id',
`subdomain_access` int(1) unsigned COMMENT '1 if affinity group can be accessible from the subdomain',
PRIMARY KEY (`id`),
CONSTRAINT `fk_affinity_group_domain_map__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
CONSTRAINT `fk_affinity_group_domain_map__affinity_group_id` FOREIGN KEY (`affinity_group_id`) REFERENCES `affinity_group`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `cloud`.`dedicated_resources` (
`id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT COMMENT 'id',
@ -898,6 +908,7 @@ CREATE VIEW `cloud`.`affinity_group_view` AS
affinity_group.type type,
affinity_group.description description,
affinity_group.uuid uuid,
affinity_group.acl_type acl_type,
account.id account_id,
account.uuid account_uuid,
account.account_name account_name,