content: // 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.List;
  import java.util.Set;
  
  import javax.inject.Inject;
  
  import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
  import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
  
  import com.cloud.dc.ClusterVO;
  import com.cloud.dc.DataCenter;
  import com.cloud.dc.DataCenterVO;
  import com.cloud.dc.DedicatedResourceVO;
  import com.cloud.dc.HostPodVO;
  import com.cloud.dc.dao.ClusterDao;
  import com.cloud.dc.dao.DataCenterDao;
  import com.cloud.dc.dao.DedicatedResourceDao;
  import com.cloud.dc.dao.HostPodDao;
  import com.cloud.deploy.DeploymentPlan;
  import com.cloud.deploy.DeploymentPlanner.ExcludeList;
  import com.cloud.domain.DomainVO;
  import com.cloud.domain.dao.DomainDao;
  import com.cloud.exception.AffinityConflictException;
  import com.cloud.host.HostVO;
  import com.cloud.host.dao.HostDao;
  import com.cloud.utils.db.DB;
  import com.cloud.utils.db.SearchBuilder;
  import com.cloud.utils.db.SearchCriteria;
  import com.cloud.utils.db.Transaction;
  import com.cloud.utils.db.TransactionCallbackNoReturn;
  import com.cloud.utils.db.TransactionStatus;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.vm.VirtualMachine;
  import com.cloud.vm.VirtualMachineProfile;
  import com.cloud.vm.dao.UserVmDao;
  import com.cloud.vm.dao.VMInstanceDao;
  
  public class ExplicitDedicationProcessor extends AffinityProcessorBase implements AffinityGroupProcessor {
  
      @Inject
      protected UserVmDao _vmDao;
      @Inject
      protected VMInstanceDao _vmInstanceDao;
      @Inject
      protected DataCenterDao _dcDao;
      @Inject
      protected DedicatedResourceDao _dedicatedDao;
      @Inject
      protected HostPodDao _podDao;
      @Inject
      protected ClusterDao _clusterDao;
      @Inject
      protected HostDao _hostDao;
      @Inject
      protected DomainDao _domainDao;
      @Inject
      protected AffinityGroupDao _affinityGroupDao;
      @Inject
      protected AffinityGroupVMMapDao _affinityGroupVMMapDao;
  
      /**
       * This method will process the affinity group of type 'Explicit Dedication' for a deployment of a VM that demands dedicated resources.
       * For ExplicitDedicationProcessor we need to add dedicated resources into the IncludeList based on the level we have dedicated resources available.
       * For eg. if admin dedicates a pod to a domain, then all the user in that domain can use the resources of that pod.
       * We need to take care of the situation when dedicated resources further have resources dedicated to sub-domain/account.
       * This IncludeList is then used to update the avoid list for a given data center.
       */
      @Override
      public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, List<VirtualMachine> vmList) throws AffinityConflictException {
          VirtualMachine vm = vmProfile.getVirtualMachine();
          List<AffinityGroupVMMapVO> vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType());
          DataCenter dc = _dcDao.findById(vm.getDataCenterId());
          List<DedicatedResourceVO> resourceList = new ArrayList<DedicatedResourceVO>();
  
          if (vmGroupMappings != null && !vmGroupMappings.isEmpty()) {
  
              for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) {
                  if (vmGroupMapping != null) {
                      if (logger.isDebugEnabled()) {
                          logger.debug("Processing affinity group {} of type 'ExplicitDedication' for VM: {}", _affinityGroupDao.findById(vmGroupMapping.getAffinityGroupId()), vm);
                      }
  
                      long affinityGroupId = vmGroupMapping.getAffinityGroupId();
  
                      List<DedicatedResourceVO> dr = _dedicatedDao.listByAffinityGroupId(affinityGroupId);
                      resourceList.addAll(dr);
  
                  }
              }
  
              boolean canUse = false;
  
              if (plan.getHostId() != null) {
                  HostVO host = _hostDao.findById(plan.getHostId());
                  ClusterVO clusterofHost = _clusterDao.findById(host.getClusterId());
                  HostPodVO podOfHost = _podDao.findById(host.getPodId());
                  DataCenterVO zoneOfHost = _dcDao.findById(host.getDataCenterId());
                  if (resourceList != null && resourceList.size() != 0) {
                      for (DedicatedResourceVO resource : resourceList) {
                          if ((resource.getHostId() != null && resource.getHostId().longValue() == plan.getHostId().longValue()) ||
                                  (resource.getClusterId() != null && resource.getClusterId().longValue() == clusterofHost.getId()) ||
                                  (resource.getPodId() != null && resource.getPodId().longValue() == podOfHost.getId()) ||
                                  (resource.getDataCenterId() != null && resource.getDataCenterId().longValue() == zoneOfHost.getId())) {
                              canUse = true;
                          }
                      }
                  }
                  if (!canUse) {
                      throw new CloudRuntimeException("Cannot use this host " + host.getName() + " for explicit dedication");
                  }
              } else if (plan.getClusterId() != null) {
                  ClusterVO cluster = _clusterDao.findById(plan.getClusterId());
                  HostPodVO podOfCluster = _podDao.findById(cluster.getPodId());
                  DataCenterVO zoneOfCluster = _dcDao.findById(cluster.getDataCenterId());
                  List<HostVO> hostToUse = new ArrayList<HostVO>();
                  // check whether this cluster or its pod is dedicated
                  if (resourceList != null && resourceList.size() != 0) {
                      for (DedicatedResourceVO resource : resourceList) {
                          if ((resource.getClusterId() != null && resource.getClusterId() == cluster.getId()) ||
                              (resource.getPodId() != null && resource.getPodId() == podOfCluster.getId()) ||
                              (resource.getDataCenterId() != null && resource.getDataCenterId() == zoneOfCluster.getId())) {
                              canUse = true;
                          }
  
                          // check for all dedicated host; if it belongs to this
                          // cluster
                          if (!canUse) {
                              if (resource.getHostId() != null) {
                                  HostVO dHost = _hostDao.findById(resource.getHostId());
                                  if (dHost.getClusterId() == cluster.getId()) {
                                      hostToUse.add(dHost);
                                  }
                              }
                          }
  
                      }
                  }
  
                  if (hostToUse.isEmpty() && !canUse) {
                      throw new CloudRuntimeException("Cannot use this cluster " + cluster.getName() + " for explicit dedication");
                  }
  
                  if (hostToUse != null && hostToUse.size() != 0) {
                      // add other non-dedicated hosts to avoid list
                      List<HostVO> hostList = _hostDao.findByClusterId(cluster.getId());
                      for (HostVO host : hostList) {
                          if (!hostToUse.contains(host)) {
                              avoid.addHost(host.getId());
                          }
                      }
                  }
  
              } else if (plan.getPodId() != null) {
                  HostPodVO pod = _podDao.findById(plan.getPodId());
                  DataCenterVO zoneOfPod = _dcDao.findById(pod.getDataCenterId());
                  List<ClusterVO> clustersToUse = new ArrayList<ClusterVO>();
                  List<HostVO> hostsToUse = new ArrayList<HostVO>();
                  // check whether this cluster or its pod is dedicated
                  if (resourceList != null && resourceList.size() != 0) {
                      for (DedicatedResourceVO resource : resourceList) {
                          if ((resource.getPodId() != null && resource.getPodId() == pod.getId()) ||
                              (resource.getDataCenterId() != null && resource.getDataCenterId() == zoneOfPod.getId())) {
                              canUse = true;
                          }
  
                          // check for all dedicated cluster/host; if it belongs
                          // to
                          // this pod
                          if (!canUse) {
                              if (resource.getClusterId() != null) {
                                  ClusterVO dCluster = _clusterDao.findById(resource.getClusterId());
                                  if (dCluster.getPodId() == pod.getId()) {
                                      clustersToUse.add(dCluster);
                                  }
                              }
                              if (resource.getHostId() != null) {
                                  HostVO dHost = _hostDao.findById(resource.getHostId());
                                  if (dHost.getPodId() == pod.getId()) {
                                      hostsToUse.add(dHost);
                                  }
                              }
                          }
  
                      }
                  }
  
                  if (hostsToUse.isEmpty() && clustersToUse.isEmpty() && !canUse) {
                      throw new CloudRuntimeException("Cannot use this pod " + pod.getName() + " for explicit dedication");
                  }
  
                  if (clustersToUse != null && clustersToUse.size() != 0) {
                      // add other non-dedicated clusters to avoid list
                      List<ClusterVO> clusterList = _clusterDao.listByPodId(pod.getId());
                      for (ClusterVO cluster : clusterList) {
                          if (!clustersToUse.contains(cluster)) {
                              avoid.addCluster(cluster.getId());
                          }
                      }
                  }
  
                  if (hostsToUse != null && hostsToUse.size() != 0) {
                      // add other non-dedicated hosts to avoid list
                      List<HostVO> hostList = _hostDao.findByPodId(pod.getId());
                      for (HostVO host : hostList) {
                          if (!hostsToUse.contains(host)) {
                              avoid.addHost(host.getId());
                          }
                      }
                  }
  
              } else {
                  // check all resources under this zone
                  if (resourceList != null && resourceList.size() != 0) {
                      avoid = updateAvoidList(resourceList, avoid, dc);
                  } else {
                      avoid.addDataCenter(dc.getId());
                      if (logger.isDebugEnabled()) {
                          logger.debug("No dedicated resources available for this domain or account under this group");
                      }
                  }
  
                  if (logger.isDebugEnabled()) {
                      logger.debug("ExplicitDedicationProcessor returns Avoid List as: Deploy avoids pods: " + avoid.getPodsToAvoid() + ", clusters: " +
                          avoid.getClustersToAvoid() + ", hosts: " + avoid.getHostsToAvoid());
                  }
              }
          }
  
      }
  
      private ExcludeList updateAvoidList(List<DedicatedResourceVO> dedicatedResources, ExcludeList avoidList, DataCenter dc) {
          ExcludeList includeList = new ExcludeList();
          for (DedicatedResourceVO dr : dedicatedResources) {
              if (dr.getHostId() != null) {
                  includeList.addHost(dr.getHostId());
                  HostVO dedicatedHost = _hostDao.findById(dr.getHostId());
                  includeList.addCluster(dedicatedHost.getClusterId());
                  includeList.addPod(dedicatedHost.getPodId());
              }
  
              if (dr.getClusterId() != null) {
                  includeList.addCluster(dr.getClusterId());
                  //add all hosts inside this in includeList
                  List<HostVO> hostList = _hostDao.findByClusterId(dr.getClusterId());
                  for (HostVO host : hostList) {
                      DedicatedResourceVO dHost = _dedicatedDao.findByHostId(host.getId());
                      if (dHost != null && !dedicatedResources.contains(dHost)) {
                          avoidList.addHost(host.getId());
                      } else {
                          includeList.addHost(host.getId());
                      }
                  }
                  ClusterVO dedicatedCluster = _clusterDao.findById(dr.getClusterId());
                  includeList.addPod(dedicatedCluster.getPodId());
              }
  
              if (dr.getPodId() != null) {
                  includeList.addPod(dr.getPodId());
                  //add all cluster under this pod in includeList
                  List<ClusterVO> clusterList = _clusterDao.listByPodId(dr.getPodId());
                  for (ClusterVO cluster : clusterList) {
                      DedicatedResourceVO dCluster = _dedicatedDao.findByClusterId(cluster.getId());
                      if (dCluster != null && !dedicatedResources.contains(dCluster)) {
                          avoidList.addCluster(cluster.getId());
                      } else {
                          includeList.addCluster(cluster.getId());
                      }
                  }
                  //add all hosts inside this pod in includeList
                  List<HostVO> hostList = _hostDao.findByPodId(dr.getPodId());
                  for (HostVO host : hostList) {
                      DedicatedResourceVO dHost = _dedicatedDao.findByHostId(host.getId());
                      if (dHost != null && !dedicatedResources.contains(dHost)) {
                          avoidList.addHost(host.getId());
                      } else {
                          includeList.addHost(host.getId());
                      }
                  }
              }
  
              if (dr.getDataCenterId() != null) {
                  includeList.addDataCenter(dr.getDataCenterId());
                  //add all Pod under this data center in includeList
                  List<HostPodVO> podList = _podDao.listByDataCenterId(dr.getDataCenterId());
                  for (HostPodVO pod : podList) {
                      DedicatedResourceVO dPod = _dedicatedDao.findByPodId(pod.getId());
                      if (dPod != null && !dedicatedResources.contains(dPod)) {
                          if (logger.isDebugEnabled()) {
                              logger.debug(String.format("Avoiding POD %s because it is not dedicated.", pod));
                          }
                          avoidList.addPod(pod.getId());
                      } else {
                          includeList.addPod(pod.getId());
                      }
                  }
                  List<ClusterVO> clusterList = _clusterDao.listClustersByDcId(dr.getDataCenterId());
                  for (ClusterVO cluster : clusterList) {
                      DedicatedResourceVO dCluster = _dedicatedDao.findByClusterId(cluster.getId());
                      if (dCluster != null && !dedicatedResources.contains(dCluster)) {
                          avoidList.addCluster(cluster.getId());
                      } else {
                          includeList.addCluster(cluster.getId());
                      }
                  }
                  //add all hosts inside this in includeList
                  List<Long> hostList = _hostDao.listEnabledIdsByDataCenterId(dr.getDataCenterId());
                  for (Long hostId : hostList) {
                      DedicatedResourceVO dHost = _dedicatedDao.findByHostId(hostId);
                      if (dHost != null && !dedicatedResources.contains(dHost)) {
                          avoidList.addHost(hostId);
                      } else {
                          includeList.addHost(hostId);
                      }
                  }
              }
          }
          //Update avoid list using includeList.
          //add resources in avoid list which are not in include list.
  
          List<HostPodVO> pods = _podDao.listByDataCenterId(dc.getId());
          List<ClusterVO> clusters = _clusterDao.listClustersByDcId(dc.getId());
          List<Long> hostIds = _hostDao.listEnabledIdsByDataCenterId(dc.getId());
          Set<Long> podsInIncludeList = includeList.getPodsToAvoid();
          Set<Long> clustersInIncludeList = includeList.getClustersToAvoid();
          Set<Long> hostsInIncludeList = includeList.getHostsToAvoid();
  
          for (HostPodVO pod : pods) {
              if (podsInIncludeList != null && !podsInIncludeList.contains(pod.getId())) {
                  if (logger.isDebugEnabled()) {
                      logger.debug(String.format("Avoiding POD %s, as it is not in include list.", pod));
                  }
                  avoidList.addPod(pod.getId());
              }
          }
  
          for (ClusterVO cluster : clusters) {
              if (clustersInIncludeList != null && !clustersInIncludeList.contains(cluster.getId())) {
                  avoidList.addCluster(cluster.getId());
              }
          }
  
          for (Long hostId : hostIds) {
              if (hostsInIncludeList != null && !hostsInIncludeList.contains(hostId)) {
                  avoidList.addHost(hostId);
              }
          }
          return avoidList;
      }
  
      private List<DedicatedResourceVO> searchInParentDomainResources(long domainId) {
          List<Long> domainIds = getDomainParentIds(domainId);
          List<DedicatedResourceVO> dr = new ArrayList<DedicatedResourceVO>();
          for (Long id : domainIds) {
              List<DedicatedResourceVO> resource = _dedicatedDao.listByDomainId(id);
              if (resource != null) {
                  dr.addAll(resource);
              }
          }
          return dr;
      }
  
      private List<DedicatedResourceVO> searchInDomainResources(long domainId) {
          List<DedicatedResourceVO> dr = _dedicatedDao.listByDomainId(domainId);
          return dr;
      }
  
      private List<Long> getDomainParentIds(long domainId) {
          DomainVO domainRecord = _domainDao.findById(domainId);
          List<Long> domainIds = new ArrayList<Long>();
          domainIds.add(domainRecord.getId());
          while (domainRecord.getParent() != null) {
              domainRecord = _domainDao.findById(domainRecord.getParent());
              domainIds.add(domainRecord.getId());
          }
          return domainIds;
      }
  
      @Override
      public boolean isAdminControlledGroup() {
          return true;
      }
  
      @Override
      public boolean canBeSharedDomainWide() {
          return true;
      }
  
      @DB
      @Override
      public void handleDeleteGroup(final AffinityGroup group) {
          // When a group of the 'ExplicitDedication' type gets deleted, make sure
          // to remove the dedicated resources in the group as well.
          if (group != null) {
              List<DedicatedResourceVO> dedicatedResources = _dedicatedDao.listByAffinityGroupId(group.getId());
              if (!dedicatedResources.isEmpty()) {
                  if (logger.isDebugEnabled()) {
                      logger.debug("Releasing the dedicated resources under group: " + group);
                  }
  
                  Transaction.execute(new TransactionCallbackNoReturn() {
                      @Override
                      public void doInTransactionWithoutResult(TransactionStatus status) {
                          SearchBuilder<DedicatedResourceVO> listByAffinityGroup = _dedicatedDao.createSearchBuilder();
                          listByAffinityGroup.and("affinityGroupId", listByAffinityGroup.entity().getAffinityGroupId(), SearchCriteria.Op.EQ);
                          listByAffinityGroup.done();
                          SearchCriteria<DedicatedResourceVO> sc = listByAffinityGroup.create();
                          sc.setParameters("affinityGroupId", group.getId());
  
                          _dedicatedDao.lockRows(sc, null, true);
                          _dedicatedDao.remove(sc);
                      }
                  });
              } else {
                  if (logger.isDebugEnabled()) {
                      logger.debug("No dedicated resources to releease under group: " + group);
                  }
              }
          }
  
          return;
      }
  
      @Override
      public boolean subDomainAccess() {
          return true;
      }
  }
  