// 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 java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.utils.fsm.StateMachine2; import org.apache.log4j.Logger; 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.api.ApiConstants; import org.apache.cloudstack.api.command.user.affinitygroup.CreateAffinityGroupCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.PublishScope; 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.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.DomainManager; import com.cloud.uservm.UserVm; import com.cloud.utils.Pair; import com.cloud.utils.component.Manager; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.DB; import com.cloud.utils.db.EntityManager; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.TransactionCallbackNoReturn; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.fsm.StateListener; import com.cloud.vm.UserVmVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.Event; import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.UserVmDao; public class AffinityGroupServiceImpl extends ManagerBase implements AffinityGroupService, Manager, StateListener<State, VirtualMachine.Event, VirtualMachine> { public static final Logger s_logger = Logger.getLogger(AffinityGroupServiceImpl.class); private String _name; @Inject AccountManager _accountMgr; @Inject AffinityGroupDao _affinityGroupDao; @Inject AffinityGroupVMMapDao _affinityGroupVMMapDao; @Inject AffinityGroupDomainMapDao _affinityGroupDomainMapDao; @Inject private UserVmDao _userVmDao; @Inject DomainDao _domainDao; @Inject DomainManager _domainMgr; @Inject MessageBus _messageBus; protected List<AffinityGroupProcessor> _affinityProcessors; public List<AffinityGroupProcessor> getAffinityGroupProcessors() { return _affinityProcessors; } public void setAffinityGroupProcessors(List<AffinityGroupProcessor> affinityProcessors) { _affinityProcessors = affinityProcessors; } @DB @Override @ActionEvent(eventType = EventTypes.EVENT_AFFINITY_GROUP_CREATE, eventDescription = "Creating Affinity Group", create = true) public AffinityGroup createAffinityGroup(CreateAffinityGroupCmd createAffinityGroupCmd) { return createAffinityGroup(createAffinityGroupCmd.getAccountName(), createAffinityGroupCmd.getProjectId(), createAffinityGroupCmd.getDomainId(), createAffinityGroupCmd.getAffinityGroupName(), createAffinityGroupCmd.getAffinityGroupType(), createAffinityGroupCmd.getDescription()); } @DB @Override public AffinityGroup createAffinityGroup(final String accountName, final Long projectId, final Long domainId, final String affinityGroupName, final String affinityGroupType, final String description) { // validate the affinityGroupType Map<String, AffinityGroupProcessor> typeProcessorMap = getAffinityTypeToProcessorMap(); if (typeProcessorMap == null || typeProcessorMap.isEmpty()) { throw new InvalidParameterValueException("Unable to create affinity group, no Affinity Group Types configured"); } AffinityGroupProcessor processor = typeProcessorMap.get(affinityGroupType); if(processor == null){ throw new InvalidParameterValueException("Unable to create affinity group, invalid affinity group type" + affinityGroupType); } Account caller = CallContext.current().getCallingAccount(); if (processor.isAdminControlledGroup() && !_accountMgr.isRootAdmin(caller.getId())) { throw new PermissionDeniedException("Cannot create the affinity group"); } ControlledEntity.ACLType aclType = null; Account owner = null; boolean domainLevel = false; if (projectId == null && domainId != null && accountName == null) { verifyAccessToDomainWideProcessor(caller, processor); DomainVO domain = getDomain(domainId); _accountMgr.checkAccess(caller, domain); // domain level group, owner is SYSTEM. owner = _accountMgr.getAccount(Account.ACCOUNT_ID_SYSTEM); aclType = ControlledEntity.ACLType.Domain; domainLevel = true; } else { owner = _accountMgr.finalizeOwner(caller, accountName, domainId, projectId); aclType = ControlledEntity.ACLType.Account; } verifyAffinityGroupNameInUse(owner.getAccountId(), owner.getDomainId(), affinityGroupName); verifyDomainLevelAffinityGroupName(domainLevel, owner.getDomainId(), affinityGroupName); AffinityGroupVO group = createAffinityGroup(processor, owner, aclType, affinityGroupName, affinityGroupType, description); if (s_logger.isDebugEnabled()) { s_logger.debug("Created affinity group =" + affinityGroupName); } return group; } private void verifyAccessToDomainWideProcessor(Account caller, AffinityGroupProcessor processor) { if (!_accountMgr.isRootAdmin(caller.getId())) { throw new InvalidParameterValueException("Unable to create affinity group, account name must be passed with the domainId"); } if (!processor.canBeSharedDomainWide()) { throw new InvalidParameterValueException("Unable to create affinity group, account name is needed. Affinity group type "+ processor.getType() +" cannot be shared domain wide"); } } private AffinityGroupVO createAffinityGroup(final AffinityGroupProcessor processor, final Account owner, final ACLType aclType, final String affinityGroupName, final String affinityGroupType, final String description) { return Transaction.execute(new TransactionCallback<AffinityGroupVO>() { @Override public AffinityGroupVO doInTransaction(TransactionStatus status) { AffinityGroupVO group = new AffinityGroupVO(affinityGroupName, affinityGroupType, description, owner.getDomainId(), owner.getId(), aclType); _affinityGroupDao.persist(group); if (aclType == ACLType.Domain) { boolean subDomainAccess = false; subDomainAccess = processor.subDomainAccess(); AffinityGroupDomainMapVO domainMap = new AffinityGroupDomainMapVO(group.getId(), owner.getDomainId(), subDomainAccess); _affinityGroupDomainMapDao.persist(domainMap); //send event for storing the domain wide resource access Map<String, Object> params = new HashMap<String, Object>(); params.put(ApiConstants.ENTITY_TYPE, AffinityGroup.class); params.put(ApiConstants.ENTITY_ID, group.getId()); params.put(ApiConstants.DOMAIN_ID, owner.getDomainId()); params.put(ApiConstants.SUBDOMAIN_ACCESS, subDomainAccess); _messageBus.publish(_name, EntityManager.MESSAGE_ADD_DOMAIN_WIDE_ENTITY_EVENT, PublishScope.LOCAL, params); } return group; } }); } private DomainVO getDomain(Long domainId) { DomainVO domain = _domainDao.findById(domainId); if (domain == null) { throw new InvalidParameterValueException("Unable to find domain by specified id"); } return domain; } private void verifyAffinityGroupNameInUse(long accountId, long domainId, String affinityGroupName) { if (_affinityGroupDao.isNameInUse(accountId, domainId, affinityGroupName)) { throw new InvalidParameterValueException("Unable to create affinity group, a group with name " + affinityGroupName + " already exists."); } } private void verifyDomainLevelAffinityGroupName(boolean domainLevel, long domainId, String affinityGroupName) { if (domainLevel && _affinityGroupDao.findDomainLevelGroupByName(domainId, affinityGroupName) != null) { throw new InvalidParameterValueException("Unable to create affinity group, a group with name " + affinityGroupName + " already exists under the domain."); } } @DB @ActionEvent(eventType = EventTypes.EVENT_AFFINITY_GROUP_DELETE, eventDescription = "Deleting affinity group") public boolean deleteAffinityGroup(Long affinityGroupId, String account, Long projectId, Long domainId, String affinityGroupName) { AffinityGroupVO group = getAffinityGroup(affinityGroupId, account, projectId, domainId, affinityGroupName); // check permissions Account caller = CallContext.current().getCallingAccount(); _accountMgr.checkAccess(caller, AccessType.OperateEntry, true, group); final Long affinityGroupIdFinal = group.getId(); deleteAffinityGroup(affinityGroupIdFinal); // remove its related ACL permission Pair<Class<?>, Long> params = new Pair<Class<?>, Long>(AffinityGroup.class, affinityGroupIdFinal); _messageBus.publish(_name, EntityManager.MESSAGE_REMOVE_ENTITY_EVENT, PublishScope.LOCAL, params); if (s_logger.isDebugEnabled()) { s_logger.debug("Deleted affinity group id=" + affinityGroupIdFinal); } return true; } private AffinityGroupVO getAffinityGroup(Long affinityGroupId, String account, Long projectId, Long domainId, String affinityGroupName) { AffinityGroupVO group = null; if (affinityGroupId != null) { group = _affinityGroupDao.findById(affinityGroupId); } else if (affinityGroupName != null) { group = getAffinityGroupByName(account, projectId, domainId, affinityGroupName); } else { throw new InvalidParameterValueException("Either the affinity group Id or group name must be specified to delete the group"); } if (group == null) { throw new InvalidParameterValueException("Unable to find affinity group " + (affinityGroupId == null ? affinityGroupName : affinityGroupId)); } return group; } private AffinityGroupVO getAffinityGroupByName(String account, Long projectId, Long domainId, String affinityGroupName) { AffinityGroupVO group = null; if(account == null && domainId != null){ group = _affinityGroupDao.findDomainLevelGroupByName(domainId, affinityGroupName); }else{ Long accountId = _accountMgr.finalyzeAccountId(account, domainId, projectId, true); if(accountId == null){ Account caller = CallContext.current().getCallingAccount(); group = _affinityGroupDao.findByAccountAndName(caller.getAccountId(), affinityGroupName); }else{ group = _affinityGroupDao.findByAccountAndName(accountId, affinityGroupName); } } return group; } private void deleteAffinityGroup(final Long affinityGroupId) { Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { AffinityGroupVO group = _affinityGroupDao.lockRow(affinityGroupId, true); if (group == null) { throw new InvalidParameterValueException("Unable to find affinity group by id " + affinityGroupId); } List<AffinityGroupVMMapVO> affinityGroupVmMap = _affinityGroupVMMapDao.listByAffinityGroup(affinityGroupId); if (!affinityGroupVmMap.isEmpty()) { SearchBuilder<AffinityGroupVMMapVO> listByAffinityGroup = _affinityGroupVMMapDao.createSearchBuilder(); listByAffinityGroup.and("affinityGroupId", listByAffinityGroup.entity().getAffinityGroupId(), SearchCriteria.Op.EQ); listByAffinityGroup.done(); SearchCriteria<AffinityGroupVMMapVO> sc = listByAffinityGroup.create(); sc.setParameters("affinityGroupId", affinityGroupId); _affinityGroupVMMapDao.lockRows(sc, null, true); _affinityGroupVMMapDao.remove(sc); } // call processor to handle the group delete AffinityGroupProcessor processor = getAffinityGroupProcessorForType(group.getType()); if (processor != null) { processor.handleDeleteGroup(group); } if(_affinityGroupDao.expunge(affinityGroupId)){ AffinityGroupDomainMapVO groupDomain = _affinityGroupDomainMapDao .findByAffinityGroup(affinityGroupId); if (groupDomain != null) { _affinityGroupDomainMapDao.remove(groupDomain.getId()); } } } }); } @Override public List<String> listAffinityGroupTypes() { List<String> types = new ArrayList<String>(); for (AffinityGroupProcessor processor : _affinityProcessors) { if (processor.isAdminControlledGroup()) { continue; // we dont list the type if this group can be // created only as an admin/system operation. } types.add(processor.getType()); } return types; } protected Map<String, AffinityGroupProcessor> getAffinityTypeToProcessorMap() { Map<String, AffinityGroupProcessor> typeProcessorMap = new HashMap<String, AffinityGroupProcessor>(); for (AffinityGroupProcessor processor : _affinityProcessors) { typeProcessorMap.put(processor.getType(), processor); } 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; VirtualMachine.State.getStateMachine().registerListener(this); return true; } @Override public boolean start() { return true; } @Override public boolean stop() { return true; } @Override public String getName() { return _name; } @Override public AffinityGroup getAffinityGroup(Long groupId) { return _affinityGroupDao.findById(groupId); } @Override public boolean preStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vo, boolean status, Object opaque) { return true; } @Override public boolean postStateTransitionEvent(StateMachine2.Transition<State, Event> transition, VirtualMachine vo, boolean status, Object opaque) { if (!status) { return false; } State newState = transition.getToState(); if ((newState == State.Expunging) || (newState == State.Error)) { // cleanup all affinity groups associations of the Expunged VM SearchCriteria<AffinityGroupVMMapVO> sc = _affinityGroupVMMapDao.createSearchCriteria(); sc.addAnd("instanceId", SearchCriteria.Op.EQ, vo.getId()); _affinityGroupVMMapDao.expunge(sc); } return true; } @Override public UserVm updateVMAffinityGroups(Long vmId, List<Long> affinityGroupIds) { // Verify input parameters UserVmVO vmInstance = _userVmDao.findById(vmId); if (vmInstance == null) { throw new InvalidParameterValueException("Unable to find a virtual machine with id " + vmId); } // Check that the VM is stopped if (!vmInstance.getState().equals(State.Stopped)) { s_logger.warn("Unable to update affinity groups of the virtual machine " + vmInstance.toString() + " in state " + vmInstance.getState()); throw new InvalidParameterValueException("Unable update affinity groups of the virtual machine " + vmInstance.toString() + " " + "in state " + vmInstance.getState() + "; make sure the virtual machine is stopped and not in an error state before updating."); } Account caller = CallContext.current().getCallingAccount(); Account owner = _accountMgr.getAccount(vmInstance.getAccountId()); // check that the affinity groups exist for (Long affinityGroupId : affinityGroupIds) { AffinityGroupVO ag = _affinityGroupDao.findById(affinityGroupId); if (ag == null) { throw new InvalidParameterValueException("Unable to find affinity group by id " + affinityGroupId); } 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.getId())) { if (ag.getAccountId() != owner.getAccountId()) { throw new PermissionDeniedException("Affinity Group " + ag + " does not belong to the VM's account"); } } } } _affinityGroupVMMapDao.updateMap(vmId, affinityGroupIds); if (s_logger.isDebugEnabled()) { s_logger.debug("Updated VM :" + vmId + " affinity groups to =" + affinityGroupIds); } // APIResponseHelper will pull out the updated affinitygroups. return vmInstance; } @Override public boolean isAffinityGroupProcessorAvailable(String affinityGroupType) { for (AffinityGroupProcessor processor : _affinityProcessors) { if (affinityGroupType != null && affinityGroupType.equals(processor.getType())) { return true; } } return false; } private AffinityGroupProcessor getAffinityGroupProcessorForType(String affinityGroupType) { for (AffinityGroupProcessor processor : _affinityProcessors) { if (affinityGroupType != null && affinityGroupType.equals(processor.getType())) { return processor; } } return null; } @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; } }