This is an automated email from the ASF dual-hosted git repository. weizhou pushed a commit to branch 4.18-non-strict-affinity-groups in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 111f3b5a15fcab807850cf338a33f33a9eb56978 Author: Wei Zhou <[email protected]> AuthorDate: Thu Oct 20 15:07:42 2022 +0200 server: add Non-Strict host affinity and anti-affinty groups --- .../com/cloud/deploy/DataCenterDeployment.java | 28 +++++ .../main/java/com/cloud/deploy/DeploymentPlan.java | 15 +++ client/pom.xml | 10 ++ .../core/spring-core-registry-core-context.xml | 2 +- .../cloud/deploy/DeploymentPlanningManager.java | 6 + .../non-strict-host-affinity/pom.xml | 30 +++++ .../affinity/NonStrictHostAffinityProcessor.java | 121 ++++++++++++++++++ .../non-strict-host-affinity/module.properties | 18 +++ .../spring-non-strict-host-affinity-context.xml | 37 ++++++ .../NonStrictHostAffinityProcessorTest.java | 136 +++++++++++++++++++++ .../non-strict-host-anti-affinity/pom.xml | 30 +++++ .../NonStrictHostAntiAffinityProcessor.java | 121 ++++++++++++++++++ .../module.properties | 18 +++ ...pring-non-strict-host-anti-affinity-context.xml | 37 ++++++ .../NonStrictHostAntiAffinityProcessorTest.java | 136 +++++++++++++++++++++ plugins/pom.xml | 2 + .../deploy/DeploymentPlanningManagerImpl.java | 33 +++++ .../com/cloud/server/ManagementServerImpl.java | 3 + .../affinity/AffinityGroupServiceImpl.java | 5 +- ui/src/config/section/compute.js | 2 +- ui/src/views/AutogenView.vue | 11 ++ 21 files changed, 797 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/com/cloud/deploy/DataCenterDeployment.java b/api/src/main/java/com/cloud/deploy/DataCenterDeployment.java index 3ee544cf4e..53c008b3b8 100644 --- a/api/src/main/java/com/cloud/deploy/DataCenterDeployment.java +++ b/api/src/main/java/com/cloud/deploy/DataCenterDeployment.java @@ -20,7 +20,9 @@ import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.vm.ReservationContext; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class DataCenterDeployment implements DeploymentPlan { long _dcId; @@ -34,6 +36,7 @@ public class DataCenterDeployment implements DeploymentPlan { ReservationContext _context; List<Long> preferredHostIds = new ArrayList<>(); boolean migrationPlan; + Map<Long, HostPriority> hostPriorities = new HashMap<>(); public DataCenterDeployment(long dataCenterId) { this(dataCenterId, null, null, null, null, null); @@ -117,4 +120,29 @@ public class DataCenterDeployment implements DeploymentPlan { return migrationPlan; } + @Override + public void addHostPriority(Long hostId, HostPriority priority) { + HostPriority currentPriority = hostPriorities.get(hostId); + if (currentPriority == null || HostPriority.NORMAL.equals(currentPriority)) { + hostPriorities.put(hostId, priority); + } else if (!HostPriority.PROHIBITED.equals(currentPriority)) { + if (HostPriority.HIGH.equals(priority)) { + HostPriority newPriority = HostPriority.LOW.equals(currentPriority) ? HostPriority.NORMAL : HostPriority.HIGH; + hostPriorities.put(hostId, newPriority); + } else if (HostPriority.LOW.equals(priority)) { + HostPriority newPriority = HostPriority.HIGH.equals(currentPriority) ? HostPriority.NORMAL : HostPriority.LOW; + hostPriorities.put(hostId, newPriority); + } + } + } + + @Override + public Map<Long, HostPriority> getHostPriorities() { + return hostPriorities; + } + + @Override + public void setHostPriorities(Map<Long, HostPriority> priorities) { + this.hostPriorities = priorities; + } } diff --git a/api/src/main/java/com/cloud/deploy/DeploymentPlan.java b/api/src/main/java/com/cloud/deploy/DeploymentPlan.java index c71bf3e931..17eb088d1d 100644 --- a/api/src/main/java/com/cloud/deploy/DeploymentPlan.java +++ b/api/src/main/java/com/cloud/deploy/DeploymentPlan.java @@ -20,10 +20,19 @@ import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.vm.ReservationContext; import java.util.List; +import java.util.Map; /** */ public interface DeploymentPlan { + + enum HostPriority { + HIGH, + NORMAL, + LOW, + PROHIBITED + } + // TODO: This interface is not fully developed. It really // number of parameters to be specified. @@ -73,4 +82,10 @@ public interface DeploymentPlan { List<Long> getPreferredHosts(); boolean isMigrationPlan(); + + void addHostPriority(Long hostId, HostPriority priority); + + Map<Long, HostPriority> getHostPriorities(); + + void setHostPriorities(Map<Long, HostPriority> priorities); } diff --git a/client/pom.xml b/client/pom.xml index 921a4adbf8..5934b340dd 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -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> diff --git a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml index cb5e965216..fa386bb154 100644 --- a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml +++ b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml @@ -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> diff --git a/engine/components-api/src/main/java/com/cloud/deploy/DeploymentPlanningManager.java b/engine/components-api/src/main/java/com/cloud/deploy/DeploymentPlanningManager.java index d3a5f7f737..620311284d 100644 --- a/engine/components-api/src/main/java/com/cloud/deploy/DeploymentPlanningManager.java +++ b/engine/components-api/src/main/java/com/cloud/deploy/DeploymentPlanningManager.java @@ -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, DeploymentPlan.HostPriority> priorities, List<Host> hosts); } diff --git a/plugins/affinity-group-processors/non-strict-host-affinity/pom.xml b/plugins/affinity-group-processors/non-strict-host-affinity/pom.xml new file mode 100644 index 0000000000..ffbe5cd2e5 --- /dev/null +++ b/plugins/affinity-group-processors/non-strict-host-affinity/pom.xml @@ -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> diff --git a/plugins/affinity-group-processors/non-strict-host-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessor.java b/plugins/affinity-group-processors/non-strict-host-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessor.java new file mode 100644 index 0000000000..6a354614b7 --- /dev/null +++ b/plugins/affinity-group-processors/non-strict-host-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessor.java @@ -0,0 +1,121 @@ +// 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 static final Logger s_logger = Logger.getLogger(NonStrictHostAffinityProcessor.class); + @Inject + protected UserVmDao _vmDao; + @Inject + protected VMInstanceDao _vmInstanceDao; + @Inject + protected AffinityGroupDao _affinityGroupDao; + @Inject + protected AffinityGroupVMMapDao _affinityGroupVMMapDao; + private int _vmCapacityReleaseInterval; + @Inject + protected ConfigurationDao _configDao; + + @Inject + protected VMReservationDao _reservationDao; + + @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 (s_logger.isDebugEnabled()) { + s_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()) { + if (groupVM.getHostId() != null) { + plan.addHostPriority(groupVM.getHostId(), DeploymentPlan.HostPriority.HIGH); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Marked host " + groupVM.getHostId() + " to as low priority, since VM " + groupVM.getId() + " is present on the host"); + } + } 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) { + plan.addHostPriority(groupVM.getLastHostId(), DeploymentPlan.HostPriority.HIGH); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Marked host " + groupVM.getLastHostId() + " as low priority, since VM " + groupVM.getId() + + " is present on the host, in Stopped state but has reserved capacity"); + } + } + } + } + } + } + + @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; + } + +} diff --git a/plugins/affinity-group-processors/non-strict-host-affinity/src/main/resources/META-INF/cloudstack/non-strict-host-affinity/module.properties b/plugins/affinity-group-processors/non-strict-host-affinity/src/main/resources/META-INF/cloudstack/non-strict-host-affinity/module.properties new file mode 100644 index 0000000000..98c83112f1 --- /dev/null +++ b/plugins/affinity-group-processors/non-strict-host-affinity/src/main/resources/META-INF/cloudstack/non-strict-host-affinity/module.properties @@ -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 diff --git a/plugins/affinity-group-processors/non-strict-host-affinity/src/main/resources/META-INF/cloudstack/non-strict-host-affinity/spring-non-strict-host-affinity-context.xml b/plugins/affinity-group-processors/non-strict-host-affinity/src/main/resources/META-INF/cloudstack/non-strict-host-affinity/spring-non-strict-host-affinity-context.xml new file mode 100644 index 0000000000..a80ddb1e3b --- /dev/null +++ b/plugins/affinity-group-processors/non-strict-host-affinity/src/main/resources/META-INF/cloudstack/non-strict-host-affinity/spring-non-strict-host-affinity-context.xml @@ -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> diff --git a/plugins/affinity-group-processors/non-strict-host-affinity/src/test/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessorTest.java b/plugins/affinity-group-processors/non-strict-host-affinity/src/test/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessorTest.java new file mode 100644 index 0000000000..9b241658bd --- /dev/null +++ b/plugins/affinity-group-processors/non-strict-host-affinity/src/test/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessorTest.java @@ -0,0 +1,136 @@ +/* + * 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 java.util.ArrayList; +import java.util.Arrays; +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(DeploymentPlan.HostPriority.HIGH, 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.addHostPriority(host2Id, DeploymentPlan.HostPriority.NORMAL); + plan.addHostPriority(host3Id, DeploymentPlan.HostPriority.LOW); + 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(DeploymentPlan.HostPriority.HIGH, plan.getHostPriorities().get(host2Id)); + Assert.assertNotNull(plan.getHostPriorities().get(host3Id)); + Assert.assertEquals(DeploymentPlan.HostPriority.NORMAL, plan.getHostPriorities().get(host3Id)); + } +} diff --git a/plugins/affinity-group-processors/non-strict-host-anti-affinity/pom.xml b/plugins/affinity-group-processors/non-strict-host-anti-affinity/pom.xml new file mode 100644 index 0000000000..6a8efa38a9 --- /dev/null +++ b/plugins/affinity-group-processors/non-strict-host-anti-affinity/pom.xml @@ -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-anti-affinity</artifactId> + <name>Apache CloudStack Plugin - Non-Strict Host Anti-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> diff --git a/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAntiAffinityProcessor.java b/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAntiAffinityProcessor.java new file mode 100644 index 0000000000..b774c0631b --- /dev/null +++ b/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAntiAffinityProcessor.java @@ -0,0 +1,121 @@ +// 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 NonStrictHostAntiAffinityProcessor extends AffinityProcessorBase implements AffinityGroupProcessor { + + private static final Logger s_logger = Logger.getLogger(NonStrictHostAntiAffinityProcessor.class); + @Inject + protected UserVmDao _vmDao; + @Inject + protected VMInstanceDao _vmInstanceDao; + @Inject + protected AffinityGroupDao _affinityGroupDao; + @Inject + protected AffinityGroupVMMapDao _affinityGroupVMMapDao; + private int _vmCapacityReleaseInterval; + @Inject + protected ConfigurationDao _configDao; + + @Inject + protected VMReservationDao _reservationDao; + + @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 (s_logger.isDebugEnabled()) { + s_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()) { + if (groupVM.getHostId() != null) { + plan.addHostPriority(groupVM.getHostId(), DeploymentPlan.HostPriority.LOW); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Marked host " + groupVM.getHostId() + " to as low priority, since VM " + groupVM.getId() + " is present on the host"); + } + } 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) { + plan.addHostPriority(groupVM.getLastHostId(), DeploymentPlan.HostPriority.LOW); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Marked host " + groupVM.getLastHostId() + " as low priority, since VM " + groupVM.getId() + + " is present on the host, in Stopped state but has reserved capacity"); + } + } + } + } + } + } + + @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; + } + +} diff --git a/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/main/resources/META-INF/cloudstack/non-strict-host-anti-affinity/module.properties b/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/main/resources/META-INF/cloudstack/non-strict-host-anti-affinity/module.properties new file mode 100644 index 0000000000..1d8cb21d44 --- /dev/null +++ b/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/main/resources/META-INF/cloudstack/non-strict-host-anti-affinity/module.properties @@ -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 diff --git a/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/main/resources/META-INF/cloudstack/non-strict-host-anti-affinity/spring-non-strict-host-anti-affinity-context.xml b/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/main/resources/META-INF/cloudstack/non-strict-host-anti-affinity/spring-non-strict-host-anti-affinity-context.xml new file mode 100644 index 0000000000..0f42019b26 --- /dev/null +++ b/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/main/resources/META-INF/cloudstack/non-strict-host-anti-affinity/spring-non-strict-host-anti-affinity-context.xml @@ -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> diff --git a/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/test/java/org/apache/cloudstack/affinity/NonStrictHostAntiAffinityProcessorTest.java b/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/test/java/org/apache/cloudstack/affinity/NonStrictHostAntiAffinityProcessorTest.java new file mode 100644 index 0000000000..42c5ad8b9a --- /dev/null +++ b/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/test/java/org/apache/cloudstack/affinity/NonStrictHostAntiAffinityProcessorTest.java @@ -0,0 +1,136 @@ +/* + * 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 java.util.ArrayList; +import java.util.Arrays; +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(DeploymentPlan.HostPriority.LOW, 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.addHostPriority(host2Id, DeploymentPlan.HostPriority.NORMAL); + plan.addHostPriority(host3Id, DeploymentPlan.HostPriority.HIGH); + 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(DeploymentPlan.HostPriority.LOW, plan.getHostPriorities().get(host2Id)); + Assert.assertNotNull(plan.getHostPriorities().get(host3Id)); + Assert.assertEquals(DeploymentPlan.HostPriority.NORMAL, plan.getHostPriorities().get(host3Id)); + } +} diff --git a/plugins/pom.xml b/plugins/pom.xml index 8534000afc..461ec0e02c 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -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> diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index 40f6667fae..d16b69999d 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -26,6 +26,7 @@ import java.util.Optional; import java.util.Set; import java.util.Timer; import java.util.TreeSet; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; @@ -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 @@ -1222,6 +1224,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 +1591,39 @@ 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, DeploymentPlan.HostPriority> priorities, List<Host> hosts) { + s_logger.debug("Re-ordering hosts " + hosts + " by priorities " + priorities); + + List<Host> highPriorityHosts = hosts.stream() + .filter(host -> DeploymentPlan.HostPriority.HIGH.equals(priorities.get(host.getId()))) + .collect(Collectors.toList()); + List<Host> lowPriorityHosts = hosts.stream() + .filter(host -> DeploymentPlan.HostPriority.LOW.equals(priorities.get(host.getId()))) + .collect(Collectors.toList()); + List<Host> prohibitedPriorityHosts = hosts.stream() + .filter(host -> DeploymentPlan.HostPriority.PROHIBITED.equals(priorities.get(host.getId()))) + .collect(Collectors.toList()); + List<Host> normalPriorityHosts = hosts.stream() + .filter(host -> priorities.get(host.getId()) == null || DeploymentPlan.HostPriority.NORMAL.equals(priorities.get(host.getId()))) + .collect(Collectors.toList()); + + hosts.clear(); + hosts.addAll(highPriorityHosts); + hosts.addAll(normalPriorityHosts); + hosts.addAll(lowPriorityHosts); + hosts.addAll(prohibitedPriorityHosts); + + s_logger.debug("Hosts after re-ordering are: " + hosts); + } + protected Pair<Map<Volume, List<StoragePool>>, List<Volume>> findSuitablePoolsForVolumes(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo) { List<VolumeVO> volumesTobeCreated = _volsDao.findUsableVolumesForInstance(vmProfile.getId()); diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 55bd8c119d..1f70931e5d 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -1507,6 +1507,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"); diff --git a/server/src/main/java/org/apache/cloudstack/affinity/AffinityGroupServiceImpl.java b/server/src/main/java/org/apache/cloudstack/affinity/AffinityGroupServiceImpl.java index 076740965c..48600ddc0c 100644 --- a/server/src/main/java/org/apache/cloudstack/affinity/AffinityGroupServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/affinity/AffinityGroupServiceImpl.java @@ -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(); diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js index c568a00298..a8e7c56d47 100644 --- a/ui/src/config/section/compute.js +++ b/ui/src/config/section/compute.js @@ -746,7 +746,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)'] } } }, diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue index 85c90e6c3a..926b975c97 100644 --- a/ui/src/views/AutogenView.vue +++ b/ui/src/views/AutogenView.vue @@ -1401,6 +1401,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') {
