diff --git a/api/src/com/cloud/deploy/DeploymentPlanner.java b/api/src/com/cloud/deploy/DeploymentPlanner.java index eb56a591f6b..769da39f3ff 100644 --- a/api/src/com/cloud/deploy/DeploymentPlanner.java +++ b/api/src/com/cloud/deploy/DeploymentPlanner.java @@ -213,6 +213,13 @@ public interface DeploymentPlanner extends Adapter { _hostIds.add(hostId); } + public void addHostList(Collection hostList) { + if (_hostIds == null) { + _hostIds = new HashSet(); + } + _hostIds.addAll(hostList); + } + public boolean shouldAvoid(Host host) { if (_dcIds != null && _dcIds.contains(host.getDataCenterId())) { return true; diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java index 8d7739c13e1..cf093bf4c7c 100755 --- a/api/src/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java @@ -312,6 +312,7 @@ public class ApiConstants { public static final String ACCEPT = "accept"; public static final String SORT_KEY = "sortkey"; public static final String ACCOUNT_DETAILS = "accountdetails"; + public static final String SERVICE_OFFERING_DETAILS = "serviceofferingdetails"; public static final String SERVICE_PROVIDER_LIST = "serviceproviderlist"; public static final String SERVICE_CAPABILITY_LIST = "servicecapabilitylist"; public static final String CAN_CHOOSE_SERVICE_CAPABILITY = "canchooseservicecapability"; diff --git a/api/src/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java b/api/src/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java index c155b706fc0..4c54a4e5ec6 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java @@ -16,6 +16,9 @@ // under the License. package org.apache.cloudstack.api.command.admin.offering; +import java.util.Collection; +import java.util.Map; + import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; @@ -87,6 +90,9 @@ public class CreateServiceOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.DEPLOYMENT_PLANNER, type = CommandType.STRING, description = "The deployment planner heuristics used to deploy a VM of this offering. If null, value of global config vm.deployment.planner is used") private String deploymentPlanner; + @Parameter(name = ApiConstants.SERVICE_OFFERING_DETAILS, type = CommandType.MAP, description = "details for planner, used to store specific parameters") + private Map details; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -155,6 +161,16 @@ public class CreateServiceOfferingCmd extends BaseCmd { return deploymentPlanner; } + public Map getDetails() { + if (details == null || details.isEmpty()) { + return null; + } + + Collection paramsCollection = details.values(); + Map params = (Map)(paramsCollection.toArray())[0]; + return params; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/client/pom.xml b/client/pom.xml index 197ba27975c..0c38ecb65d2 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -131,6 +131,11 @@ cloud-plugin-planner-user-concentrated-pod ${project.version} + + org.apache.cloudstack + cloud-plugin-planner-implicit-dedication + ${project.version} + org.apache.cloudstack cloud-plugin-host-allocator-random diff --git a/client/tomcatconf/applicationContext.xml.in b/client/tomcatconf/applicationContext.xml.in index 1d1eca4c191..b500fde8549 100644 --- a/client/tomcatconf/applicationContext.xml.in +++ b/client/tomcatconf/applicationContext.xml.in @@ -370,7 +370,7 @@ - + diff --git a/engine/schema/src/com/cloud/service/ServiceOfferingDetailsVO.java b/engine/schema/src/com/cloud/service/ServiceOfferingDetailsVO.java new file mode 100644 index 00000000000..b005c738e82 --- /dev/null +++ b/engine/schema/src/com/cloud/service/ServiceOfferingDetailsVO.java @@ -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 com.cloud.service; + +import org.apache.cloudstack.api.InternalIdentity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name="service_offering_details") +public class ServiceOfferingDetailsVO implements InternalIdentity { + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column(name="id") + private long id; + + @Column(name="service_offering_id") + private long serviceOfferingId; + + @Column(name="name") + private String name; + + @Column(name="value") + private String value; + + protected ServiceOfferingDetailsVO() { + } + + public ServiceOfferingDetailsVO(long serviceOfferingId, String name, String value) { + this.serviceOfferingId = serviceOfferingId; + this.name = name; + this.value = value; + } + + public long getServiceOfferingId() { + return serviceOfferingId; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public long getId() { + return id; + } +} \ No newline at end of file diff --git a/engine/schema/src/com/cloud/service/ServiceOfferingVO.java b/engine/schema/src/com/cloud/service/ServiceOfferingVO.java index fd31d301bc3..9a262c540b7 100755 --- a/engine/schema/src/com/cloud/service/ServiceOfferingVO.java +++ b/engine/schema/src/com/cloud/service/ServiceOfferingVO.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.service; +import java.util.Map; + import javax.persistence.Column; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; @@ -71,6 +73,12 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering @Column(name = "deployment_planner") private String deploymentPlanner = null; + // This is a delayed load value. If the value is null, + // then this field has not been loaded yet. + // Call service offering dao to load it. + @Transient + Map details; + protected ServiceOfferingVO() { super(); } @@ -225,4 +233,23 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering return deploymentPlanner; } + public Map getDetails() { + return details; + } + + public String getDetail(String name) { + assert (details != null) : "Did you forget to load the details?"; + + return details != null ? details.get(name) : null; + } + + public void setDetail(String name, String value) { + assert (details != null) : "Did you forget to load the details?"; + + details.put(name, value); + } + + public void setDetails(Map details) { + this.details = details; + } } diff --git a/engine/schema/src/com/cloud/service/dao/ServiceOfferingDao.java b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDao.java index 589de7cc055..7da72088431 100644 --- a/engine/schema/src/com/cloud/service/dao/ServiceOfferingDao.java +++ b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDao.java @@ -31,4 +31,6 @@ public interface ServiceOfferingDao extends GenericDao List findServiceOfferingByDomainId(Long domainId); List findSystemOffering(Long domainId, Boolean isSystem, String vm_type); ServiceOfferingVO persistDeafultServiceOffering(ServiceOfferingVO offering); + void loadDetails(ServiceOfferingVO serviceOffering); + void saveDetails(ServiceOfferingVO serviceOffering); } diff --git a/engine/schema/src/com/cloud/service/dao/ServiceOfferingDaoImpl.java b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDaoImpl.java index 062103e3198..14b2abf8fc4 100644 --- a/engine/schema/src/com/cloud/service/dao/ServiceOfferingDaoImpl.java +++ b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDaoImpl.java @@ -18,15 +18,16 @@ package com.cloud.service.dao; import java.util.Date; import java.util.List; +import java.util.Map; import javax.ejb.Local; +import javax.inject.Inject; import javax.persistence.EntityExistsException; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import com.cloud.service.ServiceOfferingVO; -import com.cloud.storage.DiskOfferingVO; import com.cloud.utils.db.DB; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; @@ -37,6 +38,8 @@ import com.cloud.utils.db.SearchCriteria; public class ServiceOfferingDaoImpl extends GenericDaoBase implements ServiceOfferingDao { protected static final Logger s_logger = Logger.getLogger(ServiceOfferingDaoImpl.class); + @Inject protected ServiceOfferingDetailsDao detailsDao; + protected final SearchBuilder UniqueNameSearch; protected final SearchBuilder ServiceOfferingsByDomainIdSearch; protected final SearchBuilder SystemServiceOffering; @@ -154,4 +157,18 @@ public class ServiceOfferingDaoImpl extends GenericDaoBase details = detailsDao.findDetails(serviceOffering.getId()); + serviceOffering.setDetails(details); + } + + @Override + public void saveDetails(ServiceOfferingVO serviceOffering) { + Map details = serviceOffering.getDetails(); + if (details != null) { + detailsDao.persist(serviceOffering.getId(), details); + } + } } diff --git a/engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDao.java b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDao.java new file mode 100644 index 00000000000..38169105819 --- /dev/null +++ b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDao.java @@ -0,0 +1,29 @@ +// 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.service.dao; + +import java.util.Map; + +import com.cloud.service.ServiceOfferingDetailsVO; +import com.cloud.utils.db.GenericDao; + +public interface ServiceOfferingDetailsDao extends GenericDao { + Map findDetails(long serviceOfferingId); + void persist(long serviceOfferingId, Map details); + ServiceOfferingDetailsVO findDetail(long serviceOfferingId, String name); + void deleteDetails(long serviceOfferingId); +} \ No newline at end of file diff --git a/engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDaoImpl.java b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDaoImpl.java new file mode 100644 index 00000000000..91d736a38c4 --- /dev/null +++ b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDaoImpl.java @@ -0,0 +1,98 @@ +// 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.service.dao; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ejb.Local; + +import org.springframework.stereotype.Component; + +import com.cloud.service.ServiceOfferingDetailsVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; + +@Component +@Local(value=ServiceOfferingDetailsDao.class) +public class ServiceOfferingDetailsDaoImpl extends GenericDaoBase + implements ServiceOfferingDetailsDao { + protected final SearchBuilder ServiceOfferingSearch; + protected final SearchBuilder DetailSearch; + + public ServiceOfferingDetailsDaoImpl() { + ServiceOfferingSearch = createSearchBuilder(); + ServiceOfferingSearch.and("serviceOfferingId", ServiceOfferingSearch.entity().getServiceOfferingId(), SearchCriteria.Op.EQ); + ServiceOfferingSearch.done(); + + DetailSearch = createSearchBuilder(); + DetailSearch.and("serviceOfferingId", DetailSearch.entity().getServiceOfferingId(), SearchCriteria.Op.EQ); + DetailSearch.and("name", DetailSearch.entity().getName(), SearchCriteria.Op.EQ); + DetailSearch.done(); + } + + @Override + public ServiceOfferingDetailsVO findDetail(long serviceOfferingId, String name) { + SearchCriteria sc = DetailSearch.create(); + sc.setParameters("serviceOfferingId", serviceOfferingId); + sc.setParameters("name", name); + ServiceOfferingDetailsVO detail = findOneIncludingRemovedBy(sc); + return detail; + } + + @Override + public Map findDetails(long serviceOfferingId) { + SearchCriteria sc = ServiceOfferingSearch.create(); + sc.setParameters("serviceOfferingId", serviceOfferingId); + List results = search(sc, null); + Map details = new HashMap(results.size()); + for (ServiceOfferingDetailsVO result : results) { + details.put(result.getName(), result.getValue()); + } + + return details; + } + + @Override + public void deleteDetails(long serviceOfferingId) { + SearchCriteria sc = ServiceOfferingSearch.create(); + sc.setParameters("serviceOfferingId", serviceOfferingId); + List results = search(sc, null); + for (ServiceOfferingDetailsVO result : results) { + remove(result.getId()); + } + } + + @Override + public void persist(long serviceOfferingId, Map details) { + Transaction txn = Transaction.currentTxn(); + txn.start(); + SearchCriteria sc = ServiceOfferingSearch.create(); + sc.setParameters("serviceOfferingId", serviceOfferingId); + expunge(sc); + + for (Map.Entry detail : details.entrySet()) { + String value = detail.getValue(); + ServiceOfferingDetailsVO vo = new ServiceOfferingDetailsVO(serviceOfferingId, detail.getKey(), value); + persist(vo); + } + txn.commit(); + } +} diff --git a/plugins/deployment-planners/implicit-dedication/pom.xml b/plugins/deployment-planners/implicit-dedication/pom.xml new file mode 100644 index 00000000000..18555923668 --- /dev/null +++ b/plugins/deployment-planners/implicit-dedication/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + cloud-plugin-planner-implicit-dedication + Apache CloudStack Plugin - Implicit Dedication Planner + + org.apache.cloudstack + cloudstack-plugins + 4.2.0-SNAPSHOT + ../../pom.xml + + diff --git a/plugins/deployment-planners/implicit-dedication/src/com/cloud/deploy/ImplicitDedicationPlanner.java b/plugins/deployment-planners/implicit-dedication/src/com/cloud/deploy/ImplicitDedicationPlanner.java new file mode 100644 index 00000000000..d47d8f52c46 --- /dev/null +++ b/plugins/deployment-planners/implicit-dedication/src/com/cloud/deploy/ImplicitDedicationPlanner.java @@ -0,0 +1,249 @@ +// 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.deploy; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.log4j.Logger; + +import com.cloud.configuration.Config; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.host.HostVO; +import com.cloud.resource.ResourceManager; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; +import com.cloud.user.Account; +import com.cloud.utils.DateUtil; +import com.cloud.utils.NumbersUtil; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +@Local(value=DeploymentPlanner.class) +public class ImplicitDedicationPlanner extends FirstFitPlanner implements DeploymentClusterPlanner { + + private static final Logger s_logger = Logger.getLogger(ImplicitDedicationPlanner.class); + + @Inject + private ServiceOfferingDao serviceOfferingDao; + @Inject + private ServiceOfferingDetailsDao serviceOfferingDetailsDao; + @Inject + private ResourceManager resourceMgr; + + private int capacityReleaseInterval; + + @Override + public boolean configure(final String name, final Map params) throws ConfigurationException { + super.configure(name, params); + capacityReleaseInterval = NumbersUtil.parseInt(_configDao.getValue(Config.CapacitySkipcountingHours.key()), 3600); + return true; + } + + @Override + public List orderClusters(VirtualMachineProfile vmProfile, + DeploymentPlan plan, ExcludeList avoid) throws InsufficientServerCapacityException { + List clusterList = super.orderClusters(vmProfile, plan, avoid); + Set hostsToAvoid = avoid.getHostsToAvoid(); + Account account = vmProfile.getOwner(); + + if (clusterList == null || clusterList.isEmpty()) { + return clusterList; + } + + // Check if strict or preferred mode should be used. + boolean preferred = isServiceOfferingUsingPlannerInPreferredMode(vmProfile.getServiceOfferingId()); + + // Get the list of all the hosts in the given clusters + List allHosts = new ArrayList(); + for (Long cluster : clusterList) { + List hostsInCluster = resourceMgr.listAllHostsInCluster(cluster); + for (HostVO hostVO : hostsInCluster) { + allHosts.add(hostVO.getId()); + } + } + + // Go over all the hosts in the cluster and get a list of + // 1. All empty hosts, not running any vms. + // 2. Hosts running vms for this account and created by a service offering which uses an + // implicit dedication planner. + // 3. Hosts running vms created by implicit planner and in strict mode of other accounts. + // 4. Hosts running vms from other account or from this account but created by a service offering which uses + // any planner besides implicit. + Set emptyHosts = new HashSet(); + Set hostRunningVmsOfAccount = new HashSet(); + Set hostRunningStrictImplicitVmsOfOtherAccounts = new HashSet(); + Set allOtherHosts = new HashSet(); + for (Long host : allHosts) { + List userVms = getVmsOnHost(host); + if (userVms == null || userVms.isEmpty()) { + emptyHosts.add(host); + } else if (checkHostSuitabilityForImplicitDedication(account.getAccountId(), userVms)) { + hostRunningVmsOfAccount.add(host); + } else if (checkIfAllVmsCreatedInStrictMode(account.getAccountId(), userVms)) { + hostRunningStrictImplicitVmsOfOtherAccounts.add(host); + } else { + allOtherHosts.add(host); + } + } + + // Hosts running vms of other accounts created by ab implicit planner in strict mode should always be avoided. + avoid.addHostList(hostRunningStrictImplicitVmsOfOtherAccounts); + + if (!hostRunningVmsOfAccount.isEmpty() && (hostsToAvoid == null || + !hostsToAvoid.containsAll(hostRunningVmsOfAccount))) { + // Check if any of hosts that are running implicit dedicated vms are available (not in avoid list). + // If so, we'll try and use these hosts. + avoid.addHostList(emptyHosts); + avoid.addHostList(allOtherHosts); + clusterList = getUpdatedClusterList(clusterList, avoid.getHostsToAvoid()); + } else if (!emptyHosts.isEmpty() && (hostsToAvoid == null || !hostsToAvoid.containsAll(emptyHosts))) { + // If there aren't implicit resources try on empty hosts + avoid.addHostList(allOtherHosts); + clusterList = getUpdatedClusterList(clusterList, avoid.getHostsToAvoid()); + } else if (!preferred) { + // If in strict mode, there is nothing else to try. + clusterList = null; + } else { + // If in preferred mode, check if hosts are available to try, otherwise return an empty cluster list. + if (!allOtherHosts.isEmpty() && (hostsToAvoid == null || !hostsToAvoid.containsAll(allOtherHosts))) { + clusterList = getUpdatedClusterList(clusterList, avoid.getHostsToAvoid()); + } else { + clusterList = null; + } + } + + return clusterList; + } + + private List getVmsOnHost(long hostId) { + List vms = _vmDao.listUpByHostId(hostId); + List vmsByLastHostId = _vmDao.listByLastHostId(hostId); + if (vmsByLastHostId.size() > 0) { + // check if any VMs are within skip.counting.hours, if yes we have to consider the host. + for (UserVmVO stoppedVM : vmsByLastHostId) { + long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - stoppedVM.getUpdateTime() + .getTime()) / 1000; + if (secondsSinceLastUpdate < capacityReleaseInterval) { + vms.add(stoppedVM); + } + } + } + + return vms; + } + + private boolean checkHostSuitabilityForImplicitDedication(Long accountId, List allVmsOnHost) { + boolean suitable = true; + for (UserVmVO vm : allVmsOnHost) { + if (vm.getAccountId() != accountId) { + s_logger.info("Host " + vm.getHostId() + " found to be unsuitable for implicit dedication as it is " + + "running instances of another account"); + suitable = false; + break; + } else { + if (!isImplicitPlannerUsedByOffering(vm.getServiceOfferingId())) { + s_logger.info("Host " + vm.getHostId() + " found to be unsuitable for implicit dedication as it " + + "is running instances of this account which haven't been created using implicit dedication."); + suitable = false; + break; + } + } + } + return suitable; + } + + private boolean checkIfAllVmsCreatedInStrictMode(Long accountId, List allVmsOnHost) { + boolean createdByImplicitStrict = true; + for (UserVmVO vm : allVmsOnHost) { + if (!isImplicitPlannerUsedByOffering(vm.getServiceOfferingId())) { + s_logger.info("Host " + vm.getHostId() + " found to be running a vm created by a planner other" + + " than implicit."); + createdByImplicitStrict = false; + break; + } else if (isServiceOfferingUsingPlannerInPreferredMode(vm.getServiceOfferingId())) { + s_logger.info("Host " + vm.getHostId() + " found to be running a vm created by an implicit planner" + + " in preferred mode."); + createdByImplicitStrict = false; + break; + } + } + return createdByImplicitStrict; + } + + private boolean isImplicitPlannerUsedByOffering(long offeringId) { + boolean implicitPlannerUsed = false; + ServiceOfferingVO offering = serviceOfferingDao.findByIdIncludingRemoved(offeringId); + if (offering == null) { + s_logger.error("Couldn't retrieve the offering by the given id : " + offeringId); + } else { + String plannerName = offering.getDeploymentPlanner(); + if (plannerName == null) { + plannerName = _globalDeploymentPlanner; + } + + if (plannerName != null && this.getName().equals(plannerName)) { + implicitPlannerUsed = true; + } + } + + return implicitPlannerUsed; + } + + private boolean isServiceOfferingUsingPlannerInPreferredMode(long serviceOfferingId) { + boolean preferred = false; + Map details = serviceOfferingDetailsDao.findDetails(serviceOfferingId); + if (details != null && !details.isEmpty()) { + String preferredAttribute = details.get("ImplicitDedicationMode"); + if (preferredAttribute != null && preferredAttribute.equals("Preferred")) { + preferred = true; + } + } + return preferred; + } + + private List getUpdatedClusterList(List clusterList, Set hostsSet) { + List updatedClusterList = new ArrayList(); + for (Long cluster : clusterList) { + List hosts = resourceMgr.listAllHostsInCluster(cluster); + Set hostsInClusterSet = new HashSet(); + for (HostVO host : hosts) { + hostsInClusterSet.add(host.getId()); + } + + if (!hostsSet.containsAll(hostsInClusterSet)) { + updatedClusterList.add(cluster); + } + } + + return updatedClusterList; + } + + @Override + public PlannerResourceUsage getResourceUsage() { + return PlannerResourceUsage.Dedicated; + } +} \ No newline at end of file diff --git a/plugins/deployment-planners/implicit-dedication/test/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java b/plugins/deployment-planners/implicit-dedication/test/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java new file mode 100644 index 00000000000..44507600db9 --- /dev/null +++ b/plugins/deployment-planners/implicit-dedication/test/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java @@ -0,0 +1,586 @@ +// 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.implicitplanner; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.test.utils.SpringUtils; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import com.cloud.capacity.CapacityManager; +import com.cloud.capacity.CapacityVO; +import com.cloud.capacity.dao.CapacityDao; +import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.dc.ClusterDetailsDao; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.HostPodDao; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.deploy.ImplicitDedicationPlanner; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.resource.ResourceManager; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; +import com.cloud.storage.StorageManager; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.GuestOSCategoryDao; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.UserContext; +import com.cloud.utils.Pair; +import com.cloud.utils.component.ComponentContext; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachineProfileImpl; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader = AnnotationConfigContextLoader.class) +public class ImplicitPlannerTest { + + @Inject + ImplicitDedicationPlanner planner = new ImplicitDedicationPlanner(); + @Inject + HostDao hostDao; + @Inject + DataCenterDao dcDao; + @Inject + HostPodDao podDao; + @Inject + ClusterDao clusterDao; + @Inject + GuestOSDao guestOSDao; + @Inject + GuestOSCategoryDao guestOSCategoryDao; + @Inject + DiskOfferingDao diskOfferingDao; + @Inject + StoragePoolHostDao poolHostDao; + @Inject + UserVmDao vmDao; + @Inject + VMInstanceDao vmInstanceDao; + @Inject + VolumeDao volsDao; + @Inject + CapacityManager capacityMgr; + @Inject + ConfigurationDao configDao; + @Inject + PrimaryDataStoreDao storagePoolDao; + @Inject + CapacityDao capacityDao; + @Inject + AccountManager accountMgr; + @Inject + StorageManager storageMgr; + @Inject + DataStoreManager dataStoreMgr; + @Inject + ClusterDetailsDao clusterDetailsDao; + @Inject + ServiceOfferingDao serviceOfferingDao; + @Inject + ServiceOfferingDetailsDao serviceOfferingDetailsDao; + @Inject + ResourceManager resourceMgr; + + private static long domainId = 5L; + long dataCenterId = 1L; + long accountId = 200L; + long offeringId = 12L; + int noOfCpusInOffering = 1; + int cpuSpeedInOffering = 500; + int ramInOffering = 512; + AccountVO acct = new AccountVO(accountId); + + @BeforeClass + public static void setUp() throws ConfigurationException { + } + + @Before + public void testSetUp() { + ComponentContext.initComponentsLifeCycle(); + + acct.setType(Account.ACCOUNT_TYPE_NORMAL); + acct.setAccountName("user1"); + acct.setDomainId(domainId); + acct.setId(accountId); + + UserContext.registerContext(1, acct, null, true); + } + + @Test + public void checkWhenDcInAvoidList() throws InsufficientServerCapacityException { + DataCenterVO mockDc = mock(DataCenterVO.class); + ExcludeList avoids = mock(ExcludeList.class); + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + + when(avoids.shouldAvoid(mockDc)).thenReturn(true); + when(vmProfile.getVirtualMachine()).thenReturn(vm); + when(vm.getDataCenterId()).thenReturn(1L); + when(dcDao.findById(1L)).thenReturn(mockDc); + + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + assertTrue("Cluster list should be null/empty if the dc is in avoid list", + (clusterList == null || clusterList.isEmpty())); + } + + @Test + public void checkStrictModeWithCurrentAccountVmsPresent() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(false); + + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster 2 and 3 are not in the cluster list. + // Host 6 and 7 should also be in avoid list. + assertFalse("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + boolean foundNeededCluster = false; + for (Long cluster : clusterList) { + if (cluster != 1) { + fail("Found a cluster that shouldn't have been present, cluster id : " + cluster); + }else { + foundNeededCluster = true; + } + } + assertTrue("Didn't find cluster 1 in the list. It should have been present", foundNeededCluster); + + Set hostsInAvoidList = avoids.getHostsToAvoid(); + assertFalse("Host 5 shouldn't have be in the avoid list, but it is present", hostsInAvoidList.contains(5L)); + Set hostsThatShouldBeInAvoidList = new HashSet(); + hostsThatShouldBeInAvoidList.add(6L); + hostsThatShouldBeInAvoidList.add(7L); + assertTrue("Hosts 6 and 7 that should have been present were not found in avoid list" , + hostsInAvoidList.containsAll(hostsThatShouldBeInAvoidList)); + } + + @Test + public void checkStrictModeHostWithCurrentAccountVmsFull() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(false); + + // Mark the host 5 with current account vms to be in avoid list. + avoids.addHost(5L); + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster 1 and 3 are not in the cluster list. + // Host 5 and 7 should also be in avoid list. + assertFalse("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + boolean foundNeededCluster = false; + for (Long cluster : clusterList) { + if (cluster != 2) { + fail("Found a cluster that shouldn't have been present, cluster id : " + cluster); + }else { + foundNeededCluster = true; + } + } + assertTrue("Didn't find cluster 2 in the list. It should have been present", foundNeededCluster); + + Set hostsInAvoidList = avoids.getHostsToAvoid(); + assertFalse("Host 6 shouldn't have be in the avoid list, but it is present", hostsInAvoidList.contains(6L)); + Set hostsThatShouldBeInAvoidList = new HashSet(); + hostsThatShouldBeInAvoidList.add(5L); + hostsThatShouldBeInAvoidList.add(7L); + assertTrue("Hosts 5 and 7 that should have been present were not found in avoid list" , + hostsInAvoidList.containsAll(hostsThatShouldBeInAvoidList)); + } + + @Test + public void checkStrictModeNoHostsAvailable() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(false); + + // Mark the host 5 and 6 to be in avoid list. + avoids.addHost(5L); + avoids.addHost(6L); + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster list is empty. + assertTrue("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + } + + @Test + public void checkPreferredModePreferredHostAvailable() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(true); + + // Mark the host 5 and 6 to be in avoid list. + avoids.addHost(5L); + avoids.addHost(6L); + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster 1 and 2 are not in the cluster list. + // Host 5 and 6 should also be in avoid list. + assertFalse("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + boolean foundNeededCluster = false; + for (Long cluster : clusterList) { + if (cluster != 3) { + fail("Found a cluster that shouldn't have been present, cluster id : " + cluster); + } else { + foundNeededCluster = true; + } + } + assertTrue("Didn't find cluster 3 in the list. It should have been present", foundNeededCluster); + + Set hostsInAvoidList = avoids.getHostsToAvoid(); + assertFalse("Host 7 shouldn't have be in the avoid list, but it is present", hostsInAvoidList.contains(7L)); + Set hostsThatShouldBeInAvoidList = new HashSet(); + hostsThatShouldBeInAvoidList.add(5L); + hostsThatShouldBeInAvoidList.add(6L); + assertTrue("Hosts 5 and 6 that should have been present were not found in avoid list" , + hostsInAvoidList.containsAll(hostsThatShouldBeInAvoidList)); + } + + @Test + public void checkPreferredModeNoHostsAvailable() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(false); + + // Mark the host 5, 6 and 7 to be in avoid list. + avoids.addHost(5L); + avoids.addHost(6L); + avoids.addHost(7L); + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster list is empty. + assertTrue("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + } + + private void initializeForTest(VirtualMachineProfileImpl vmProfile, DataCenterDeployment plan) { + DataCenterVO mockDc = mock(DataCenterVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + UserVmVO userVm = mock(UserVmVO.class); + ServiceOfferingVO offering = mock(ServiceOfferingVO.class); + + AccountVO account = mock(AccountVO.class); + when(account.getId()).thenReturn(accountId); + when(account.getAccountId()).thenReturn(accountId); + when(vmProfile.getOwner()).thenReturn(account); + when(vmProfile.getVirtualMachine()).thenReturn(vm); + when(vmProfile.getId()).thenReturn(12L); + when(vmDao.findById(12L)).thenReturn(userVm); + when(userVm.getAccountId()).thenReturn(accountId); + + when(vm.getDataCenterId()).thenReturn(dataCenterId); + when(dcDao.findById(1L)).thenReturn(mockDc); + when(plan.getDataCenterId()).thenReturn(dataCenterId); + when(plan.getClusterId()).thenReturn(null); + when(plan.getPodId()).thenReturn(null); + when(configDao.getValue(anyString())).thenReturn("false").thenReturn("CPU"); + + // Mock offering details. + when(vmProfile.getServiceOffering()).thenReturn(offering); + when(offering.getId()).thenReturn(offeringId); + when(vmProfile.getServiceOfferingId()).thenReturn(offeringId); + when(offering.getCpu()).thenReturn(noOfCpusInOffering); + when(offering.getSpeed()).thenReturn(cpuSpeedInOffering); + when(offering.getRamSize()).thenReturn(ramInOffering); + + List clustersWithEnoughCapacity = new ArrayList(); + clustersWithEnoughCapacity.add(1L); + clustersWithEnoughCapacity.add(2L); + clustersWithEnoughCapacity.add(3L); + when(capacityDao.listClustersInZoneOrPodByHostCapacities(dataCenterId, noOfCpusInOffering * cpuSpeedInOffering, + ramInOffering * 1024L * 1024L, CapacityVO.CAPACITY_TYPE_CPU, true)).thenReturn(clustersWithEnoughCapacity); + + Map clusterCapacityMap = new HashMap(); + clusterCapacityMap.put(1L, 2048D); + clusterCapacityMap.put(2L, 2048D); + clusterCapacityMap.put(3L, 2048D); + Pair, Map> clustersOrderedByCapacity = + new Pair, Map>(clustersWithEnoughCapacity, clusterCapacityMap); + when(capacityDao.orderClustersByAggregateCapacity(dataCenterId, CapacityVO.CAPACITY_TYPE_CPU, + true)).thenReturn(clustersOrderedByCapacity); + + List disabledClusters = new ArrayList(); + List clustersWithDisabledPods = new ArrayList(); + when(clusterDao.listDisabledClusters(dataCenterId, null)).thenReturn(disabledClusters); + when(clusterDao.listClustersWithDisabledPods(dataCenterId)).thenReturn(clustersWithDisabledPods); + } + + private void initializeForImplicitPlannerTest(boolean preferred) { + String plannerMode = new String("Strict"); + if (preferred) { + plannerMode = new String("Preferred"); + } + + Map details = new HashMap(); + details.put("ImplicitDedicationMode", plannerMode); + when(serviceOfferingDetailsDao.findDetails(offeringId)).thenReturn(details); + + // Initialize hosts in clusters + HostVO host1 = mock(HostVO.class); + when(host1.getId()).thenReturn(5L); + HostVO host2 = mock(HostVO.class); + when(host2.getId()).thenReturn(6L); + HostVO host3 = mock(HostVO.class); + when(host3.getId()).thenReturn(7L); + List hostsInCluster1 = new ArrayList(); + List hostsInCluster2 = new ArrayList(); + List hostsInCluster3 = new ArrayList(); + hostsInCluster1.add(host1); + hostsInCluster2.add(host2); + hostsInCluster3.add(host3); + when(resourceMgr.listAllHostsInCluster(1)).thenReturn(hostsInCluster1); + when(resourceMgr.listAllHostsInCluster(2)).thenReturn(hostsInCluster2); + when(resourceMgr.listAllHostsInCluster(3)).thenReturn(hostsInCluster3); + + // Mock vms on each host. + long offeringIdForVmsOfThisAccount = 15L; + long offeringIdForVmsOfOtherAccount = 16L; + UserVmVO vm1 = mock(UserVmVO.class); + when(vm1.getAccountId()).thenReturn(accountId); + when(vm1.getServiceOfferingId()).thenReturn(offeringIdForVmsOfThisAccount); + UserVmVO vm2 = mock(UserVmVO.class); + when(vm2.getAccountId()).thenReturn(accountId); + when(vm2.getServiceOfferingId()).thenReturn(offeringIdForVmsOfThisAccount); + // Vm from different account + UserVmVO vm3 = mock(UserVmVO.class); + when(vm3.getAccountId()).thenReturn(201L); + when(vm3.getServiceOfferingId()).thenReturn(offeringIdForVmsOfOtherAccount); + List userVmsForHost1 = new ArrayList(); + List userVmsForHost2 = new ArrayList(); + List userVmsForHost3 = new ArrayList(); + List stoppedVmsForHost = new ArrayList(); + // Host 2 is empty. + userVmsForHost1.add(vm1); + userVmsForHost1.add(vm2); + userVmsForHost3.add(vm3); + when(vmDao.listUpByHostId(5L)).thenReturn(userVmsForHost1); + when(vmDao.listUpByHostId(6L)).thenReturn(userVmsForHost2); + when(vmDao.listUpByHostId(7L)).thenReturn(userVmsForHost3); + when(vmDao.listByLastHostId(5L)).thenReturn(stoppedVmsForHost); + when(vmDao.listByLastHostId(6L)).thenReturn(stoppedVmsForHost); + when(vmDao.listByLastHostId(7L)).thenReturn(stoppedVmsForHost); + + // Mock the offering with which the vm was created. + ServiceOfferingVO offeringForVmOfThisAccount = mock(ServiceOfferingVO.class); + when(serviceOfferingDao.findByIdIncludingRemoved(offeringIdForVmsOfThisAccount)).thenReturn(offeringForVmOfThisAccount); + when(offeringForVmOfThisAccount.getDeploymentPlanner()).thenReturn(planner.getName()); + + ServiceOfferingVO offeringForVMOfOtherAccount = mock(ServiceOfferingVO.class); + when(serviceOfferingDao.findByIdIncludingRemoved(offeringIdForVmsOfOtherAccount)).thenReturn(offeringForVMOfOtherAccount); + when(offeringForVMOfOtherAccount.getDeploymentPlanner()).thenReturn("FirstFitPlanner"); + } + + @Configuration + @ComponentScan(basePackageClasses = { ImplicitDedicationPlanner.class }, + includeFilters = {@Filter(value = TestConfiguration.Library.class, type = FilterType.CUSTOM)}, + useDefaultFilters = false) + public static class TestConfiguration extends SpringUtils.CloudStackTestConfiguration { + + @Bean + public HostDao hostDao() { + return Mockito.mock(HostDao.class); + } + + @Bean + public DataCenterDao dcDao() { + return Mockito.mock(DataCenterDao.class); + } + + @Bean + public HostPodDao hostPodDao() { + return Mockito.mock(HostPodDao.class); + } + + @Bean + public ClusterDao clusterDao() { + return Mockito.mock(ClusterDao.class); + } + + @Bean + public GuestOSDao guestOsDao() { + return Mockito.mock(GuestOSDao.class); + } + + @Bean + public GuestOSCategoryDao guestOsCategoryDao() { + return Mockito.mock(GuestOSCategoryDao.class); + } + + @Bean + public DiskOfferingDao diskOfferingDao() { + return Mockito.mock(DiskOfferingDao.class); + } + + @Bean + public StoragePoolHostDao storagePoolHostDao() { + return Mockito.mock(StoragePoolHostDao.class); + } + + @Bean + public UserVmDao userVmDao() { + return Mockito.mock(UserVmDao.class); + } + + @Bean + public VMInstanceDao vmInstanceDao() { + return Mockito.mock(VMInstanceDao.class); + } + + @Bean + public VolumeDao volumeDao() { + return Mockito.mock(VolumeDao.class); + } + + @Bean + public CapacityManager capacityManager() { + return Mockito.mock(CapacityManager.class); + } + + @Bean + public ConfigurationDao configurationDao() { + return Mockito.mock(ConfigurationDao.class); + } + + @Bean + public PrimaryDataStoreDao primaryDataStoreDao() { + return Mockito.mock(PrimaryDataStoreDao.class); + } + + @Bean + public CapacityDao capacityDao() { + return Mockito.mock(CapacityDao.class); + } + + @Bean + public AccountManager accountManager() { + return Mockito.mock(AccountManager.class); + } + + @Bean + public StorageManager storageManager() { + return Mockito.mock(StorageManager.class); + } + + @Bean + public DataStoreManager dataStoreManager() { + return Mockito.mock(DataStoreManager.class); + } + + @Bean + public ClusterDetailsDao clusterDetailsDao() { + return Mockito.mock(ClusterDetailsDao.class); + } + + @Bean + public ServiceOfferingDao serviceOfferingDao() { + return Mockito.mock(ServiceOfferingDao.class); + } + + @Bean + public ServiceOfferingDetailsDao serviceOfferingDetailsDao() { + return Mockito.mock(ServiceOfferingDetailsDao.class); + } + + @Bean + public ResourceManager resourceManager() { + return Mockito.mock(ResourceManager.class); + } + + public static class Library implements TypeFilter { + @Override + public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException { + ComponentScan cs = TestConfiguration.class.getAnnotation(ComponentScan.class); + return SpringUtils.includedInBasePackageClasses(mdr.getClassMetadata().getClassName(), cs); + } + } + } +} \ No newline at end of file diff --git a/plugins/pom.xml b/plugins/pom.xml index e49fac9533a..2efa2488e86 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -38,6 +38,7 @@ affinity-group-processors/host-anti-affinity deployment-planners/user-concentrated-pod deployment-planners/user-dispersing + deployment-planners/implicit-dedication host-allocators/random hypervisors/ovm hypervisors/xen diff --git a/server/src/com/cloud/configuration/ConfigurationManager.java b/server/src/com/cloud/configuration/ConfigurationManager.java index d0ae914c20f..8db037b24ff 100755 --- a/server/src/com/cloud/configuration/ConfigurationManager.java +++ b/server/src/com/cloud/configuration/ConfigurationManager.java @@ -80,10 +80,11 @@ public interface ConfigurationManager extends ConfigurationService, Manager { * @param id * @param useVirtualNetwork * @param deploymentPlanner + * @param details * @return ID */ ServiceOfferingVO createServiceOffering(long userId, boolean isSystem, VirtualMachine.Type vm_typeType, String name, int cpu, int ramSize, int speed, String displayText, boolean localStorageRequired, - boolean offerHA, boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner); + boolean offerHA, boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner, Map details); /** * Creates a new disk offering diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 9e0c847ed57..52d617646af 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -39,7 +39,6 @@ import javax.naming.NamingException; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; - import com.cloud.dc.*; import com.cloud.dc.dao.*; import com.cloud.user.*; @@ -105,7 +104,6 @@ import com.cloud.dc.dao.DcDetailsDao; import com.cloud.dc.dao.HostPodDao; import com.cloud.dc.dao.PodVlanMapDao; import com.cloud.dc.dao.VlanDao; - import com.cloud.deploy.DataCenterDeployment; import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; @@ -165,6 +163,7 @@ import com.cloud.server.ConfigurationServer; import com.cloud.server.ManagementService; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.SwiftVO; import com.cloud.storage.dao.DiskOfferingDao; @@ -277,6 +276,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject ServiceOfferingDao _serviceOfferingDao; @Inject + ServiceOfferingDetailsDao _serviceOfferingDetailsDao; + @Inject DiskOfferingDao _diskOfferingDao; @Inject NetworkOfferingDao _networkOfferingDao; @@ -2050,19 +2051,26 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } - return createServiceOffering(userId, cmd.getIsSystem(), vmType, cmd.getServiceOfferingName(), cpuNumber.intValue(), memory.intValue(), cpuSpeed.intValue(), cmd.getDisplayText(), - localStorageRequired, offerHA, limitCpuUse, volatileVm, cmd.getTags(), cmd.getDomainId(), cmd.getHostTag(), cmd.getNetworkRate(), cmd.getDeploymentPlanner()); + return createServiceOffering(userId, cmd.getIsSystem(), vmType, cmd.getServiceOfferingName(), + cpuNumber.intValue(), memory.intValue(), cpuSpeed.intValue(), cmd.getDisplayText(), + localStorageRequired, offerHA, limitCpuUse, volatileVm, cmd.getTags(), cmd.getDomainId(), + cmd.getHostTag(), cmd.getNetworkRate(), cmd.getDeploymentPlanner(), cmd.getDetails()); } @Override @ActionEvent(eventType = EventTypes.EVENT_SERVICE_OFFERING_CREATE, eventDescription = "creating service offering") - public ServiceOfferingVO createServiceOffering(long userId, boolean isSystem, VirtualMachine.Type vm_type, String name, int cpu, int ramSize, int speed, String displayText, - boolean localStorageRequired, boolean offerHA, boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner) { + public ServiceOfferingVO createServiceOffering(long userId, boolean isSystem, VirtualMachine.Type vm_type, + String name, int cpu, int ramSize, int speed, String displayText, boolean localStorageRequired, + boolean offerHA, boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, + Integer networkRate, String deploymentPlanner, Map details) { tags = cleanupTags(tags); ServiceOfferingVO offering = new ServiceOfferingVO(name, cpu, ramSize, speed, networkRate, null, offerHA, limitResourceUse, volatileVm, displayText, localStorageRequired, false, tags, isSystem, vm_type, domainId, hostTag, deploymentPlanner); if ((offering = _serviceOfferingDao.persist(offering)) != null) { + if (details != null) { + _serviceOfferingDetailsDao.persist(offering.getId(), details); + } UserContext.current().setEventDetails("Service offering id=" + offering.getId()); return offering; } else { diff --git a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java index ba18fa1c11d..4fb182aae14 100755 --- a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java +++ b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java @@ -431,7 +431,7 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu */ @Override public ServiceOfferingVO createServiceOffering(long userId, boolean isSystem, Type vm_typeType, String name, int cpu, int ramSize, int speed, String displayText, boolean localStorageRequired, boolean offerHA, - boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner) { + boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner, Map details) { // TODO Auto-generated method stub return null; } diff --git a/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java b/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java index 7ffbe32d8bd..a8256990973 100644 --- a/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java +++ b/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java @@ -19,14 +19,8 @@ package org.apache.cloudstack.networkoffering; import java.io.IOException; -import com.cloud.dc.ClusterDetailsDao; -import com.cloud.dc.dao.*; -import com.cloud.server.ConfigurationServer; -import com.cloud.user.*; import org.apache.cloudstack.acl.SecurityChecker; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDaoImpl; -import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; import org.apache.cloudstack.test.utils.SpringUtils; import org.mockito.Mockito; import org.springframework.context.annotation.Bean; @@ -44,6 +38,18 @@ import com.cloud.api.query.dao.UserAccountJoinDaoImpl; import com.cloud.capacity.dao.CapacityDaoImpl; import com.cloud.cluster.agentlb.dao.HostTransferMapDaoImpl; import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.dc.ClusterDetailsDao; +import com.cloud.dc.dao.AccountVlanMapDaoImpl; +import com.cloud.dc.dao.ClusterDaoImpl; +import com.cloud.dc.dao.DataCenterDaoImpl; +import com.cloud.dc.dao.DataCenterIpAddressDaoImpl; +import com.cloud.dc.dao.DataCenterLinkLocalIpAddressDao; +import com.cloud.dc.dao.DataCenterVnetDaoImpl; +import com.cloud.dc.dao.DcDetailsDaoImpl; +import com.cloud.dc.dao.HostPodDaoImpl; +import com.cloud.dc.dao.PodVlanDaoImpl; +import com.cloud.dc.dao.PodVlanMapDaoImpl; +import com.cloud.dc.dao.VlanDaoImpl; import com.cloud.domain.dao.DomainDaoImpl; import com.cloud.event.dao.UsageEventDaoImpl; import com.cloud.host.dao.HostDaoImpl; @@ -80,10 +86,11 @@ import com.cloud.network.vpc.dao.PrivateIpDaoImpl; import com.cloud.network.vpn.RemoteAccessVpnService; import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; -import com.cloud.offerings.dao.NetworkOfferingServiceMapDaoImpl; import com.cloud.projects.ProjectManager; +import com.cloud.server.ConfigurationServer; import com.cloud.server.ManagementService; import com.cloud.service.dao.ServiceOfferingDaoImpl; +import com.cloud.service.dao.ServiceOfferingDetailsDaoImpl; import com.cloud.storage.dao.DiskOfferingDaoImpl; import com.cloud.storage.dao.S3DaoImpl; import com.cloud.storage.dao.SnapshotDaoImpl; @@ -94,6 +101,11 @@ import com.cloud.storage.s3.S3Manager; import com.cloud.storage.secondary.SecondaryStorageVmManager; import com.cloud.storage.swift.SwiftManager; import com.cloud.tags.dao.ResourceTagsDaoImpl; +import com.cloud.user.AccountDetailsDao; +import com.cloud.user.AccountManager; +import com.cloud.user.ResourceLimitService; +import com.cloud.user.UserContext; +import com.cloud.user.UserContextInitializer; import com.cloud.user.dao.AccountDaoImpl; import com.cloud.user.dao.UserDaoImpl; import com.cloud.vm.dao.InstanceGroupDaoImpl; @@ -110,6 +122,7 @@ import com.cloud.vm.dao.VMInstanceDaoImpl; DomainDaoImpl.class, SwiftDaoImpl.class, ServiceOfferingDaoImpl.class, + ServiceOfferingDetailsDaoImpl.class, VlanDaoImpl.class, IPAddressDaoImpl.class, ResourceTagsDaoImpl.class, diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index 442a5446be5..fe66207e5e5 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -393,7 +393,16 @@ CREATE TABLE `cloud`.`vm_snapshots` ( ALTER TABLE `cloud`.`hypervisor_capabilities` ADD COLUMN `vm_snapshot_enabled` tinyint(1) DEFAULT 0 NOT NULL COMMENT 'Whether VM snapshot is supported by hypervisor'; UPDATE `cloud`.`hypervisor_capabilities` SET `vm_snapshot_enabled`=1 WHERE `hypervisor_type` in ('VMware', 'XenServer'); - +CREATE TABLE `cloud`.`service_offering_details` ( + `id` bigint unsigned NOT NULL auto_increment, + `service_offering_id` bigint unsigned NOT NULL COMMENT 'service offering id', + `name` varchar(255) NOT NULL, + `value` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_service_offering_details__service_offering_id` FOREIGN KEY (`service_offering_id`) REFERENCES `service_offering`(`id`) ON DELETE CASCADE, + CONSTRAINT UNIQUE KEY `uk_service_offering_id_name` (`service_offering_id`, `name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + DROP VIEW IF EXISTS `cloud`.`user_vm_view`; CREATE VIEW `cloud`.`user_vm_view` AS select diff --git a/test/integration/component/test_implicit_planner.py b/test/integration/component/test_implicit_planner.py new file mode 100644 index 00000000000..ffcd248b462 --- /dev/null +++ b/test/integration/component/test_implicit_planner.py @@ -0,0 +1,232 @@ +# 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. +""" P1 tests for Storage motion +""" +#Import Local Modules +import marvin +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.remoteSSHClient import remoteSSHClient +from marvin.integration.lib.utils import * +from marvin.integration.lib.base import * +from marvin.integration.lib.common import * +from nose.plugins.attrib import attr +#Import System modules +import time + +_multiprocess_shared_ = True +class Services: + """Test VM Life Cycle Services + """ + + def __init__(self): + self.services = { + "disk_offering":{ + "displaytext": "Small", + "name": "Small", + "disksize": 1 + }, + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended in create account to + # ensure unique username generated each time + "password": "password", + }, + "small": + # Create a small virtual machine instance with disk offering + { + "displayname": "testserver", + "username": "root", # VM creds for SSH + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "service_offerings": + { + "implicitplanner": + { + # Small service offering ID to for change VM + # service offering from medium to small + "name": "Implicit Strict", + "displaytext": "Implicit Strict", + "cpunumber": 1, + "cpuspeed": 500, + "memory": 512, + "deploymentplanner": "ImplicitDedicationPlanner" + } + }, + "template": { + "displaytext": "Cent OS Template", + "name": "Cent OS Template", + "passwordenabled": True, + }, + "diskdevice": '/dev/xvdd', + # Disk device where ISO is attached to instance + "mount_dir": "/mnt/tmp", + "sleep": 60, + "timeout": 10, + #Migrate VM to hostid + "ostype": 'CentOS 5.3 (64-bit)', + # CentOS 5.3 (64-bit) + } + +class TestImplicitPlanner(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.api_client = super(TestImplicitPlanner, cls).getClsTestClient().getApiClient() + cls.services = Services().services + + # Get Zone, Domain and templates + domain = get_domain(cls.api_client, cls.services) + cls.zone = get_zone(cls.api_client, cls.services) + cls.services['mode'] = cls.zone.networktype + + template = get_template( + cls.api_client, + cls.zone.id, + cls.services["ostype"] + ) + # Set Zones and disk offerings + cls.services["small"]["zoneid"] = cls.zone.id + cls.services["small"]["template"] = template.id + + # Create VMs, NAT Rules etc + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=domain.id + ) + + cls.small_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offerings"]["implicitplanner"] + ) + + cls._cleanup = [ + cls.small_offering, + cls.account + ] + + @classmethod + def tearDownClass(cls): + cls.api_client = super(TestImplicitPlanner, cls).getClsTestClient().getApiClient() + cleanup_resources(cls.api_client, cls._cleanup) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + + def tearDown(self): + #Clean up, terminate the created ISOs + cleanup_resources(self.apiclient, self.cleanup) + return + + # This test requires multi host and at least one host which is empty (no vms should + # be running on that host). It uses an implicit planner to deploy instances and the + # instances of a new account should go to an host that doesn't have vms of any other + # account. + @attr(tags = ["advanced", "basic", "multihosts", "implicitplanner"]) + def test_01_deploy_vm_with_implicit_planner(self): + """Test implicit planner is placing vms of an account on implicitly dedicated hosts. + """ + # Validate the following + # 1. Deploy a vm using implicit planner. It should go on to a + # host that is empty (not running vms of any other account) + # 2. Deploy another vm it should get deployed on the same host. + + #create a virtual machine + virtual_machine_1 = VirtualMachine.create( + self.api_client, + self.services["small"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.small_offering.id, + mode=self.services["mode"] + ) + + list_vm_response_1 = list_virtual_machines( + self.apiclient, + id=virtual_machine_1.id + ) + self.assertEqual( + isinstance(list_vm_response_1, list), + True, + "Check list response returns a valid list" + ) + + self.assertNotEqual( + list_vm_response_1, + None, + "Check virtual machine is listVirtualMachines" + ) + + vm_response_1 = list_vm_response_1[0] + + self.assertEqual( + vm_response_1.id, + virtual_machine_1.id, + "Check virtual machine ID of VM" + ) + + virtual_machine_2 = VirtualMachine.create( + self.api_client, + self.services["small"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.small_offering.id, + mode=self.services["mode"] + ) + + list_vm_response_2 = list_virtual_machines( + self.apiclient, + id=virtual_machine_2.id + ) + self.assertEqual( + isinstance(list_vm_response_2, list), + True, + "Check list response returns a valid list" + ) + + self.assertNotEqual( + list_vm_response_2, + None, + "Check virtual machine is listVirtualMachines" + ) + + vm_response_2 = list_vm_response_2[0] + + self.assertEqual( + vm_response_2.id, + virtual_machine_2.id, + "Check virtual machine ID of VM" + ) + + self.assertEqual( + vm_response_1.hostid, + vm_response_2.hostid, + "Check both vms have the same host id" + ) + return \ No newline at end of file diff --git a/tools/marvin/marvin/integration/lib/base.py b/tools/marvin/marvin/integration/lib/base.py index ecdc8412fdb..a811f144980 100755 --- a/tools/marvin/marvin/integration/lib/base.py +++ b/tools/marvin/marvin/integration/lib/base.py @@ -1268,6 +1268,10 @@ class ServiceOffering: if "tags" in services: cmd.tags = services["tags"] + + if "deploymentplanner" in services: + cmd.deploymentplanner = services["deploymentplanner"] + # Service Offering private to that domain if domainid: cmd.domainid = domainid