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') {

Reply via email to