mirror of https://github.com/apache/cloudstack.git
new plugins: Add non-strict affinity groups (#6845)
This commit is contained in:
parent
440d7805cb
commit
889045fba5
|
|
@ -21,7 +21,9 @@ import com.cloud.vm.ReservationContext;
|
|||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DataCenterDeployment implements DeploymentPlan {
|
||||
long _dcId;
|
||||
|
|
@ -35,6 +37,7 @@ public class DataCenterDeployment implements DeploymentPlan {
|
|||
ReservationContext _context;
|
||||
List<Long> preferredHostIds = new ArrayList<>();
|
||||
boolean migrationPlan;
|
||||
Map<Long, Integer> hostPriorities = new HashMap<>();
|
||||
|
||||
public DataCenterDeployment(long dataCenterId) {
|
||||
this(dataCenterId, null, null, null, null, null);
|
||||
|
|
@ -124,4 +127,32 @@ public class DataCenterDeployment implements DeploymentPlan {
|
|||
"migrationPlan");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adjustHostPriority(Long hostId, HostPriorityAdjustment adjustment) {
|
||||
Integer currentPriority = hostPriorities.get(hostId);
|
||||
if (currentPriority == null) {
|
||||
currentPriority = DEFAULT_HOST_PRIORITY;
|
||||
} else if (currentPriority.equals(PROHIBITED_HOST_PRIORITY)) {
|
||||
return;
|
||||
}
|
||||
if (HostPriorityAdjustment.HIGHER.equals(adjustment)) {
|
||||
hostPriorities.put(hostId, currentPriority + ADJUST_HOST_PRIORITY_BY);
|
||||
} else if (HostPriorityAdjustment.LOWER.equals(adjustment)) {
|
||||
hostPriorities.put(hostId, currentPriority - ADJUST_HOST_PRIORITY_BY);
|
||||
} else if (HostPriorityAdjustment.DEFAULT.equals(adjustment)) {
|
||||
hostPriorities.put(hostId, DEFAULT_HOST_PRIORITY);
|
||||
} else if (HostPriorityAdjustment.PROHIBIT.equals(adjustment)) {
|
||||
hostPriorities.put(hostId, PROHIBITED_HOST_PRIORITY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, Integer> getHostPriorities() {
|
||||
return hostPriorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHostPriorities(Map<Long, Integer> priorities) {
|
||||
this.hostPriorities = priorities;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,23 @@ import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
|||
import com.cloud.vm.ReservationContext;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*/
|
||||
public interface DeploymentPlan {
|
||||
|
||||
Integer DEFAULT_HOST_PRIORITY = 0;
|
||||
Integer PROHIBITED_HOST_PRIORITY = Integer.MIN_VALUE;
|
||||
Integer ADJUST_HOST_PRIORITY_BY = 1;
|
||||
|
||||
enum HostPriorityAdjustment {
|
||||
HIGHER,
|
||||
DEFAULT,
|
||||
LOWER,
|
||||
PROHIBIT
|
||||
}
|
||||
|
||||
// TODO: This interface is not fully developed. It really
|
||||
// number of parameters to be specified.
|
||||
|
||||
|
|
@ -73,4 +86,10 @@ public interface DeploymentPlan {
|
|||
List<Long> getPreferredHosts();
|
||||
|
||||
boolean isMigrationPlan();
|
||||
|
||||
void adjustHostPriority(Long hostId, HostPriorityAdjustment priority);
|
||||
|
||||
Map<Long, Integer> getHostPriorities();
|
||||
|
||||
void setHostPriorities(Map<Long, Integer> priorities);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ public interface VirtualMachineProfile {
|
|||
public static final Param BootType = new Param("BootType");
|
||||
public static final Param BootIntoSetup = new Param("enterHardwareSetup");
|
||||
public static final Param PreserveNics = new Param("PreserveNics");
|
||||
public static final Param ConsiderLastHost = new Param("ConsiderLastHost");
|
||||
|
||||
private String name;
|
||||
|
||||
|
|
|
|||
|
|
@ -634,6 +634,7 @@ public class ApiConstants {
|
|||
public static final String PURPOSE = "purpose";
|
||||
public static final String IS_TAGGED = "istagged";
|
||||
public static final String INSTANCE_NAME = "instancename";
|
||||
public static final String CONSIDER_LAST_HOST = "considerlasthost";
|
||||
public static final String START_VM = "startvm";
|
||||
public static final String HA_HOST = "hahost";
|
||||
public static final String CUSTOM_DISK_OFF_MIN_SIZE = "customdiskofferingminsize";
|
||||
|
|
|
|||
|
|
@ -82,6 +82,13 @@ public class StartVMCmd extends BaseAsyncCmd implements UserCmd {
|
|||
since = "3.0.1")
|
||||
private Long hostId;
|
||||
|
||||
@Parameter(name = ApiConstants.CONSIDER_LAST_HOST,
|
||||
type = CommandType.BOOLEAN,
|
||||
description = "True by default, CloudStack will firstly try to start the VM on the last host where it run on before stopping, if destination host is not specified. " +
|
||||
"If false, CloudStack will not consider the last host and start the VM by normal process.",
|
||||
since = "4.18.0")
|
||||
private Boolean considerLastHost;
|
||||
|
||||
@Parameter(name = ApiConstants.DEPLOYMENT_PLANNER, type = CommandType.STRING, description = "Deployment planner to use for vm allocation. Available to ROOT admin only", since = "4.4", authorized = { RoleType.Admin })
|
||||
private String deploymentPlanner;
|
||||
|
||||
|
|
@ -112,6 +119,10 @@ public class StartVMCmd extends BaseAsyncCmd implements UserCmd {
|
|||
return bootIntoSetup;
|
||||
}
|
||||
|
||||
public Boolean getConsiderLastHost() {
|
||||
return considerLastHost;
|
||||
}
|
||||
|
||||
// ///////////////////////////////////////////////////
|
||||
// ///////////// API Implementation///////////////////
|
||||
// ///////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 junit.framework.TestCase;
|
||||
import org.junit.Assert;
|
||||
|
||||
public class DataCenterDeploymentTest extends TestCase {
|
||||
|
||||
private long zoneId = 1L;
|
||||
private long hostId = 2L;
|
||||
|
||||
DataCenterDeployment plan = new DataCenterDeployment(zoneId);
|
||||
|
||||
private void verifyHostPriority(Integer priority) {
|
||||
Assert.assertEquals(priority, plan.getHostPriorities().get(hostId));
|
||||
}
|
||||
|
||||
public void testHostPriorities() {
|
||||
verifyHostPriority(null);
|
||||
|
||||
plan.adjustHostPriority(hostId, DeploymentPlan.HostPriorityAdjustment.DEFAULT);
|
||||
verifyHostPriority(0);
|
||||
|
||||
plan.adjustHostPriority(hostId, DeploymentPlan.HostPriorityAdjustment.HIGHER);
|
||||
verifyHostPriority(1);
|
||||
|
||||
plan.adjustHostPriority(hostId, DeploymentPlan.HostPriorityAdjustment.LOWER);
|
||||
verifyHostPriority(0);
|
||||
|
||||
plan.adjustHostPriority(hostId, DeploymentPlan.HostPriorityAdjustment.LOWER);
|
||||
verifyHostPriority(-1);
|
||||
|
||||
plan.adjustHostPriority(hostId, DeploymentPlan.HostPriorityAdjustment.HIGHER);
|
||||
verifyHostPriority(0);
|
||||
|
||||
plan.adjustHostPriority(hostId, DeploymentPlan.HostPriorityAdjustment.HIGHER);
|
||||
verifyHostPriority(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -478,6 +478,16 @@
|
|||
<artifactId>cloud-plugin-host-affinity</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-plugin-non-strict-host-anti-affinity</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-plugin-non-strict-host-affinity</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-plugin-api-solidfire-intg-test</artifactId>
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@
|
|||
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
|
||||
<property name="orderConfigKey" value="affinity.processors.order" />
|
||||
<property name="orderConfigDefault"
|
||||
value="HostAntiAffinityProcessor,ExplicitDedicationProcessor,HostAffinityProcessor" />
|
||||
value="HostAntiAffinityProcessor,ExplicitDedicationProcessor,HostAffinityProcessor,NonStrictHostAntiAffinityProcessor,NonStrictHostAffinityProcessor" />
|
||||
<property name="excludeKey" value="affinity.processors.exclude" />
|
||||
</bean>
|
||||
|
||||
|
|
|
|||
|
|
@ -20,10 +20,14 @@ import com.cloud.dc.DataCenter;
|
|||
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
||||
import com.cloud.exception.AffinityConflictException;
|
||||
import com.cloud.exception.InsufficientServerCapacityException;
|
||||
import com.cloud.host.Host;
|
||||
import com.cloud.utils.component.Manager;
|
||||
import com.cloud.vm.VirtualMachineProfile;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface DeploymentPlanningManager extends Manager {
|
||||
|
||||
|
||||
|
|
@ -60,4 +64,6 @@ public interface DeploymentPlanningManager extends Manager {
|
|||
DeploymentPlanner getDeploymentPlannerByName(String plannerName);
|
||||
|
||||
void checkForNonDedicatedResources(VirtualMachineProfile vmProfile, DataCenter dc, ExcludeList avoids);
|
||||
|
||||
void reorderHostsByPriority(Map<Long, Integer> priorities, List<Host> hosts);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1412,6 +1412,10 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
|||
msgBuf.append(String.format("Boot into Setup: %s ", params.get(VirtualMachineProfile.Param.BootIntoSetup)));
|
||||
log = true;
|
||||
}
|
||||
if (params.get(VirtualMachineProfile.Param.ConsiderLastHost) != null) {
|
||||
msgBuf.append(String.format("Consider last host: %s ", params.get(VirtualMachineProfile.Param.ConsiderLastHost)));
|
||||
log = true;
|
||||
}
|
||||
if (log) {
|
||||
s_logger.info(msgBuf.toString());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,6 +158,9 @@ public class VMEntityManagerImpl implements VMEntityManager {
|
|||
vmProfile.getParameters().put(VirtualMachineProfile.Param.BootMode, details.get(VirtualMachineProfile.Param.BootMode.getName()));
|
||||
vmProfile.getParameters().put(VirtualMachineProfile.Param.UefiFlag, details.get(VirtualMachineProfile.Param.UefiFlag.getName()));
|
||||
}
|
||||
if (MapUtils.isNotEmpty(vmEntityVO.getDetails()) && vmEntityVO.getDetails().containsKey(VirtualMachineProfile.Param.ConsiderLastHost.getName())) {
|
||||
vmProfile.getParameters().put(VirtualMachineProfile.Param.ConsiderLastHost, vmEntityVO.getDetails().get(VirtualMachineProfile.Param.ConsiderLastHost.getName()));
|
||||
}
|
||||
DataCenterDeployment plan = new DataCenterDeployment(vm.getDataCenterId(), vm.getPodIdToDeployIn(), null, null, null, null);
|
||||
if (planToDeploy != null && planToDeploy.getDataCenterId() != 0) {
|
||||
plan =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<!--
|
||||
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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>cloud-plugin-non-strict-host-affinity</artifactId>
|
||||
<name>Apache CloudStack Plugin - Non-Strict Host Affinity Processor</name>
|
||||
<parent>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloudstack-plugins</artifactId>
|
||||
<version>4.18.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
// 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.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
|
||||
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
|
||||
import org.apache.cloudstack.engine.cloud.entity.api.db.dao.VMReservationDao;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
|
||||
import com.cloud.configuration.Config;
|
||||
import com.cloud.deploy.DeployDestination;
|
||||
import com.cloud.deploy.DeploymentPlan;
|
||||
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
||||
import com.cloud.exception.AffinityConflictException;
|
||||
import com.cloud.utils.DateUtil;
|
||||
import com.cloud.utils.NumbersUtil;
|
||||
import com.cloud.vm.VMInstanceVO;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.VirtualMachineProfile;
|
||||
import com.cloud.vm.dao.UserVmDao;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
|
||||
public class NonStrictHostAffinityProcessor extends AffinityProcessorBase implements AffinityGroupProcessor {
|
||||
|
||||
private final Logger logger = Logger.getLogger(this.getClass().getName());
|
||||
@Inject
|
||||
protected UserVmDao vmDao;
|
||||
@Inject
|
||||
protected VMInstanceDao vmInstanceDao;
|
||||
@Inject
|
||||
protected AffinityGroupDao affinityGroupDao;
|
||||
@Inject
|
||||
protected AffinityGroupVMMapDao affinityGroupVMMapDao;
|
||||
@Inject
|
||||
protected ConfigurationDao configDao;
|
||||
|
||||
@Inject
|
||||
protected VMReservationDao reservationDao;
|
||||
|
||||
private int vmCapacityReleaseInterval;
|
||||
|
||||
@Override
|
||||
public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid) throws AffinityConflictException {
|
||||
VirtualMachine vm = vmProfile.getVirtualMachine();
|
||||
List<AffinityGroupVMMapVO> vmGroupMappings = affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType());
|
||||
|
||||
for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) {
|
||||
if (vmGroupMapping != null) {
|
||||
processAffinityGroup(vmGroupMapping, plan, vm);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void processAffinityGroup(AffinityGroupVMMapVO vmGroupMapping, DeploymentPlan plan, VirtualMachine vm) {
|
||||
AffinityGroupVO group = affinityGroupDao.findById(vmGroupMapping.getAffinityGroupId());
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Processing affinity group " + group.getName() + " for VM Id: " + vm.getId());
|
||||
}
|
||||
|
||||
List<Long> groupVMIds = affinityGroupVMMapDao.listVmIdsByAffinityGroup(group.getId());
|
||||
groupVMIds.remove(vm.getId());
|
||||
|
||||
for (Long groupVMId : groupVMIds) {
|
||||
VMInstanceVO groupVM = vmInstanceDao.findById(groupVMId);
|
||||
if (groupVM != null && !groupVM.isRemoved()) {
|
||||
processVmInAffinityGroup(plan, groupVM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void processVmInAffinityGroup(DeploymentPlan plan, VMInstanceVO groupVM) {
|
||||
if (groupVM.getHostId() != null) {
|
||||
Integer priority = adjustHostPriority(plan, groupVM.getHostId());
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Updated host %s priority to %s , since VM %s is present on the host",
|
||||
groupVM.getHostId(), priority, groupVM.getId()));
|
||||
}
|
||||
} else if (Arrays.asList(VirtualMachine.State.Starting, VirtualMachine.State.Stopped).contains(groupVM.getState()) && groupVM.getLastHostId() != null) {
|
||||
long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - groupVM.getUpdateTime().getTime()) / 1000;
|
||||
if (secondsSinceLastUpdate < vmCapacityReleaseInterval) {
|
||||
Integer priority = adjustHostPriority(plan, groupVM.getLastHostId());
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Updated host %s priority to %s , since VM %s" +
|
||||
" is present on the host, in %s state but has reserved capacity",
|
||||
groupVM.getLastHostId(), priority, groupVM.getId(), groupVM.getState()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Integer adjustHostPriority(DeploymentPlan plan, Long hostId) {
|
||||
plan.adjustHostPriority(hostId, DeploymentPlan.HostPriorityAdjustment.HIGHER);
|
||||
return plan.getHostPriorities().get(hostId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
|
||||
super.configure(name, params);
|
||||
vmCapacityReleaseInterval = NumbersUtil.parseInt(configDao.getValue(Config.CapacitySkipcountingHours.key()), 3600);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(VirtualMachineProfile vmProfile, DeployDestination plannedDestination) throws AffinityConflictException {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# 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.
|
||||
name=non-strict-host-affinity
|
||||
parent=planner
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<!--
|
||||
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.
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd"
|
||||
>
|
||||
|
||||
<bean id="NonStrictHostAffinityProcessor"
|
||||
class="org.apache.cloudstack.affinity.NonStrictHostAffinityProcessor">
|
||||
<property name="name" value="NonStrictHostAffinityProcessor" />
|
||||
<property name="type" value="non-strict host affinity" />
|
||||
</bean>
|
||||
|
||||
|
||||
</beans>
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* 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 com.cloud.deploy.DataCenterDeployment;
|
||||
import com.cloud.deploy.DeploymentPlan;
|
||||
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
||||
import com.cloud.vm.VMInstanceVO;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.VirtualMachineProfile;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
|
||||
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.Spy;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.nullable;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
public class NonStrictHostAffinityProcessorTest {
|
||||
|
||||
@Spy
|
||||
@InjectMocks
|
||||
NonStrictHostAffinityProcessor processor = new NonStrictHostAffinityProcessor();
|
||||
|
||||
@Mock
|
||||
AffinityGroupVMMapDao _affinityGroupVMMapDao;
|
||||
@Mock
|
||||
AffinityGroupDao affinityGroupDao;
|
||||
@Mock
|
||||
VMInstanceDao vmInstanceDao;
|
||||
|
||||
long vmId = 10L;
|
||||
long vm2Id = 11L;
|
||||
long vm3Id = 12L;
|
||||
long affinityGroupId = 20L;
|
||||
long zoneId = 2L;
|
||||
long host2Id = 3L;
|
||||
long host3Id = 4L;
|
||||
|
||||
@Test
|
||||
public void testProcessWithEmptyPlan() {
|
||||
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
|
||||
when(vm.getId()).thenReturn(vmId);
|
||||
VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
|
||||
when(vmProfile.getVirtualMachine()).thenReturn(vm);
|
||||
|
||||
List<AffinityGroupVMMapVO> vmGroupMappings = new ArrayList<>();
|
||||
vmGroupMappings.add(new AffinityGroupVMMapVO(affinityGroupId, vmId));
|
||||
when(_affinityGroupVMMapDao.findByVmIdType(eq(vmId), nullable(String.class))).thenReturn(vmGroupMappings);
|
||||
|
||||
DataCenterDeployment plan = new DataCenterDeployment(zoneId);
|
||||
ExcludeList avoid = new ExcludeList();
|
||||
|
||||
AffinityGroupVO affinityGroupVO = Mockito.mock(AffinityGroupVO.class);
|
||||
when(affinityGroupDao.findById(affinityGroupId)).thenReturn(affinityGroupVO);
|
||||
when(affinityGroupVO.getId()).thenReturn(affinityGroupId);
|
||||
List<Long> groupVMIds = new ArrayList<>(Arrays.asList(vmId, vm2Id));
|
||||
when(_affinityGroupVMMapDao.listVmIdsByAffinityGroup(affinityGroupId)).thenReturn(groupVMIds);
|
||||
VMInstanceVO vm2 = new VMInstanceVO();
|
||||
when(vmInstanceDao.findById(vm2Id)).thenReturn(vm2);
|
||||
vm2.setHostId(host2Id);
|
||||
|
||||
processor.process(vmProfile, plan, avoid);
|
||||
|
||||
Assert.assertEquals(1, plan.getHostPriorities().size());
|
||||
Assert.assertNotNull(plan.getHostPriorities().get(host2Id));
|
||||
Assert.assertEquals(Integer.valueOf(1), plan.getHostPriorities().get(host2Id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessWithPlan() {
|
||||
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
|
||||
when(vm.getId()).thenReturn(vmId);
|
||||
VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
|
||||
when(vmProfile.getVirtualMachine()).thenReturn(vm);
|
||||
|
||||
List<AffinityGroupVMMapVO> vmGroupMappings = new ArrayList<>();
|
||||
vmGroupMappings.add(new AffinityGroupVMMapVO(affinityGroupId, vmId));
|
||||
when(_affinityGroupVMMapDao.findByVmIdType(eq(vmId), nullable(String.class))).thenReturn(vmGroupMappings);
|
||||
|
||||
DataCenterDeployment plan = new DataCenterDeployment(zoneId);
|
||||
plan.adjustHostPriority(host2Id, DeploymentPlan.HostPriorityAdjustment.DEFAULT);
|
||||
plan.adjustHostPriority(host3Id, DeploymentPlan.HostPriorityAdjustment.LOWER);
|
||||
ExcludeList avoid = new ExcludeList();
|
||||
|
||||
AffinityGroupVO affinityGroupVO = Mockito.mock(AffinityGroupVO.class);
|
||||
when(affinityGroupDao.findById(affinityGroupId)).thenReturn(affinityGroupVO);
|
||||
when(affinityGroupVO.getId()).thenReturn(affinityGroupId);
|
||||
List<Long> groupVMIds = new ArrayList<>(Arrays.asList(vmId, vm2Id, vm3Id));
|
||||
when(_affinityGroupVMMapDao.listVmIdsByAffinityGroup(affinityGroupId)).thenReturn(groupVMIds);
|
||||
VMInstanceVO vm2 = new VMInstanceVO();
|
||||
when(vmInstanceDao.findById(vm2Id)).thenReturn(vm2);
|
||||
vm2.setHostId(host2Id);
|
||||
VMInstanceVO vm3 = new VMInstanceVO();
|
||||
when(vmInstanceDao.findById(vm3Id)).thenReturn(vm3);
|
||||
vm3.setHostId(host3Id);
|
||||
|
||||
processor.process(vmProfile, plan, avoid);
|
||||
|
||||
Assert.assertEquals(2, plan.getHostPriorities().size());
|
||||
Assert.assertNotNull(plan.getHostPriorities().get(host2Id));
|
||||
Assert.assertEquals(Integer.valueOf(1), plan.getHostPriorities().get(host2Id));
|
||||
Assert.assertNotNull(plan.getHostPriorities().get(host3Id));
|
||||
Assert.assertEquals(Integer.valueOf(0), plan.getHostPriorities().get(host3Id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessWithNotRunningVM() {
|
||||
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
|
||||
when(vm.getId()).thenReturn(vmId);
|
||||
VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
|
||||
when(vmProfile.getVirtualMachine()).thenReturn(vm);
|
||||
|
||||
List<AffinityGroupVMMapVO> vmGroupMappings = new ArrayList<>();
|
||||
vmGroupMappings.add(new AffinityGroupVMMapVO(affinityGroupId, vmId));
|
||||
when(_affinityGroupVMMapDao.findByVmIdType(eq(vmId), nullable(String.class))).thenReturn(vmGroupMappings);
|
||||
|
||||
DataCenterDeployment plan = new DataCenterDeployment(zoneId);
|
||||
ExcludeList avoid = new ExcludeList();
|
||||
|
||||
AffinityGroupVO affinityGroupVO = Mockito.mock(AffinityGroupVO.class);
|
||||
when(affinityGroupDao.findById(affinityGroupId)).thenReturn(affinityGroupVO);
|
||||
when(affinityGroupVO.getId()).thenReturn(affinityGroupId);
|
||||
List<Long> groupVMIds = new ArrayList<>(Arrays.asList(vmId, vm2Id));
|
||||
when(_affinityGroupVMMapDao.listVmIdsByAffinityGroup(affinityGroupId)).thenReturn(groupVMIds);
|
||||
VMInstanceVO vm2 = Mockito.mock(VMInstanceVO.class);
|
||||
when(vmInstanceDao.findById(vm2Id)).thenReturn(vm2);
|
||||
when(vm2.getHostId()).thenReturn(null);
|
||||
when(vm2.getLastHostId()).thenReturn(host2Id);
|
||||
when(vm2.getState()).thenReturn(VirtualMachine.State.Starting);
|
||||
when(vm2.getUpdateTime()).thenReturn(new Date());
|
||||
|
||||
ReflectionTestUtils.setField(processor, "vmCapacityReleaseInterval", 3600);
|
||||
processor.process(vmProfile, plan, avoid);
|
||||
|
||||
Assert.assertEquals(1, plan.getHostPriorities().size());
|
||||
Assert.assertNotNull(plan.getHostPriorities().get(host2Id));
|
||||
Assert.assertEquals(Integer.valueOf(1), plan.getHostPriorities().get(host2Id));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<!--
|
||||
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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>cloud-plugin-non-strict-host-anti-affinity</artifactId>
|
||||
<name>Apache CloudStack Plugin - Non-Strict Host Anti-Affinity Processor</name>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-plugin-non-strict-host-affinity</artifactId>
|
||||
<version>4.18.0.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<parent>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloudstack-plugins</artifactId>
|
||||
<version>4.18.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// 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 com.cloud.deploy.DeploymentPlan;
|
||||
|
||||
public class NonStrictHostAntiAffinityProcessor extends NonStrictHostAffinityProcessor {
|
||||
|
||||
@Override
|
||||
protected Integer adjustHostPriority(DeploymentPlan plan, Long hostId) {
|
||||
plan.adjustHostPriority(hostId, DeploymentPlan.HostPriorityAdjustment.LOWER);
|
||||
return plan.getHostPriorities().get(hostId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# 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.
|
||||
name=non-strict-host-anti-affinity
|
||||
parent=planner
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<!--
|
||||
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.
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd"
|
||||
>
|
||||
|
||||
<bean id="NonStrictHostAntiAffinityProcessor"
|
||||
class="org.apache.cloudstack.affinity.NonStrictHostAntiAffinityProcessor">
|
||||
<property name="name" value="NonStrictHostAntiAffinityProcessor" />
|
||||
<property name="type" value="non-strict host anti-affinity" />
|
||||
</bean>
|
||||
|
||||
|
||||
</beans>
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* 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 com.cloud.deploy.DataCenterDeployment;
|
||||
import com.cloud.deploy.DeploymentPlan;
|
||||
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
||||
import com.cloud.vm.VMInstanceVO;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.VirtualMachineProfile;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
|
||||
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.Spy;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.nullable;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
public class NonStrictHostAntiAffinityProcessorTest {
|
||||
|
||||
@Spy
|
||||
@InjectMocks
|
||||
NonStrictHostAntiAffinityProcessor processor = new NonStrictHostAntiAffinityProcessor();
|
||||
|
||||
@Mock
|
||||
AffinityGroupVMMapDao _affinityGroupVMMapDao;
|
||||
@Mock
|
||||
AffinityGroupDao affinityGroupDao;
|
||||
@Mock
|
||||
VMInstanceDao vmInstanceDao;
|
||||
|
||||
long vmId = 10L;
|
||||
long vm2Id = 11L;
|
||||
long vm3Id = 12L;
|
||||
long affinityGroupId = 20L;
|
||||
long zoneId = 2L;
|
||||
long host2Id = 3L;
|
||||
long host3Id = 4L;
|
||||
|
||||
@Test
|
||||
public void testProcessWithEmptyPlan() {
|
||||
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
|
||||
when(vm.getId()).thenReturn(vmId);
|
||||
VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
|
||||
when(vmProfile.getVirtualMachine()).thenReturn(vm);
|
||||
|
||||
List<AffinityGroupVMMapVO> vmGroupMappings = new ArrayList<>();
|
||||
vmGroupMappings.add(new AffinityGroupVMMapVO(affinityGroupId, vmId));
|
||||
when(_affinityGroupVMMapDao.findByVmIdType(eq(vmId), nullable(String.class))).thenReturn(vmGroupMappings);
|
||||
|
||||
DataCenterDeployment plan = new DataCenterDeployment(zoneId);
|
||||
ExcludeList avoid = new ExcludeList();
|
||||
|
||||
AffinityGroupVO affinityGroupVO = Mockito.mock(AffinityGroupVO.class);
|
||||
when(affinityGroupDao.findById(affinityGroupId)).thenReturn(affinityGroupVO);
|
||||
when(affinityGroupVO.getId()).thenReturn(affinityGroupId);
|
||||
List<Long> groupVMIds = new ArrayList<>(Arrays.asList(vmId, vm2Id));
|
||||
when(_affinityGroupVMMapDao.listVmIdsByAffinityGroup(affinityGroupId)).thenReturn(groupVMIds);
|
||||
VMInstanceVO vm2 = new VMInstanceVO();
|
||||
when(vmInstanceDao.findById(vm2Id)).thenReturn(vm2);
|
||||
vm2.setHostId(host2Id);
|
||||
|
||||
processor.process(vmProfile, plan, avoid);
|
||||
|
||||
Assert.assertEquals(1, plan.getHostPriorities().size());
|
||||
Assert.assertNotNull(plan.getHostPriorities().get(host2Id));
|
||||
Assert.assertEquals(Integer.valueOf(-1), plan.getHostPriorities().get(host2Id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessWithPlan() {
|
||||
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
|
||||
when(vm.getId()).thenReturn(vmId);
|
||||
VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
|
||||
when(vmProfile.getVirtualMachine()).thenReturn(vm);
|
||||
|
||||
List<AffinityGroupVMMapVO> vmGroupMappings = new ArrayList<>();
|
||||
vmGroupMappings.add(new AffinityGroupVMMapVO(affinityGroupId, vmId));
|
||||
when(_affinityGroupVMMapDao.findByVmIdType(eq(vmId), nullable(String.class))).thenReturn(vmGroupMappings);
|
||||
|
||||
DataCenterDeployment plan = new DataCenterDeployment(zoneId);
|
||||
plan.adjustHostPriority(host2Id, DeploymentPlan.HostPriorityAdjustment.DEFAULT);
|
||||
plan.adjustHostPriority(host3Id, DeploymentPlan.HostPriorityAdjustment.HIGHER);
|
||||
ExcludeList avoid = new ExcludeList();
|
||||
|
||||
AffinityGroupVO affinityGroupVO = Mockito.mock(AffinityGroupVO.class);
|
||||
when(affinityGroupDao.findById(affinityGroupId)).thenReturn(affinityGroupVO);
|
||||
when(affinityGroupVO.getId()).thenReturn(affinityGroupId);
|
||||
List<Long> groupVMIds = new ArrayList<>(Arrays.asList(vmId, vm2Id, vm3Id));
|
||||
when(_affinityGroupVMMapDao.listVmIdsByAffinityGroup(affinityGroupId)).thenReturn(groupVMIds);
|
||||
VMInstanceVO vm2 = new VMInstanceVO();
|
||||
when(vmInstanceDao.findById(vm2Id)).thenReturn(vm2);
|
||||
vm2.setHostId(host2Id);
|
||||
VMInstanceVO vm3 = new VMInstanceVO();
|
||||
when(vmInstanceDao.findById(vm3Id)).thenReturn(vm3);
|
||||
vm3.setHostId(host3Id);
|
||||
|
||||
processor.process(vmProfile, plan, avoid);
|
||||
|
||||
Assert.assertEquals(2, plan.getHostPriorities().size());
|
||||
Assert.assertNotNull(plan.getHostPriorities().get(host2Id));
|
||||
Assert.assertEquals(Integer.valueOf(-1), plan.getHostPriorities().get(host2Id));
|
||||
Assert.assertNotNull(plan.getHostPriorities().get(host3Id));
|
||||
Assert.assertEquals(Integer.valueOf(0), plan.getHostPriorities().get(host3Id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessWithNotRunningVM() {
|
||||
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
|
||||
when(vm.getId()).thenReturn(vmId);
|
||||
VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
|
||||
when(vmProfile.getVirtualMachine()).thenReturn(vm);
|
||||
|
||||
List<AffinityGroupVMMapVO> vmGroupMappings = new ArrayList<>();
|
||||
vmGroupMappings.add(new AffinityGroupVMMapVO(affinityGroupId, vmId));
|
||||
when(_affinityGroupVMMapDao.findByVmIdType(eq(vmId), nullable(String.class))).thenReturn(vmGroupMappings);
|
||||
|
||||
DataCenterDeployment plan = new DataCenterDeployment(zoneId);
|
||||
ExcludeList avoid = new ExcludeList();
|
||||
|
||||
AffinityGroupVO affinityGroupVO = Mockito.mock(AffinityGroupVO.class);
|
||||
when(affinityGroupDao.findById(affinityGroupId)).thenReturn(affinityGroupVO);
|
||||
when(affinityGroupVO.getId()).thenReturn(affinityGroupId);
|
||||
List<Long> groupVMIds = new ArrayList<>(Arrays.asList(vmId, vm2Id));
|
||||
when(_affinityGroupVMMapDao.listVmIdsByAffinityGroup(affinityGroupId)).thenReturn(groupVMIds);
|
||||
VMInstanceVO vm2 = Mockito.mock(VMInstanceVO.class);
|
||||
when(vmInstanceDao.findById(vm2Id)).thenReturn(vm2);
|
||||
when(vm2.getHostId()).thenReturn(null);
|
||||
when(vm2.getLastHostId()).thenReturn(host2Id);
|
||||
when(vm2.getState()).thenReturn(VirtualMachine.State.Starting);
|
||||
when(vm2.getUpdateTime()).thenReturn(new Date());
|
||||
|
||||
ReflectionTestUtils.setField(processor, "vmCapacityReleaseInterval", 3600);
|
||||
processor.process(vmProfile, plan, avoid);
|
||||
|
||||
Assert.assertEquals(1, plan.getHostPriorities().size());
|
||||
Assert.assertNotNull(plan.getHostPriorities().get(host2Id));
|
||||
Assert.assertEquals(Integer.valueOf(-1), plan.getHostPriorities().get(host2Id));
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +50,8 @@
|
|||
<module>affinity-group-processors/explicit-dedication</module>
|
||||
<module>affinity-group-processors/host-affinity</module>
|
||||
<module>affinity-group-processors/host-anti-affinity</module>
|
||||
<module>affinity-group-processors/non-strict-host-affinity</module>
|
||||
<module>affinity-group-processors/non-strict-host-anti-affinity</module>
|
||||
|
||||
<module>alert-handlers/snmp-alerts</module>
|
||||
<module>alert-handlers/syslog-alerts</module>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package com.cloud.deploy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
|
@ -382,6 +383,7 @@ StateListener<State, VirtualMachine.Event, VirtualMachine>, Configurable {
|
|||
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Deploy avoids pods: " + avoids.getPodsToAvoid() + ", clusters: " + avoids.getClustersToAvoid() + ", hosts: " + avoids.getHostsToAvoid());
|
||||
s_logger.debug("Deploy hosts with priorities " + plan.getHostPriorities() + " , hosts have NORMAL priority by default");
|
||||
}
|
||||
|
||||
// call planners
|
||||
|
|
@ -406,7 +408,10 @@ StateListener<State, VirtualMachine.Event, VirtualMachine>, Configurable {
|
|||
planner = getDeploymentPlannerByName(plannerName);
|
||||
}
|
||||
|
||||
if (vm.getLastHostId() != null && haVmTag == null) {
|
||||
String considerLastHostStr = (String)vmProfile.getParameter(VirtualMachineProfile.Param.ConsiderLastHost);
|
||||
boolean considerLastHost = vm.getLastHostId() != null && haVmTag == null &&
|
||||
(considerLastHostStr == null || Boolean.TRUE.toString().equalsIgnoreCase(considerLastHostStr));
|
||||
if (considerLastHost) {
|
||||
s_logger.debug("This VM has last host_id specified, trying to choose the same host: " + vm.getLastHostId());
|
||||
|
||||
HostVO host = _hostDao.findById(vm.getLastHostId());
|
||||
|
|
@ -1222,6 +1227,7 @@ StateListener<State, VirtualMachine.Event, VirtualMachine>, Configurable {
|
|||
// cluster.
|
||||
DataCenterDeployment potentialPlan =
|
||||
new DataCenterDeployment(plan.getDataCenterId(), clusterVO.getPodId(), clusterVO.getId(), null, plan.getPoolId(), null, plan.getReservationContext());
|
||||
potentialPlan.setHostPriorities(plan.getHostPriorities());
|
||||
|
||||
Pod pod = _podDao.findById(clusterVO.getPodId());
|
||||
if (CollectionUtils.isNotEmpty(avoid.getPodsToAvoid()) && avoid.getPodsToAvoid().contains(pod.getId())) {
|
||||
|
|
@ -1588,9 +1594,35 @@ StateListener<State, VirtualMachine.Event, VirtualMachine>, Configurable {
|
|||
if (suitableHosts.isEmpty()) {
|
||||
s_logger.debug("No suitable hosts found");
|
||||
}
|
||||
|
||||
// re-order hosts by priority
|
||||
reorderHostsByPriority(plan.getHostPriorities(), suitableHosts);
|
||||
|
||||
return suitableHosts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reorderHostsByPriority(Map<Long, Integer> priorities, List<Host> hosts) {
|
||||
s_logger.info("Re-ordering hosts " + hosts + " by priorities " + priorities);
|
||||
|
||||
hosts.removeIf(host -> DataCenterDeployment.PROHIBITED_HOST_PRIORITY.equals(getHostPriority(priorities, host.getId())));
|
||||
|
||||
Collections.sort(hosts, new Comparator<>() {
|
||||
@Override
|
||||
public int compare(Host host1, Host host2) {
|
||||
int res = getHostPriority(priorities, host1.getId()).compareTo(getHostPriority(priorities, host2.getId()));
|
||||
return -res;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
s_logger.info("Hosts after re-ordering are: " + hosts);
|
||||
}
|
||||
|
||||
private Integer getHostPriority(Map<Long, Integer> priorities, Long hostId) {
|
||||
return priorities.get(hostId) != null ? priorities.get(hostId) : DeploymentPlan.DEFAULT_HOST_PRIORITY;
|
||||
}
|
||||
|
||||
protected Pair<Map<Volume, List<StoragePool>>, List<Volume>> findSuitablePoolsForVolumes(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid,
|
||||
int returnUpTo) {
|
||||
List<VolumeVO> volumesTobeCreated = _volsDao.findUsableVolumesForInstance(vmProfile.getId());
|
||||
|
|
|
|||
|
|
@ -1508,6 +1508,9 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
|||
}
|
||||
}
|
||||
|
||||
// re-order hosts by priority
|
||||
_dpMgr.reorderHostsByPriority(plan.getHostPriorities(), suitableHosts);
|
||||
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
if (suitableHosts.isEmpty()) {
|
||||
s_logger.debug("No suitable hosts found");
|
||||
|
|
|
|||
|
|
@ -3225,6 +3225,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
if (uefiDetail != null) {
|
||||
addVmUefiBootOptionsToParams(additonalParams, uefiDetail.getName(), uefiDetail.getValue());
|
||||
}
|
||||
if (cmd.getConsiderLastHost() != null) {
|
||||
additonalParams.put(VirtualMachineProfile.Param.ConsiderLastHost, cmd.getConsiderLastHost().toString());
|
||||
}
|
||||
|
||||
return startVirtualMachine(cmd.getId(), cmd.getPodId(), cmd.getClusterId(), cmd.getHostId(), additonalParams, cmd.getDeploymentPlanner()).first();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,8 +127,9 @@ public class AffinityGroupServiceImpl extends ManagerBase implements AffinityGro
|
|||
|
||||
AffinityGroupProcessor processor = typeProcessorMap.get(affinityGroupType);
|
||||
|
||||
if(processor == null){
|
||||
throw new InvalidParameterValueException("Unable to create affinity group, invalid affinity group type" + affinityGroupType);
|
||||
if (processor == null) {
|
||||
throw new InvalidParameterValueException(String.format("Unable to create affinity group, invalid affinity group type: %s. " +
|
||||
"Valid values are %s", affinityGroupType, String.join(",", typeProcessorMap.keySet())));
|
||||
}
|
||||
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import java.io.IOException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
|
@ -1029,4 +1030,49 @@ public class DeploymentPlanningManagerImplTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReorderHostsByPriority() {
|
||||
Map<Long, Integer> priorities = new LinkedHashMap<>();
|
||||
priorities.put(1L, 3);
|
||||
priorities.put(2L, -6);
|
||||
priorities.put(3L, 5);
|
||||
priorities.put(5L, 8);
|
||||
priorities.put(6L, -1);
|
||||
priorities.put(8L, 5);
|
||||
priorities.put(9L, DataCenterDeployment.PROHIBITED_HOST_PRIORITY);
|
||||
|
||||
Host host1 = Mockito.mock(Host.class);
|
||||
Mockito.when(host1.getId()).thenReturn(1L);
|
||||
Host host2 = Mockito.mock(Host.class);
|
||||
Mockito.when(host2.getId()).thenReturn(2L);
|
||||
Host host3 = Mockito.mock(Host.class);
|
||||
Mockito.when(host3.getId()).thenReturn(3L);
|
||||
Host host4 = Mockito.mock(Host.class);
|
||||
Mockito.when(host4.getId()).thenReturn(4L);
|
||||
Host host5 = Mockito.mock(Host.class);
|
||||
Mockito.when(host5.getId()).thenReturn(5L);
|
||||
Host host6 = Mockito.mock(Host.class);
|
||||
Mockito.when(host6.getId()).thenReturn(6L);
|
||||
Host host7 = Mockito.mock(Host.class);
|
||||
Mockito.when(host7.getId()).thenReturn(7L);
|
||||
Host host8 = Mockito.mock(Host.class);
|
||||
Mockito.when(host8.getId()).thenReturn(8L);
|
||||
Host host9 = Mockito.mock(Host.class);
|
||||
Mockito.when(host9.getId()).thenReturn(9L);
|
||||
|
||||
List<Host> hosts = new ArrayList<>(Arrays.asList(host1, host2, host3, host4, host5, host6, host7, host8, host9));
|
||||
_dpm.reorderHostsByPriority(priorities, hosts);
|
||||
|
||||
Assert.assertEquals(8, hosts.size());
|
||||
|
||||
Assert.assertEquals(5, hosts.get(0).getId());
|
||||
Assert.assertEquals(3, hosts.get(1).getId());
|
||||
Assert.assertEquals(8, hosts.get(2).getId());
|
||||
Assert.assertEquals(1, hosts.get(3).getId());
|
||||
Assert.assertEquals(4, hosts.get(4).getId());
|
||||
Assert.assertEquals(7, hosts.get(5).getId());
|
||||
Assert.assertEquals(6, hosts.get(6).getId());
|
||||
Assert.assertEquals(2, hosts.get(7).getId());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,448 @@
|
|||
# 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.
|
||||
|
||||
"""
|
||||
Tests of Non-Strict (host anti-affinity and host affinity) affinity groups
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from nose.plugins.attrib import attr
|
||||
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||
from marvin.cloudstackAPI import startVirtualMachine, stopVirtualMachine, destroyVirtualMachine
|
||||
|
||||
from marvin.lib.base import (Account,
|
||||
AffinityGroup,
|
||||
Domain,
|
||||
Host,
|
||||
ServiceOffering,
|
||||
VirtualMachine,
|
||||
Zone,
|
||||
Network,
|
||||
NetworkOffering)
|
||||
|
||||
from marvin.lib.common import (get_domain,
|
||||
get_zone,
|
||||
get_template)
|
||||
|
||||
|
||||
class TestNonStrictAffinityGroups(cloudstackTestCase):
|
||||
"""
|
||||
Test Non-Strict (host anti-affinity and host affinity) affinity groups
|
||||
"""
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.testClient = super(
|
||||
TestNonStrictAffinityGroups,
|
||||
cls).getClsTestClient()
|
||||
cls.apiclient = cls.testClient.getApiClient()
|
||||
cls.services = cls.testClient.getParsedTestDataConfig()
|
||||
|
||||
zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
|
||||
cls.zone = Zone(zone.__dict__)
|
||||
cls.template = get_template(cls.apiclient, cls.zone.id)
|
||||
cls._cleanup = []
|
||||
|
||||
cls.logger = logging.getLogger("TestNonStrictAffinityGroups")
|
||||
cls.stream_handler = logging.StreamHandler()
|
||||
cls.logger.setLevel(logging.DEBUG)
|
||||
cls.logger.addHandler(cls.stream_handler)
|
||||
|
||||
cls.skipTests = False
|
||||
hosts = Host.list(
|
||||
cls.apiclient,
|
||||
zoneid=cls.zone.id,
|
||||
state='Up',
|
||||
resourcestate='Enabled'
|
||||
)
|
||||
if not hosts or not isinstance(hosts, list) or len(hosts) < 2:
|
||||
cls.logger.debug("This test requires at least two (Up and Enabled) hosts in the zone")
|
||||
cls.skipTests = True
|
||||
return
|
||||
|
||||
cls.domain = get_domain(cls.apiclient)
|
||||
|
||||
# 1. Create small service offering
|
||||
cls.service_offering = ServiceOffering.create(
|
||||
cls.apiclient,
|
||||
cls.services["service_offerings"]["small"]
|
||||
)
|
||||
cls._cleanup.append(cls.service_offering)
|
||||
|
||||
# 3. Create network offering for isolated networks
|
||||
cls.network_offering_isolated = NetworkOffering.create(
|
||||
cls.apiclient,
|
||||
cls.services["isolated_network_offering"]
|
||||
)
|
||||
cls.network_offering_isolated.update(cls.apiclient, state='Enabled')
|
||||
cls._cleanup.append(cls.network_offering_isolated)
|
||||
|
||||
# 4. Create sub-domain
|
||||
cls.sub_domain = Domain.create(
|
||||
cls.apiclient,
|
||||
cls.services["acl"]["domain1"]
|
||||
)
|
||||
cls._cleanup.append(cls.sub_domain)
|
||||
|
||||
# 5. Create regular user
|
||||
cls.regular_user = Account.create(
|
||||
cls.apiclient,
|
||||
cls.services["acl"]["accountD11A"],
|
||||
domainid=cls.sub_domain.id
|
||||
)
|
||||
cls._cleanup.append(cls.regular_user)
|
||||
|
||||
# 5. Create api clients for regular user
|
||||
cls.regular_user_user = cls.regular_user.user[0]
|
||||
cls.regular_user_apiclient = cls.testClient.getUserApiClient(
|
||||
cls.regular_user_user.username, cls.sub_domain.name
|
||||
)
|
||||
|
||||
# 7. Create network for regular user
|
||||
cls.services["network"]["name"] = "Test Network Isolated - Regular user"
|
||||
cls.user_network = Network.create(
|
||||
cls.regular_user_apiclient,
|
||||
cls.services["network"],
|
||||
networkofferingid=cls.network_offering_isolated.id,
|
||||
zoneid=cls.zone.id
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super(TestNonStrictAffinityGroups, cls).tearDownClass()
|
||||
|
||||
def setUp(self):
|
||||
if self.skipTests:
|
||||
self.skipTest("This test requires at least two (Up and Enabled) hosts in the zone")
|
||||
self.apiclient = self.testClient.getApiClient()
|
||||
self.cleanup = []
|
||||
|
||||
def tearDown(self):
|
||||
super(TestNonStrictAffinityGroups, self).tearDown()
|
||||
|
||||
@classmethod
|
||||
def get_vm_host_id(cls, vm_id):
|
||||
list_vms = VirtualMachine.list(
|
||||
cls.apiclient,
|
||||
id=vm_id
|
||||
)
|
||||
vm = list_vms[0]
|
||||
return vm.hostid
|
||||
|
||||
@attr(tags=["advanced"], required_hardware="false")
|
||||
def test_01_non_strict_host_anti_affinity(self):
|
||||
""" Verify Non-Strict host anti-affinity """
|
||||
|
||||
# 1. Create Non-Strict host anti-affinity
|
||||
# 2. Deploy vm-1 with the group
|
||||
# 3. Deploy vm-2 with the group. It will be started on different host if there are multiple hosts.
|
||||
# 4. Migrate vm-2 to same host as vm-1
|
||||
# 5. Stop vm-2, start vm-2. It will be started on same host as vm-1
|
||||
# 6. Stop vm-2, start vm-2 with considerlasthost=false. It will be started on different host as vm-1
|
||||
# 7. Deploy vm-3 with same host, vm-3 should be started on specified host.
|
||||
# 8. Deploy vm-4 with startvm=false, then start the VM.
|
||||
# vm-4 should be started on different host if there are multiple hosts.
|
||||
|
||||
self.logger.debug("=== Running test_01_non_strict_host_anti_affinity ===")
|
||||
|
||||
# 1. Create Non-Strict host anti-affinity
|
||||
affinity_group_params = {
|
||||
"name": "Test affinity group",
|
||||
"type": "non-strict host anti-affinity",
|
||||
}
|
||||
self.affinity_group = AffinityGroup.create(
|
||||
self.regular_user_apiclient,
|
||||
affinity_group_params
|
||||
)
|
||||
self.cleanup.append(self.affinity_group)
|
||||
|
||||
# 2. Deploy vm-1 with the group
|
||||
self.services["virtual_machine"]["name"] = "virtual-machine-1"
|
||||
self.services["virtual_machine"]["displayname"] = "virtual-machine-1"
|
||||
self.virtual_machine_1 = VirtualMachine.create(
|
||||
self.regular_user_apiclient,
|
||||
self.services["virtual_machine"],
|
||||
serviceofferingid=self.service_offering.id,
|
||||
templateid=self.template.id,
|
||||
zoneid=self.zone.id,
|
||||
networkids=self.user_network.id,
|
||||
affinitygroupids=self.affinity_group.id
|
||||
)
|
||||
self.cleanup.append(self.virtual_machine_1)
|
||||
vm_1_host_id = self.get_vm_host_id(self.virtual_machine_1.id)
|
||||
|
||||
# 3. Deploy vm-2 with the group. It will be started on different host if there are multiple hosts.
|
||||
self.services["virtual_machine"]["name"] = "virtual-machine-2"
|
||||
self.services["virtual_machine"]["displayname"] = "virtual-machine-2"
|
||||
self.virtual_machine_2 = VirtualMachine.create(
|
||||
self.regular_user_apiclient,
|
||||
self.services["virtual_machine"],
|
||||
serviceofferingid=self.service_offering.id,
|
||||
templateid=self.template.id,
|
||||
zoneid=self.zone.id,
|
||||
networkids=self.user_network.id,
|
||||
affinitygroupids=self.affinity_group.id
|
||||
)
|
||||
vm_2_host_id = self.get_vm_host_id(self.virtual_machine_2.id)
|
||||
|
||||
self.assertNotEqual(vm_1_host_id,
|
||||
vm_2_host_id,
|
||||
msg="Both VMs of affinity group %s are on the same host" % self.affinity_group.name)
|
||||
|
||||
# 4. Migrate vm-2 to same host as vm-1
|
||||
self.virtual_machine_2.migrate(
|
||||
self.apiclient,
|
||||
hostid=vm_1_host_id
|
||||
)
|
||||
|
||||
# 5. Stop vm-2, start vm-2. It will be started on same host as vm-1
|
||||
stopCmd = stopVirtualMachine.stopVirtualMachineCmd()
|
||||
stopCmd.id = self.virtual_machine_2.id
|
||||
stopCmd.forced = True
|
||||
self.apiclient.stopVirtualMachine(stopCmd)
|
||||
|
||||
startCmd = startVirtualMachine.startVirtualMachineCmd()
|
||||
startCmd.id = self.virtual_machine_2.id
|
||||
self.apiclient.startVirtualMachine(startCmd)
|
||||
|
||||
vm_2_host_id = self.get_vm_host_id(self.virtual_machine_2.id)
|
||||
|
||||
self.assertEqual(vm_1_host_id,
|
||||
vm_2_host_id,
|
||||
msg="Both VMs of affinity group %s are on the different host" % self.affinity_group.name)
|
||||
|
||||
# 6. Stop vm-2, start vm-2 with considerlasthost=false. It will be started on different host as vm-1
|
||||
stopCmd.id = self.virtual_machine_2.id
|
||||
stopCmd.forced = True
|
||||
self.apiclient.stopVirtualMachine(stopCmd)
|
||||
|
||||
startCmd = startVirtualMachine.startVirtualMachineCmd()
|
||||
startCmd.id = self.virtual_machine_2.id
|
||||
startCmd.considerlasthost = False
|
||||
self.apiclient.startVirtualMachine(startCmd)
|
||||
|
||||
vm_2_host_id = self.get_vm_host_id(self.virtual_machine_2.id)
|
||||
|
||||
self.assertNotEqual(vm_1_host_id,
|
||||
vm_2_host_id,
|
||||
msg="Both VMs of affinity group %s are on the same host" % self.affinity_group.name)
|
||||
|
||||
destroyCmd = destroyVirtualMachine.destroyVirtualMachineCmd()
|
||||
destroyCmd.id = self.virtual_machine_2.id
|
||||
destroyCmd.expunge = True
|
||||
self.apiclient.destroyVirtualMachine(destroyCmd)
|
||||
|
||||
# 7. Deploy vm-3 with same host, vm-3 should be started on specified host.
|
||||
self.services["virtual_machine"]["name"] = "virtual-machine-3"
|
||||
self.services["virtual_machine"]["displayname"] = "virtual-machine-3"
|
||||
self.virtual_machine_3 = VirtualMachine.create(
|
||||
self.apiclient,
|
||||
self.services["virtual_machine"],
|
||||
serviceofferingid=self.service_offering.id,
|
||||
templateid=self.template.id,
|
||||
zoneid=self.zone.id,
|
||||
networkids=self.user_network.id,
|
||||
affinitygroupids=self.affinity_group.id,
|
||||
domainid=self.sub_domain.id,
|
||||
accountid=self.regular_user.name,
|
||||
hostid=vm_1_host_id
|
||||
)
|
||||
vm_3_host_id = self.get_vm_host_id(self.virtual_machine_3.id)
|
||||
|
||||
self.assertEqual(vm_1_host_id,
|
||||
vm_3_host_id,
|
||||
msg="virtual-machine-3 should be started on %s" % vm_1_host_id)
|
||||
|
||||
destroyCmd.id = self.virtual_machine_3.id
|
||||
destroyCmd.expunge = True
|
||||
self.apiclient.destroyVirtualMachine(destroyCmd)
|
||||
|
||||
# 8. Deploy vm-4 with startvm=false, then start the VM.
|
||||
# vm-4 should be started on different host if there are multiple hosts.
|
||||
self.services["virtual_machine"]["name"] = "virtual-machine-4"
|
||||
self.services["virtual_machine"]["displayname"] = "virtual-machine-4"
|
||||
self.virtual_machine_4 = VirtualMachine.create(
|
||||
self.regular_user_apiclient,
|
||||
self.services["virtual_machine"],
|
||||
serviceofferingid=self.service_offering.id,
|
||||
templateid=self.template.id,
|
||||
zoneid=self.zone.id,
|
||||
networkids=self.user_network.id,
|
||||
affinitygroupids=self.affinity_group.id,
|
||||
startvm=False
|
||||
)
|
||||
self.cleanup.append(self.virtual_machine_4)
|
||||
|
||||
startCmd.id = self.virtual_machine_4.id
|
||||
startCmd.considerlasthost = None
|
||||
self.apiclient.startVirtualMachine(startCmd)
|
||||
|
||||
vm_4_host_id = self.get_vm_host_id(self.virtual_machine_4.id)
|
||||
|
||||
self.assertNotEqual(vm_1_host_id,
|
||||
vm_4_host_id,
|
||||
msg="virtual-machine-4 should be not started on %s" % vm_1_host_id)
|
||||
|
||||
@attr(tags=["advanced"], required_hardware="false")
|
||||
def test_02_non_strict_host_affinity(self):
|
||||
""" Verify Non-Strict host affinity """
|
||||
|
||||
# 1. Create Non-Strict host affinity
|
||||
# 2. Deploy vm-11 with the group
|
||||
# 3. Deploy vm-12 with the group. It will be started on same host.
|
||||
# 4. Migrate vm-12 to different host as vm-11
|
||||
# 5. Stop vm-12, start vm-12. It will be started on different host as vm-11
|
||||
# 6. Stop vm-12, start vm-12 with considerlasthost=false. It will be started on same host as vm-11
|
||||
# 7. Deploy vm-13 with different host, vm-13 should be started on specified host.
|
||||
# 8. Deploy vm-14 with startvm=false, then start the VM. vm-14 should be started on same host.
|
||||
|
||||
self.logger.debug("=== Running test_02_non_strict_host_affinity ===")
|
||||
|
||||
# 1. Create Non-Strict host affinity
|
||||
affinity_group_params = {
|
||||
"name": "Test affinity group",
|
||||
"type": "non-strict host affinity",
|
||||
}
|
||||
self.affinity_group = AffinityGroup.create(
|
||||
self.regular_user_apiclient,
|
||||
affinity_group_params
|
||||
)
|
||||
self.cleanup.append(self.affinity_group)
|
||||
|
||||
# 2. Deploy vm-11 with the group
|
||||
self.services["virtual_machine"]["name"] = "virtual-machine-11"
|
||||
self.services["virtual_machine"]["displayname"] = "virtual-machine-11"
|
||||
self.virtual_machine_11 = VirtualMachine.create(
|
||||
self.regular_user_apiclient,
|
||||
self.services["virtual_machine"],
|
||||
serviceofferingid=self.service_offering.id,
|
||||
templateid=self.template.id,
|
||||
zoneid=self.zone.id,
|
||||
networkids=self.user_network.id,
|
||||
affinitygroupids=self.affinity_group.id
|
||||
)
|
||||
self.cleanup.append(self.virtual_machine_11)
|
||||
vm_11_host_id = self.get_vm_host_id(self.virtual_machine_11.id)
|
||||
|
||||
# 3. Deploy vm-12 with the group. It will be started on same host.
|
||||
self.services["virtual_machine"]["name"] = "virtual-machine-12"
|
||||
self.services["virtual_machine"]["displayname"] = "virtual-machine-12"
|
||||
self.virtual_machine_12 = VirtualMachine.create(
|
||||
self.regular_user_apiclient,
|
||||
self.services["virtual_machine"],
|
||||
serviceofferingid=self.service_offering.id,
|
||||
templateid=self.template.id,
|
||||
zoneid=self.zone.id,
|
||||
networkids=self.user_network.id,
|
||||
affinitygroupids=self.affinity_group.id
|
||||
)
|
||||
vm_12_host_id = self.get_vm_host_id(self.virtual_machine_12.id)
|
||||
|
||||
self.assertEqual(vm_11_host_id,
|
||||
vm_12_host_id,
|
||||
msg="Both VMs of affinity group %s are on the different host" % self.affinity_group.name)
|
||||
|
||||
# 4. Migrate vm-12 to different host as vm-11
|
||||
self.virtual_machine_12.migrate(
|
||||
self.apiclient
|
||||
)
|
||||
|
||||
# 5. Stop vm-12, start vm-12. It will be started on different host as vm-11
|
||||
stopCmd = stopVirtualMachine.stopVirtualMachineCmd()
|
||||
stopCmd.id = self.virtual_machine_12.id
|
||||
stopCmd.forced = True
|
||||
self.apiclient.stopVirtualMachine(stopCmd)
|
||||
|
||||
startCmd = startVirtualMachine.startVirtualMachineCmd()
|
||||
startCmd.id = self.virtual_machine_12.id
|
||||
self.apiclient.startVirtualMachine(startCmd)
|
||||
|
||||
vm_12_host_id = self.get_vm_host_id(self.virtual_machine_12.id)
|
||||
|
||||
self.assertNotEqual(vm_11_host_id,
|
||||
vm_12_host_id,
|
||||
msg="Both VMs of affinity group %s are on the same host" % self.affinity_group.name)
|
||||
|
||||
# 6. Stop vm-12, start vm-12 with considerlasthost=false. It will be started on same host as vm-11
|
||||
stopCmd.id = self.virtual_machine_12.id
|
||||
stopCmd.forced = True
|
||||
self.apiclient.stopVirtualMachine(stopCmd)
|
||||
|
||||
startCmd.id = self.virtual_machine_12.id
|
||||
startCmd.considerlasthost = False
|
||||
self.apiclient.startVirtualMachine(startCmd)
|
||||
|
||||
vm_12_host_id = self.get_vm_host_id(self.virtual_machine_12.id)
|
||||
|
||||
self.assertEqual(vm_11_host_id,
|
||||
vm_12_host_id,
|
||||
msg="Both VMs of affinity group %s are on the different host" % self.affinity_group.name)
|
||||
|
||||
destroyCmd = destroyVirtualMachine.destroyVirtualMachineCmd()
|
||||
destroyCmd.id = self.virtual_machine_12.id
|
||||
destroyCmd.expunge = True
|
||||
self.apiclient.destroyVirtualMachine(destroyCmd)
|
||||
|
||||
# 7. Deploy vm-13 with different host, vm-13 should be started on specified host.
|
||||
self.services["virtual_machine"]["name"] = "virtual-machine-13"
|
||||
self.services["virtual_machine"]["displayname"] = "virtual-machine-13"
|
||||
self.virtual_machine_13 = VirtualMachine.create(
|
||||
self.apiclient,
|
||||
self.services["virtual_machine"],
|
||||
serviceofferingid=self.service_offering.id,
|
||||
templateid=self.template.id,
|
||||
zoneid=self.zone.id,
|
||||
networkids=self.user_network.id,
|
||||
affinitygroupids=self.affinity_group.id,
|
||||
domainid=self.sub_domain.id,
|
||||
accountid=self.regular_user.name,
|
||||
hostid=vm_12_host_id
|
||||
)
|
||||
vm_13_host_id = self.get_vm_host_id(self.virtual_machine_13.id)
|
||||
|
||||
self.assertEqual(vm_12_host_id,
|
||||
vm_13_host_id,
|
||||
msg="virtual-machine-13 should be started on %s" % vm_12_host_id)
|
||||
|
||||
destroyCmd.id = self.virtual_machine_13.id
|
||||
destroyCmd.expunge = True
|
||||
self.apiclient.destroyVirtualMachine(destroyCmd)
|
||||
|
||||
# 8. Deploy vm-14 with startvm=false, then start the VM. vm-14 should be started on same host.
|
||||
self.services["virtual_machine"]["name"] = "virtual-machine-14"
|
||||
self.services["virtual_machine"]["displayname"] = "virtual-machine-14"
|
||||
self.virtual_machine_14 = VirtualMachine.create(
|
||||
self.regular_user_apiclient,
|
||||
self.services["virtual_machine"],
|
||||
serviceofferingid=self.service_offering.id,
|
||||
templateid=self.template.id,
|
||||
zoneid=self.zone.id,
|
||||
networkids=self.user_network.id,
|
||||
affinitygroupids=self.affinity_group.id,
|
||||
startvm=False
|
||||
)
|
||||
self.cleanup.append(self.virtual_machine_14)
|
||||
|
||||
startCmd.id = self.virtual_machine_14.id
|
||||
startCmd.considerlasthost = None
|
||||
self.apiclient.startVirtualMachine(startCmd)
|
||||
|
||||
vm_14_host_id = self.get_vm_host_id(self.virtual_machine_14.id)
|
||||
|
||||
self.assertEqual(vm_11_host_id,
|
||||
vm_14_host_id,
|
||||
msg="virtual-machine-4 should be started on %s" % vm_11_host_id)
|
||||
|
|
@ -438,6 +438,7 @@
|
|||
"label.confirmpassword.description": "Please type the same password again.",
|
||||
"label.connectiontimeout": "Connection timeout",
|
||||
"label.conservemode": "Conserve mode",
|
||||
"label.considerlasthost": "Consider Last Host",
|
||||
"label.consoleproxy": "Console proxy",
|
||||
"label.console.proxy": "Console proxy",
|
||||
"label.console.proxy.vm": "Console proxy VM",
|
||||
|
|
|
|||
|
|
@ -117,7 +117,8 @@ export default {
|
|||
dataView: true,
|
||||
groupAction: true,
|
||||
popup: true,
|
||||
groupMap: (selection) => { return selection.map(x => { return { id: x } }) },
|
||||
groupMap: (selection, values) => { return selection.map(x => { return { id: x, considerlasthost: values.considerlasthost } }) },
|
||||
args: ['considerlasthost'],
|
||||
show: (record) => { return ['Stopped'].includes(record.state) },
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/StartVirtualMachine.vue')))
|
||||
},
|
||||
|
|
@ -850,7 +851,7 @@ export default {
|
|||
args: ['name', 'description', 'type'],
|
||||
mapping: {
|
||||
type: {
|
||||
options: ['host anti-affinity', 'host affinity']
|
||||
options: ['host anti-affinity (Strict)', 'host affinity (Strict)', 'host anti-affinity (Non-Strict)', 'host affinity (Non-Strict)']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1405,6 +1405,17 @@ export default {
|
|||
}
|
||||
if (action.mapping && key in action.mapping && action.mapping[key].options) {
|
||||
params[key] = action.mapping[key].options[input]
|
||||
if (['createAffinityGroup'].includes(action.api) && key === 'type') {
|
||||
if (params[key] === 'host anti-affinity (Strict)') {
|
||||
params[key] = 'host anti-affinity'
|
||||
} else if (params[key] === 'host affinity (Strict)') {
|
||||
params[key] = 'host affinity'
|
||||
} else if (params[key] === 'host anti-affinity (Non-Strict)') {
|
||||
params[key] = 'non-strict host anti-affinity'
|
||||
} else if (params[key] === 'host affinity (Non-Strict)') {
|
||||
params[key] = 'non-strict host affinity'
|
||||
}
|
||||
}
|
||||
} else if (param.type === 'list') {
|
||||
params[key] = input.map(e => { return param.opts[e].id }).reduce((str, name) => { return str + ',' + name })
|
||||
} else if (param.name === 'account' || param.name === 'keypair') {
|
||||
|
|
|
|||
|
|
@ -97,6 +97,13 @@
|
|||
<a-switch v-model:checked="form.bootintosetup" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="considerlasthost" ref="considerlasthost">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.considerlasthost')" :tooltip="apiParams.considerlasthost.description"/>
|
||||
</template>
|
||||
<a-switch v-model:checked="form.considerlasthost" />
|
||||
</a-form-item>
|
||||
|
||||
<div :span="24" class="action-button">
|
||||
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
|
||||
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
|
||||
|
|
@ -147,7 +154,9 @@ export default {
|
|||
methods: {
|
||||
initForm () {
|
||||
this.formRef = ref()
|
||||
this.form = reactive({})
|
||||
this.form = reactive({
|
||||
considerlasthost: true
|
||||
})
|
||||
this.rules = reactive({})
|
||||
},
|
||||
fetchPods () {
|
||||
|
|
|
|||
|
|
@ -92,12 +92,17 @@ export default {
|
|||
{
|
||||
dataIndex: 'name',
|
||||
title: this.$t('label.affinity.groups'),
|
||||
width: '40%'
|
||||
width: '30%'
|
||||
},
|
||||
{
|
||||
dataIndex: 'type',
|
||||
title: this.$t('label.type'),
|
||||
width: '30%'
|
||||
},
|
||||
{
|
||||
dataIndex: 'description',
|
||||
title: this.$t('label.description'),
|
||||
width: '60%'
|
||||
width: '40%'
|
||||
}
|
||||
],
|
||||
selectedRowKeys: [],
|
||||
|
|
|
|||
Loading…
Reference in New Issue