This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch 4.19
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.19 by this push:
     new 1955d8f3db0 Add advance settings to fine tune DRS imbalance 
calculation (#8521)
1955d8f3db0 is described below

commit 1955d8f3db096bffd22f0c445b1758a1f90c32d6
Author: Vishesh <[email protected]>
AuthorDate: Tue Feb 13 11:18:53 2024 +0530

    Add advance settings to fine tune DRS imbalance calculation (#8521)
    
    * Use free/total instead of free metric to calculate imbalance
    
    * Filter out hosts for condensed while checking imbalance
    
    * Make DRS more configurable
    
    * code refactor
    
    * Add unit tests
    
    * fixup
    
    * Fix validation for drs.imbalance.condensed.skip.threshold
    
    * Add logging and other minor changes for drs
    
    * Add some logging for drs
    
    * Change format for drs imbalance to string
    
    * Show drs imbalance as percentage
    
    * Fixup label for memorytotal in en.json
---
 .../cloudstack/cluster/ClusterDrsAlgorithm.java    | 192 +++++++++++++++------
 .../cloudstack/cluster/ClusterDrsService.java      |  23 +++
 .../cluster/ClusterDrsAlgorithmTest.java           |  97 +++++++++++
 .../org/apache/cloudstack/cluster/Balanced.java    |  76 ++++----
 .../apache/cloudstack/cluster/BalancedTest.java    |  53 ++----
 .../org/apache/cloudstack/cluster/Condensed.java   |  79 ++++-----
 .../apache/cloudstack/cluster/CondensedTest.java   |  46 ++---
 .../cloudstack/metrics/MetricsServiceImpl.java     |  23 ++-
 .../response/ClusterMetricsResponse.java           |  12 ++
 .../configuration/ConfigurationManagerImpl.java    |   1 +
 .../cloudstack/cluster/ClusterDrsServiceImpl.java  |  28 +--
 .../cluster/ClusterDrsServiceImplTest.java         |   6 +-
 ui/public/locales/en.json                          |   3 +-
 ui/src/components/view/ListView.vue                |   3 +
 ui/src/config/section/infra/clusters.js            |   4 +-
 ui/src/views/infra/ClusterDRSTab.vue               |  18 +-
 16 files changed, 431 insertions(+), 233 deletions(-)

diff --git 
a/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithm.java 
b/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithm.java
index 889c49298ec..15f7fcd8174 100644
--- a/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithm.java
+++ b/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithm.java
@@ -33,6 +33,10 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
+import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetric;
+import static 
org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetricType;
+import static 
org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetricUseRatio;
+
 public interface ClusterDrsAlgorithm extends Adapter {
 
     /**
@@ -42,16 +46,17 @@ public interface ClusterDrsAlgorithm extends Adapter {
      * @param clusterId
      *         the ID of the cluster to check
      * @param cpuList
-     *         a list of CPU allocated values for each host in the cluster
+     *         a list of Ternary of used, reserved & total CPU for each host 
in the cluster
      * @param memoryList
-     *         a list of memory allocated values for each host in the cluster
+     *         a list of Ternary of used, reserved & total memory values for 
each host in the cluster
      *
      * @return true if a DRS operation is needed, false otherwise
      *
      * @throws ConfigurationException
      *         if there is an error in the configuration
      */
-    boolean needsDrs(long clusterId, List<Long> cpuList, List<Long> 
memoryList) throws ConfigurationException;
+    boolean needsDrs(long clusterId, List<Ternary<Long, Long, Long>> cpuList,
+            List<Ternary<Long, Long, Long>> memoryList) throws 
ConfigurationException;
 
 
     /**
@@ -65,18 +70,19 @@ public interface ClusterDrsAlgorithm extends Adapter {
      *         the service offering for the virtual machine
      * @param destHost
      *         the destination host for the virtual machine
-     * @param hostCpuFreeMap
-     *         a map of host IDs to the amount of CPU free on each host
-     * @param hostMemoryFreeMap
-     *         a map of host IDs to the amount of memory free on each host
+     * @param hostCpuMap
+     *         a map of host IDs to the Ternary of used, reserved and total 
CPU on each host
+     * @param hostMemoryMap
+     *         a map of host IDs to the Ternary of used, reserved and total 
memory on each host
      * @param requiresStorageMotion
      *         whether storage motion is required for the virtual machine
      *
      * @return a ternary containing improvement, cost, benefit
      */
     Ternary<Double, Double, Double> getMetrics(long clusterId, VirtualMachine 
vm, ServiceOffering serviceOffering,
-                                               Host destHost, Map<Long, Long> 
hostCpuFreeMap,
-                                               Map<Long, Long> 
hostMemoryFreeMap, Boolean requiresStorageMotion);
+            Host destHost, Map<Long, Ternary<Long, Long, Long>> hostCpuMap,
+            Map<Long, Ternary<Long, Long, Long>> hostMemoryMap,
+            Boolean requiresStorageMotion) throws ConfigurationException;
 
     /**
      * Calculates the imbalance of the cluster after a virtual machine 
migration.
@@ -87,54 +93,93 @@ public interface ClusterDrsAlgorithm extends Adapter {
      *         the virtual machine being migrated
      * @param destHost
      *         the destination host for the virtual machine
-     * @param hostCpuFreeMap
-     *         a map of host IDs to the amount of CPU free on each host
-     * @param hostMemoryFreeMap
-     *         a map of host IDs to the amount of memory free on each host
+     * @param hostCpuMap
+     *         a map of host IDs to the Ternary of used, reserved and total 
CPU on each host
+     * @param hostMemoryMap
+     *         a map of host IDs to the Ternary of used, reserved and total 
memory on each host
      *
      * @return a pair containing the CPU and memory imbalance of the cluster 
after the migration
      */
-    default Pair<Double, Double> getImbalancePostMigration(ServiceOffering 
serviceOffering, VirtualMachine vm,
-                                                           Host destHost, 
Map<Long, Long> hostCpuFreeMap,
-                                                           Map<Long, Long> 
hostMemoryFreeMap) {
-        List<Long> postCpuList = new ArrayList<>();
-        List<Long> postMemoryList = new ArrayList<>();
-        final int vmCpu = serviceOffering.getCpu() * 
serviceOffering.getSpeed();
-        final long vmRam = serviceOffering.getRamSize() * 1024L * 1024L;
-
-        for (Long hostId : hostCpuFreeMap.keySet()) {
-            long cpu = hostCpuFreeMap.get(hostId);
-            long memory = hostMemoryFreeMap.get(hostId);
-            if (hostId == destHost.getId()) {
-                postCpuList.add(cpu - vmCpu);
-                postMemoryList.add(memory - vmRam);
-            } else if (hostId.equals(vm.getHostId())) {
-                postCpuList.add(cpu + vmCpu);
-                postMemoryList.add(memory + vmRam);
-            } else {
-                postCpuList.add(cpu);
-                postMemoryList.add(memory);
-            }
+    default Double getImbalancePostMigration(ServiceOffering serviceOffering, 
VirtualMachine vm,
+            Host destHost, Map<Long, Ternary<Long, Long, Long>> hostCpuMap,
+            Map<Long, Ternary<Long, Long, Long>> hostMemoryMap) throws 
ConfigurationException {
+        Pair<Long, Map<Long, Ternary<Long, Long, Long>>> pair = 
getHostMetricsMapAndType(destHost.getClusterId(), serviceOffering, hostCpuMap, 
hostMemoryMap);
+        long vmMetric = pair.first();
+        Map<Long, Ternary<Long, Long, Long>> hostMetricsMap = pair.second();
+
+        List<Double> list = new ArrayList<>();
+        for (Long hostId : hostMetricsMap.keySet()) {
+            list.add(getMetricValuePostMigration(destHost.getClusterId(), 
hostMetricsMap.get(hostId), vmMetric, hostId, destHost.getId(), 
vm.getHostId()));
         }
-        return new Pair<>(getClusterImbalance(postCpuList), 
getClusterImbalance(postMemoryList));
+        return getImbalance(list);
     }
 
-    /**
-     * The cluster imbalance is defined as the percentage deviation from the 
mean
-     * for a configured metric of the cluster. The standard deviation is used 
as a
-     * mathematical tool to normalize the metric data for all the resource and 
the
-     * percentage deviation provides an easy tool to compare a cluster’s 
current
-     * state against the defined imbalance threshold. Because this is 
essentially a
-     * percentage, the value is a number between 0.0 and 1.0.
-     * Cluster Imbalance, Ic = σc / mavg , where σc is the standard deviation 
and
-     * mavg is the mean metric value for the cluster.
-     */
-    default Double getClusterImbalance(List<Long> metricList) {
+    private Pair<Long, Map<Long, Ternary<Long, Long, Long>>> 
getHostMetricsMapAndType(Long clusterId,
+            ServiceOffering serviceOffering, Map<Long, Ternary<Long, Long, 
Long>> hostCpuMap,
+            Map<Long, Ternary<Long, Long, Long>> hostMemoryMap) throws 
ConfigurationException {
+        String metric = getClusterDrsMetric(clusterId);
+        Pair<Long, Map<Long, Ternary<Long, Long, Long>>> pair;
+        switch (metric) {
+            case "cpu":
+                pair = new Pair<>((long) serviceOffering.getCpu() * 
serviceOffering.getSpeed(), hostCpuMap);
+                break;
+            case "memory":
+                pair = new Pair<>(serviceOffering.getRamSize() * 1024L * 
1024L, hostMemoryMap);
+                break;
+            default:
+                throw new ConfigurationException(
+                        String.format("Invalid metric: %s for cluster: %d", 
metric, clusterId));
+        }
+        return pair;
+    }
+
+    private Double getMetricValuePostMigration(Long clusterId, Ternary<Long, 
Long, Long> metrics, long vmMetric,
+            long hostId, long destHostId, long vmHostId) {
+        long used = metrics.first();
+        long actualTotal = metrics.third() - metrics.second();
+        long free = actualTotal - metrics.first();
+
+        if (hostId == destHostId) {
+            used += vmMetric;
+            free -= vmMetric;
+        } else if (hostId == vmHostId) {
+            used -= vmMetric;
+            free += vmMetric;
+        }
+        return getMetricValue(clusterId, used, free, actualTotal, null);
+    }
+
+    private static Double getImbalance(List<Double> metricList) {
         Double clusterMeanMetric = getClusterMeanMetric(metricList);
         Double clusterStandardDeviation = 
getClusterStandardDeviation(metricList, clusterMeanMetric);
         return clusterStandardDeviation / clusterMeanMetric;
     }
 
+    static String getClusterDrsMetric(long clusterId) {
+        return ClusterDrsMetric.valueIn(clusterId);
+    }
+
+    static Double getMetricValue(long clusterId, long used, long free, long 
total, Float skipThreshold) {
+        boolean useRatio = getDrsMetricUseRatio(clusterId);
+        switch (getDrsMetricType(clusterId)) {
+            case "free":
+                if (skipThreshold != null && free < skipThreshold * total) 
return null;
+                if (useRatio) {
+                    return (double) free / total;
+                } else {
+                    return (double) free;
+                }
+            case "used":
+                if (skipThreshold != null && used > skipThreshold * total) 
return null;
+                if (useRatio) {
+                    return (double) used / total;
+                } else {
+                    return (double) used;
+                }
+        }
+        return null;
+    }
+
     /**
      * Mean is the average of a collection or set of metrics. In context of a 
DRS
      * cluster, the cluster metrics defined as the average metrics value for 
some
@@ -142,7 +187,7 @@ public interface ClusterDrsAlgorithm extends Adapter {
      * Cluster Mean Metric, mavg = (∑mi) / N, where mi is a measurable metric 
for a
      * resource ‘i’ in a cluster with total N number of resources.
      */
-    default Double getClusterMeanMetric(List<Long> metricList) {
+    static Double getClusterMeanMetric(List<Double> metricList) {
         return new Mean().evaluate(metricList.stream().mapToDouble(i -> 
i).toArray());
     }
 
@@ -157,11 +202,62 @@ public interface ClusterDrsAlgorithm extends Adapter {
      * mean metric value and mi is a measurable metric for some resource ‘i’ 
in the
      * cluster with total N number of resources.
      */
-    default Double getClusterStandardDeviation(List<Long> metricList, Double 
mean) {
+    static Double getClusterStandardDeviation(List<Double> metricList, Double 
mean) {
         if (mean != null) {
             return new 
StandardDeviation(false).evaluate(metricList.stream().mapToDouble(i -> 
i).toArray(), mean);
         } else {
             return new 
StandardDeviation(false).evaluate(metricList.stream().mapToDouble(i -> 
i).toArray());
         }
     }
+
+    static boolean getDrsMetricUseRatio(long clusterId) {
+        return ClusterDrsMetricUseRatio.valueIn(clusterId);
+    }
+
+    static String getDrsMetricType(long clusterId) {
+        return ClusterDrsMetricType.valueIn(clusterId);
+    }
+
+    /**
+     * The cluster imbalance is defined as the percentage deviation from the 
mean
+     * for a configured metric of the cluster. The standard deviation is used 
as a
+     * mathematical tool to normalize the metric data for all the resource and 
the
+     * percentage deviation provides an easy tool to compare a cluster’s 
current
+     * state against the defined imbalance threshold. Because this is 
essentially a
+     * percentage, the value is a number between 0.0 and 1.0.
+     * Cluster Imbalance, Ic = σc / mavg , where σc is the standard deviation 
and
+     * mavg is the mean metric value for the cluster.
+     */
+    static Double getClusterImbalance(Long clusterId, List<Ternary<Long, Long, 
Long>> cpuList,
+            List<Ternary<Long, Long, Long>> memoryList, Float skipThreshold) 
throws ConfigurationException {
+        String metric = getClusterDrsMetric(clusterId);
+        List<Double> list;
+        switch (metric) {
+            case "cpu":
+                list = getMetricList(clusterId, cpuList, skipThreshold);
+                break;
+            case "memory":
+                list = getMetricList(clusterId, memoryList, skipThreshold);
+                break;
+            default:
+                throw new ConfigurationException(
+                        String.format("Invalid metric: %s for cluster: %d", 
metric, clusterId));
+        }
+        return getImbalance(list);
+    }
+
+    static List<Double> getMetricList(Long clusterId, List<Ternary<Long, Long, 
Long>> hostMetricsList,
+            Float skipThreshold) {
+        List<Double> list = new ArrayList<>();
+        for (Ternary<Long, Long, Long> ternary : hostMetricsList) {
+            long used = ternary.first();
+            long actualTotal = ternary.third() - ternary.second();
+            long free = actualTotal - ternary.first();
+            Double metricValue = getMetricValue(clusterId, used, free, 
actualTotal, skipThreshold);
+            if (metricValue != null) {
+                list.add(metricValue);
+            }
+        }
+        return list;
+    }
 }
diff --git 
a/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsService.java 
b/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsService.java
index 91be8c535a4..ba6a6464fc2 100644
--- a/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsService.java
+++ b/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsService.java
@@ -66,6 +66,29 @@ public interface ClusterDrsService extends Manager, 
Configurable, Scheduler {
             true, ConfigKey.Scope.Cluster, null, "DRS metric", null, null, 
null, ConfigKey.Kind.Select,
             "memory,cpu");
 
+    ConfigKey<String> ClusterDrsMetricType = new ConfigKey<>(String.class, 
"drs.metric.type", ConfigKey.CATEGORY_ADVANCED,
+            "used",
+            "The metric type used to measure imbalance in a cluster. This can 
completely change the imbalance value. Possible values are free, used.",
+            true, ConfigKey.Scope.Cluster, null, "DRS metric type", null, 
null, null, ConfigKey.Kind.Select,
+            "free,used");
+
+    ConfigKey<Boolean> ClusterDrsMetricUseRatio = new 
ConfigKey<>(Boolean.class, "drs.metric.use.ratio", ConfigKey.CATEGORY_ADVANCED,
+            "true",
+            "Whether to use ratio of selected metric & total. Useful when the 
cluster has hosts with different capacities",
+            true, ConfigKey.Scope.Cluster, null, "DRS metric use ratio", null, 
null, null, ConfigKey.Kind.Select,
+            "true,false");
+
+    ConfigKey<Float> ClusterDrsImbalanceSkipThreshold = new 
ConfigKey<>(Float.class,
+            "drs.imbalance.condensed.skip.threshold", 
ConfigKey.CATEGORY_ADVANCED, "0.95",
+            "Threshold to ignore the metric for a host while calculating the 
imbalance to decide " +
+                    "whether DRS is required for a cluster.This is to avoid 
cases when the calculated imbalance" +
+                    " gets skewed due to a single host having a very high/low 
metric  value resulting in imbalance" +
+                    " being higher than 1. If " + ClusterDrsMetricType.key() + 
" is 'free', set a lower value and if it is 'used' " +
+                    "set a higher value. The value should be between 0.0 and 
1.0",
+            true, ConfigKey.Scope.Cluster, null, "DRS imbalance skip threshold 
for Condensed algorithm",
+            null, null, null);
+
+
     /**
      * Generate a DRS plan for a cluster and save it as per the parameters
      *
diff --git 
a/api/src/test/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithmTest.java 
b/api/src/test/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithmTest.java
new file mode 100644
index 00000000000..88379810944
--- /dev/null
+++ 
b/api/src/test/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithmTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.cluster;
+
+import com.cloud.utils.Ternary;
+import junit.framework.TestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.List;
+
+import static org.apache.cloudstack.cluster.ClusterDrsAlgorithm.getMetricValue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ClusterDrsAlgorithmTest extends TestCase {
+
+    @Test
+    public void testGetMetricValue() {
+        List<Ternary<Boolean, String, Double>> testData = List.of(
+                new Ternary<>(true, "free", 0.4),
+                new Ternary<>(false, "free", 40.0),
+                new Ternary<>(true, "used", 0.3),
+                new Ternary<>(false, "used", 30.0)
+        );
+
+        long used = 30;
+        long free = 40;
+        long total = 100;
+
+        for (Ternary<Boolean, String, Double> data : testData) {
+            boolean useRatio = data.first();
+            String metricType = data.second();
+            double expectedValue = data.third();
+
+            try (MockedStatic<ClusterDrsAlgorithm> ignored = 
Mockito.mockStatic(ClusterDrsAlgorithm.class)) {
+                
when(ClusterDrsAlgorithm.getDrsMetricUseRatio(1L)).thenReturn(useRatio);
+                
when(ClusterDrsAlgorithm.getDrsMetricType(1L)).thenReturn(metricType);
+                when(ClusterDrsAlgorithm.getMetricValue(anyLong(), anyLong(), 
anyLong(), anyLong(), any())).thenCallRealMethod();
+
+                assertEquals(expectedValue, getMetricValue(1, used, free, 
total, null));
+            }
+        }
+    }
+
+    @Test
+    public void testGetMetricValueWithSkipThreshold() {
+        List<Ternary<Boolean, String, Double>> testData = List.of(
+                new Ternary<>(true, "free", 0.15),
+                new Ternary<>(false, "free", 15.0),
+                new Ternary<>(true, "used", null),
+                new Ternary<>(false, "used", null)
+        );
+
+        long used = 80;
+        long free = 15;
+        long total = 100;
+
+        for (Ternary<Boolean, String, Double> data : testData) {
+            boolean useRatio = data.first();
+            String metricType = data.second();
+            Double expectedValue = data.third();
+            float skipThreshold = metricType.equals("free") ? 0.1f : 0.7f;
+
+            try (MockedStatic<ClusterDrsAlgorithm> ignored = 
Mockito.mockStatic(ClusterDrsAlgorithm.class)) {
+                
when(ClusterDrsAlgorithm.getDrsMetricUseRatio(1L)).thenReturn(useRatio);
+                
when(ClusterDrsAlgorithm.getDrsMetricType(1L)).thenReturn(metricType);
+                when(ClusterDrsAlgorithm.getMetricValue(anyLong(), anyLong(), 
anyLong(), anyLong(), anyFloat())).thenCallRealMethod();
+
+                assertEquals(expectedValue, 
ClusterDrsAlgorithm.getMetricValue(1L, used, free, total, skipThreshold));
+            }
+        }
+    }
+}
diff --git 
a/plugins/drs/cluster/balanced/src/main/java/org/apache/cloudstack/cluster/Balanced.java
 
b/plugins/drs/cluster/balanced/src/main/java/org/apache/cloudstack/cluster/Balanced.java
index dc15a820560..ea234c2b794 100644
--- 
a/plugins/drs/cluster/balanced/src/main/java/org/apache/cloudstack/cluster/Balanced.java
+++ 
b/plugins/drs/cluster/balanced/src/main/java/org/apache/cloudstack/cluster/Balanced.java
@@ -21,10 +21,10 @@ package org.apache.cloudstack.cluster;
 
 import com.cloud.host.Host;
 import com.cloud.offering.ServiceOffering;
-import com.cloud.utils.Pair;
 import com.cloud.utils.Ternary;
 import com.cloud.utils.component.AdapterBase;
 import com.cloud.vm.VirtualMachine;
+import org.apache.log4j.Logger;
 
 import javax.naming.ConfigurationException;
 import java.util.ArrayList;
@@ -32,68 +32,56 @@ import java.util.List;
 import java.util.Map;
 
 import static 
org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsImbalanceThreshold;
-import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetric;
 
 public class Balanced extends AdapterBase implements ClusterDrsAlgorithm {
 
-    @Override
-    public String getName() {
-        return "balanced";
-    }
+    private static final Logger logger = Logger.getLogger(Balanced.class);
 
     @Override
-    public boolean needsDrs(long clusterId, List<Long> cpuList, List<Long> 
memoryList) throws ConfigurationException {
-        Double cpuImbalance = getClusterImbalance(cpuList);
-        Double memoryImbalance = getClusterImbalance(memoryList);
+    public boolean needsDrs(long clusterId, List<Ternary<Long, Long, Long>> 
cpuList,
+            List<Ternary<Long, Long, Long>> memoryList) throws 
ConfigurationException {
         double threshold = getThreshold(clusterId);
-        String metric = ClusterDrsMetric.valueIn(clusterId);
-        switch (metric) {
-            case "cpu":
-                return cpuImbalance > threshold;
-            case "memory":
-                return memoryImbalance > threshold;
-            default:
-                throw new ConfigurationException(
-                        String.format("Invalid metric: %s for cluster: %d", 
metric, clusterId));
+        Double imbalance = ClusterDrsAlgorithm.getClusterImbalance(clusterId, 
cpuList, memoryList, null);
+        String drsMetric = ClusterDrsAlgorithm.getClusterDrsMetric(clusterId);
+        String metricType = ClusterDrsAlgorithm.getDrsMetricType(clusterId);
+        Boolean useRatio = ClusterDrsAlgorithm.getDrsMetricUseRatio(clusterId);
+        if (imbalance > threshold) {
+            logger.debug(String.format("Cluster %d needs DRS. Imbalance: %s 
Threshold: %s Algorithm: %s DRS metric: %s Metric Type: %s Use ratio: %s",
+                    clusterId, imbalance, threshold, getName(), drsMetric, 
metricType, useRatio));
+            return true;
+        } else {
+            logger.debug(String.format("Cluster %d does not need DRS. 
Imbalance: %s Threshold: %s Algorithm: %s DRS metric: %s Metric Type: %s Use 
ratio: %s",
+                    clusterId, imbalance, threshold, getName(), drsMetric, 
metricType, useRatio));
+            return false;
         }
     }
 
-    private double getThreshold(long clusterId) throws ConfigurationException {
+    private double getThreshold(long clusterId) {
         return 1.0 - ClusterDrsImbalanceThreshold.valueIn(clusterId);
     }
 
+    @Override
+    public String getName() {
+        return "balanced";
+    }
+
     @Override
     public Ternary<Double, Double, Double> getMetrics(long clusterId, 
VirtualMachine vm,
-                                                      ServiceOffering 
serviceOffering, Host destHost,
-                                                      Map<Long, Long> 
hostCpuUsedMap, Map<Long, Long> hostMemoryUsedMap,
-                                                      Boolean 
requiresStorageMotion) {
-        Double preCpuImbalance = getClusterImbalance(new 
ArrayList<>(hostCpuUsedMap.values()));
-        Double preMemoryImbalance = getClusterImbalance(new 
ArrayList<>(hostMemoryUsedMap.values()));
+            ServiceOffering serviceOffering, Host destHost,
+            Map<Long, Ternary<Long, Long, Long>> hostCpuMap, Map<Long, 
Ternary<Long, Long, Long>> hostMemoryMap,
+            Boolean requiresStorageMotion) throws ConfigurationException {
+        Double preImbalance = 
ClusterDrsAlgorithm.getClusterImbalance(clusterId, new 
ArrayList<>(hostCpuMap.values()), new ArrayList<>(hostMemoryMap.values()), 
null);
+        Double postImbalance = getImbalancePostMigration(serviceOffering, vm, 
destHost, hostCpuMap, hostMemoryMap);
 
-        Pair<Double, Double> imbalancePair = 
getImbalancePostMigration(serviceOffering, vm, destHost, hostCpuUsedMap,
-                hostMemoryUsedMap);
-        Double postCpuImbalance = imbalancePair.first();
-        Double postMemoryImbalance = imbalancePair.second();
+        logger.debug(String.format("Cluster %d pre-imbalance: %s 
post-imbalance: %s Algorithm: %s VM: %s srcHost: %d destHost: %s",
+                clusterId, preImbalance, postImbalance, getName(), 
vm.getUuid(), vm.getHostId(), destHost.getUuid()));
 
         // This needs more research to determine the cost and benefit of a 
migration
         // TODO: Cost should be a factor of the VM size and the host capacity
         // TODO: Benefit should be a factor of the VM size and the host 
capacity and the number of VMs on the host
-        double cost = 0.0;
-        double benefit = 1.0;
-
-        String metric = ClusterDrsMetric.valueIn(clusterId);
-        final double improvement;
-        switch (metric) {
-            case "cpu":
-                improvement = preCpuImbalance - postCpuImbalance;
-                break;
-            case "memory":
-                improvement = preMemoryImbalance - postMemoryImbalance;
-                break;
-            default:
-                improvement = preCpuImbalance + preMemoryImbalance - 
postCpuImbalance - postMemoryImbalance;
-        }
-
+        final double improvement = preImbalance - postImbalance;
+        final double cost = 0.0;
+        final double benefit = 1.0;
         return new Ternary<>(improvement, cost, benefit);
     }
 }
diff --git 
a/plugins/drs/cluster/balanced/src/test/java/org/apache/cloudstack/cluster/BalancedTest.java
 
b/plugins/drs/cluster/balanced/src/test/java/org/apache/cloudstack/cluster/BalancedTest.java
index 0da807e65c3..a1562b52e38 100644
--- 
a/plugins/drs/cluster/balanced/src/test/java/org/apache/cloudstack/cluster/BalancedTest.java
+++ 
b/plugins/drs/cluster/balanced/src/test/java/org/apache/cloudstack/cluster/BalancedTest.java
@@ -21,7 +21,6 @@ package org.apache.cloudstack.cluster;
 
 import com.cloud.host.Host;
 import com.cloud.service.ServiceOfferingVO;
-import com.cloud.service.dao.ServiceOfferingDao;
 import com.cloud.utils.Ternary;
 import com.cloud.vm.VirtualMachine;
 import org.apache.cloudstack.framework.config.ConfigKey;
@@ -30,13 +29,13 @@ import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
-import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.junit.MockitoJUnitRunner;
 
 import javax.naming.ConfigurationException;
 import java.lang.reflect.Field;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -66,14 +65,7 @@ public class BalancedTest {
 
     Map<Long, List<VirtualMachine>> hostVmMap;
 
-    List<Long> cpuList, memoryList;
-
-    Map<Long, Long> hostCpuFreeMap, hostMemoryFreeMap;
-
-
-    @Mock
-    private ServiceOfferingDao serviceOfferingDao;
-
+    Map<Long, Ternary<Long, Long, Long>> hostCpuFreeMap, hostMemoryFreeMap;
 
     private AutoCloseable closeable;
 
@@ -98,24 +90,21 @@ public class BalancedTest {
 
         Mockito.when(serviceOffering.getCpu()).thenReturn(1);
         Mockito.when(serviceOffering.getSpeed()).thenReturn(1000);
-        Mockito.when(serviceOffering.getRamSize()).thenReturn(512);
+        Mockito.when(serviceOffering.getRamSize()).thenReturn(1024);
 
         overrideDefaultConfigValue(ClusterDrsImbalanceThreshold, 
"_defaultValue", "0.5");
 
-        cpuList = Arrays.asList(1L, 2L);
-        memoryList = Arrays.asList(512L, 2048L);
-
         hostCpuFreeMap = new HashMap<>();
-        hostCpuFreeMap.put(1L, 2000L);
-        hostCpuFreeMap.put(2L, 1000L);
+        hostCpuFreeMap.put(1L, new Ternary<>(1000L, 0L, 10000L));
+        hostCpuFreeMap.put(2L, new Ternary<>(2000L, 0L, 10000L));
 
         hostMemoryFreeMap = new HashMap<>();
-        hostMemoryFreeMap.put(1L, 2048L * 1024L * 1024L);
-        hostMemoryFreeMap.put(2L, 512L * 1024L * 1024L);
+        hostMemoryFreeMap.put(1L, new Ternary<>(512L * 1024L * 1024L, 0L, 
8192L * 1024L * 1024L));
+        hostMemoryFreeMap.put(2L, new Ternary<>(2048L * 1024L * 1024L, 0L, 
8192L * 1024L * 1024L));
     }
 
     private void overrideDefaultConfigValue(final ConfigKey configKey, final 
String name,
-                                            final Object o) throws 
IllegalAccessException, NoSuchFieldException {
+            final Object o) throws IllegalAccessException, 
NoSuchFieldException {
         Field f = ConfigKey.class.getDeclaredField(name);
         f.setAccessible(true);
         f.set(configKey, o);
@@ -144,7 +133,7 @@ public class BalancedTest {
     @Test
     public void needsDrsWithCpu() throws ConfigurationException, 
NoSuchFieldException, IllegalAccessException {
         overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "cpu");
-        assertFalse(balanced.needsDrs(clusterId, cpuList, memoryList));
+        assertFalse(balanced.needsDrs(clusterId, new 
ArrayList<>(hostCpuFreeMap.values()), new 
ArrayList<>(hostMemoryFreeMap.values())));
     }
 
     /*
@@ -154,14 +143,14 @@ public class BalancedTest {
     @Test
     public void needsDrsWithMemory() throws ConfigurationException, 
NoSuchFieldException, IllegalAccessException {
         overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", 
"memory");
-        assertTrue(balanced.needsDrs(clusterId, cpuList, memoryList));
+        assertTrue(balanced.needsDrs(clusterId, new 
ArrayList<>(hostCpuFreeMap.values()), new 
ArrayList<>(hostMemoryFreeMap.values())));
     }
 
     /* 3. cluster with "unknown" metric */
     @Test
-    public void needsDrsWithUnknown() throws ConfigurationException, 
NoSuchFieldException, IllegalAccessException {
+    public void needsDrsWithUnknown() throws NoSuchFieldException, 
IllegalAccessException {
         overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", 
"unknown");
-        assertThrows(ConfigurationException.class, () -> 
balanced.needsDrs(clusterId, cpuList, memoryList));
+        assertThrows(ConfigurationException.class, () -> 
balanced.needsDrs(clusterId, new ArrayList<>(hostCpuFreeMap.values()), new 
ArrayList<>(hostMemoryFreeMap.values())));
     }
 
     /**
@@ -188,7 +177,7 @@ public class BalancedTest {
      improvement = 0.3333 - 0.3333  = 0.0
     */
     @Test
-    public void getMetricsWithCpu() throws NoSuchFieldException, 
IllegalAccessException {
+    public void getMetricsWithCpu() throws NoSuchFieldException, 
IllegalAccessException, ConfigurationException {
         overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "cpu");
         Ternary<Double, Double, Double> result = 
balanced.getMetrics(clusterId, vm3, serviceOffering, destHost,
                 hostCpuFreeMap, hostMemoryFreeMap, false);
@@ -202,7 +191,7 @@ public class BalancedTest {
      improvement = 0.6 - 0.2 = 0.4
     */
     @Test
-    public void getMetricsWithMemory() throws NoSuchFieldException, 
IllegalAccessException {
+    public void getMetricsWithMemory() throws NoSuchFieldException, 
IllegalAccessException, ConfigurationException {
         overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", 
"memory");
         Ternary<Double, Double, Double> result = 
balanced.getMetrics(clusterId, vm3, serviceOffering, destHost,
                 hostCpuFreeMap, hostMemoryFreeMap, false);
@@ -210,18 +199,4 @@ public class BalancedTest {
         assertEquals(0, result.second(), 0.0);
         assertEquals(1, result.third(), 0.0);
     }
-
-    /*
-     3. cluster with default metric
-     improvement = 0.3333 + 0.6 - 0.3333 - 0.2 = 0.4
-    */
-    @Test
-    public void getMetricsWithDefault() throws NoSuchFieldException, 
IllegalAccessException {
-        overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "both");
-        Ternary<Double, Double, Double> result = 
balanced.getMetrics(clusterId, vm3, serviceOffering, destHost,
-                hostCpuFreeMap, hostMemoryFreeMap, false);
-        assertEquals(0.4, result.first(), 0.01);
-        assertEquals(0, result.second(), 0.0);
-        assertEquals(1, result.third(), 0.0);
-    }
 }
diff --git 
a/plugins/drs/cluster/condensed/src/main/java/org/apache/cloudstack/cluster/Condensed.java
 
b/plugins/drs/cluster/condensed/src/main/java/org/apache/cloudstack/cluster/Condensed.java
index aefd11905ef..dc1546f9f6a 100644
--- 
a/plugins/drs/cluster/condensed/src/main/java/org/apache/cloudstack/cluster/Condensed.java
+++ 
b/plugins/drs/cluster/condensed/src/main/java/org/apache/cloudstack/cluster/Condensed.java
@@ -21,78 +21,71 @@ package org.apache.cloudstack.cluster;
 
 import com.cloud.host.Host;
 import com.cloud.offering.ServiceOffering;
-import com.cloud.utils.Pair;
 import com.cloud.utils.Ternary;
 import com.cloud.utils.component.AdapterBase;
 import com.cloud.vm.VirtualMachine;
+import org.apache.log4j.Logger;
 
 import javax.naming.ConfigurationException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
+import static 
org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsImbalanceSkipThreshold;
 import static 
org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsImbalanceThreshold;
-import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetric;
 
 public class Condensed extends AdapterBase implements ClusterDrsAlgorithm {
 
-    @Override
-    public String getName() {
-        return "condensed";
-    }
+    private static final Logger logger = Logger.getLogger(Condensed.class);
 
     @Override
-    public boolean needsDrs(long clusterId, List<Long> cpuList, List<Long> 
memoryList) throws ConfigurationException {
-        Double cpuImbalance = getClusterImbalance(cpuList);
-        Double memoryImbalance = getClusterImbalance(memoryList);
+    public boolean needsDrs(long clusterId, List<Ternary<Long, Long, Long>> 
cpuList,
+            List<Ternary<Long, Long, Long>> memoryList) throws 
ConfigurationException {
         double threshold = getThreshold(clusterId);
-        String metric = ClusterDrsMetric.valueIn(clusterId);
-        switch (metric) {
-            case "cpu":
-                return cpuImbalance < threshold;
-            case "memory":
-                return memoryImbalance < threshold;
-            default:
-                throw new ConfigurationException(
-                        String.format("Invalid metric: %s for cluster: %d", 
metric, clusterId));
+        Float skipThreshold = 
ClusterDrsImbalanceSkipThreshold.valueIn(clusterId);
+        Double imbalance = ClusterDrsAlgorithm.getClusterImbalance(clusterId, 
cpuList, memoryList, skipThreshold);
+        String drsMetric = ClusterDrsAlgorithm.getClusterDrsMetric(clusterId);
+        String metricType = ClusterDrsAlgorithm.getDrsMetricType(clusterId);
+        Boolean useRatio = ClusterDrsAlgorithm.getDrsMetricUseRatio(clusterId);
+        if (imbalance < threshold) {
+
+            logger.debug(String.format("Cluster %d needs DRS. Imbalance: %s 
Threshold: %s Algorithm: %s DRS metric: %s Metric Type: %s Use ratio: %s 
SkipThreshold: %s",
+                    clusterId, imbalance, threshold, getName(), drsMetric, 
metricType, useRatio, skipThreshold));
+            return true;
+        } else {
+            logger.debug(String.format("Cluster %d does not need DRS. 
Imbalance: %s Threshold: %s Algorithm: %s DRS metric: %s Metric Type: %s Use 
ratio: %s SkipThreshold: %s",
+                    clusterId, imbalance, threshold, getName(), drsMetric, 
metricType, useRatio, skipThreshold));
+            return false;
         }
     }
 
-    private double getThreshold(long clusterId) throws ConfigurationException {
+    private double getThreshold(long clusterId) {
         return ClusterDrsImbalanceThreshold.valueIn(clusterId);
     }
 
+    @Override
+    public String getName() {
+        return "condensed";
+    }
+
     @Override
     public Ternary<Double, Double, Double> getMetrics(long clusterId, 
VirtualMachine vm,
-                                                      ServiceOffering 
serviceOffering, Host destHost,
-                                                      Map<Long, Long> 
hostCpuUsedMap, Map<Long, Long> hostMemoryUsedMap,
-                                                      Boolean 
requiresStorageMotion) {
-        Double preCpuImbalance = getClusterImbalance(new 
ArrayList<>(hostCpuUsedMap.values()));
-        Double preMemoryImbalance = getClusterImbalance(new 
ArrayList<>(hostMemoryUsedMap.values()));
+            ServiceOffering serviceOffering, Host destHost,
+            Map<Long, Ternary<Long, Long, Long>> hostCpuMap, Map<Long, 
Ternary<Long, Long, Long>> hostMemoryMap,
+            Boolean requiresStorageMotion) throws ConfigurationException {
+        Double preImbalance = 
ClusterDrsAlgorithm.getClusterImbalance(clusterId, new 
ArrayList<>(hostCpuMap.values()),
+                new ArrayList<>(hostMemoryMap.values()), null);
+        Double postImbalance = getImbalancePostMigration(serviceOffering, vm, 
destHost, hostCpuMap, hostMemoryMap);
 
-        Pair<Double, Double> imbalancePair = 
getImbalancePostMigration(serviceOffering, vm, destHost, hostCpuUsedMap,
-                hostMemoryUsedMap);
-        Double postCpuImbalance = imbalancePair.first();
-        Double postMemoryImbalance = imbalancePair.second();
+        logger.debug(String.format("Cluster %d pre-imbalance: %s 
post-imbalance: %s Algorithm: %s VM: %s srcHost: %d destHost: %s",
+                clusterId, preImbalance, postImbalance, getName(), 
vm.getUuid(), vm.getHostId(), destHost.getUuid()));
 
         // This needs more research to determine the cost and benefit of a 
migration
         // TODO: Cost should be a factor of the VM size and the host capacity
         // TODO: Benefit should be a factor of the VM size and the host 
capacity and the number of VMs on the host
-        double cost = 0;
-        double benefit = 1;
-
-        String metric = ClusterDrsMetric.valueIn(clusterId);
-        double improvement;
-        switch (metric) {
-            case "cpu":
-                improvement = postCpuImbalance - preCpuImbalance;
-                break;
-            case "memory":
-                improvement = postMemoryImbalance - preMemoryImbalance;
-                break;
-            default:
-                improvement = postCpuImbalance + postMemoryImbalance - 
preCpuImbalance - preMemoryImbalance;
-        }
+        final double improvement = postImbalance - preImbalance;
+        final double cost = 0;
+        final double benefit = 1;
         return new Ternary<>(improvement, cost, benefit);
     }
 }
diff --git 
a/plugins/drs/cluster/condensed/src/test/java/org/apache/cloudstack/cluster/CondensedTest.java
 
b/plugins/drs/cluster/condensed/src/test/java/org/apache/cloudstack/cluster/CondensedTest.java
index 0ba2b66379a..d5072774534 100644
--- 
a/plugins/drs/cluster/condensed/src/test/java/org/apache/cloudstack/cluster/CondensedTest.java
+++ 
b/plugins/drs/cluster/condensed/src/test/java/org/apache/cloudstack/cluster/CondensedTest.java
@@ -35,6 +35,7 @@ import org.mockito.junit.MockitoJUnitRunner;
 
 import javax.naming.ConfigurationException;
 import java.lang.reflect.Field;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -64,9 +65,7 @@ public class CondensedTest {
 
     Map<Long, List<VirtualMachine>> hostVmMap;
 
-    List<Long> cpuList, memoryList;
-
-    Map<Long, Long> hostCpuFreeMap, hostMemoryFreeMap;
+    Map<Long, Ternary<Long, Long, Long>> hostCpuFreeMap, hostMemoryFreeMap;
 
 
     private AutoCloseable closeable;
@@ -95,21 +94,18 @@ public class CondensedTest {
 
         overrideDefaultConfigValue(ClusterDrsImbalanceThreshold, 
"_defaultValue", "0.5");
 
-        cpuList = Arrays.asList(1L, 2L);
-        memoryList = Arrays.asList(512L, 2048L);
-
         hostCpuFreeMap = new HashMap<>();
-        hostCpuFreeMap.put(1L, 2000L);
-        hostCpuFreeMap.put(2L, 1000L);
+        hostCpuFreeMap.put(1L, new Ternary<>(1000L, 0L, 10000L));
+        hostCpuFreeMap.put(2L, new Ternary<>(2000L, 0L, 10000L));
 
         hostMemoryFreeMap = new HashMap<>();
-        hostMemoryFreeMap.put(1L, 2048L * 1024L * 1024L);
-        hostMemoryFreeMap.put(2L, 512L * 1024L * 1024L);
+        hostMemoryFreeMap.put(1L, new Ternary<>(512L * 1024L * 1024L, 0L, 
8192L * 1024L * 1024L));
+        hostMemoryFreeMap.put(2L, new Ternary<>(2048L * 1024L * 1024L, 0L, 
8192L * 1024L * 1024L));
     }
 
     private void overrideDefaultConfigValue(final ConfigKey configKey,
-                                            final String name,
-                                            final Object o) throws 
IllegalAccessException, NoSuchFieldException {
+            final String name,
+            final Object o) throws IllegalAccessException, 
NoSuchFieldException {
         Field f = ConfigKey.class.getDeclaredField(name);
         f.setAccessible(true);
         f.set(configKey, o);
@@ -138,7 +134,7 @@ public class CondensedTest {
     @Test
     public void needsDrsWithCpu() throws ConfigurationException, 
NoSuchFieldException, IllegalAccessException {
         overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "cpu");
-        assertTrue(condensed.needsDrs(clusterId, cpuList, memoryList));
+        assertTrue(condensed.needsDrs(clusterId, new 
ArrayList<>(hostCpuFreeMap.values()), new 
ArrayList<>(hostMemoryFreeMap.values())));
     }
 
     /*
@@ -148,14 +144,14 @@ public class CondensedTest {
     @Test
     public void needsDrsWithMemory() throws ConfigurationException, 
NoSuchFieldException, IllegalAccessException {
         overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", 
"memory");
-        assertFalse(condensed.needsDrs(clusterId, cpuList, memoryList));
+        assertFalse(condensed.needsDrs(clusterId, new 
ArrayList<>(hostCpuFreeMap.values()), new 
ArrayList<>(hostMemoryFreeMap.values())));
     }
 
     /* 3. cluster with "unknown" metric */
     @Test
-    public void needsDrsWithUnknown() throws ConfigurationException, 
NoSuchFieldException, IllegalAccessException {
+    public void needsDrsWithUnknown() throws NoSuchFieldException, 
IllegalAccessException {
         overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", 
"unknown");
-        assertThrows(ConfigurationException.class, () -> 
condensed.needsDrs(clusterId, cpuList, memoryList));
+        assertThrows(ConfigurationException.class, () -> 
condensed.needsDrs(clusterId, new ArrayList<>(hostCpuFreeMap.values()), new 
ArrayList<>(hostMemoryFreeMap.values())));
     }
 
     /**
@@ -182,7 +178,7 @@ public class CondensedTest {
      improvement = 0.3333 - 0.3333 = 0.0
     */
     @Test
-    public void getMetricsWithCpu() throws NoSuchFieldException, 
IllegalAccessException {
+    public void getMetricsWithCpu() throws NoSuchFieldException, 
IllegalAccessException, ConfigurationException {
         overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "cpu");
         Ternary<Double, Double, Double> result = 
condensed.getMetrics(clusterId, vm3, serviceOffering, destHost,
                 hostCpuFreeMap, hostMemoryFreeMap, false);
@@ -196,7 +192,7 @@ public class CondensedTest {
      improvement = 0.2 - 0.6 = -0.4
     */
     @Test
-    public void getMetricsWithMemory() throws NoSuchFieldException, 
IllegalAccessException {
+    public void getMetricsWithMemory() throws NoSuchFieldException, 
IllegalAccessException, ConfigurationException {
         overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", 
"memory");
         Ternary<Double, Double, Double> result = 
condensed.getMetrics(clusterId, vm3, serviceOffering, destHost,
                 hostCpuFreeMap, hostMemoryFreeMap, false);
@@ -204,18 +200,4 @@ public class CondensedTest {
         assertEquals(0, result.second(), 0.0);
         assertEquals(1, result.third(), 0.0);
     }
-
-    /*
-     3. cluster with default metric
-     improvement = 0.3333 + 0.2 - 0.3333 - 0.6 = -0.4
-    */
-    @Test
-    public void getMetricsWithDefault() throws NoSuchFieldException, 
IllegalAccessException {
-        overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "both");
-        Ternary<Double, Double, Double> result = 
condensed.getMetrics(clusterId, vm3, serviceOffering, destHost,
-                hostCpuFreeMap, hostMemoryFreeMap, false);
-        assertEquals(-0.4, result.first(), 0.0001);
-        assertEquals(0, result.second(), 0.0);
-        assertEquals(1, result.third(), 0.0);
-    }
 }
diff --git 
a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java
 
b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java
index 51c020fc237..11c33968a04 100644
--- 
a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java
+++ 
b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java
@@ -29,7 +29,9 @@ import java.util.Map;
 import java.util.Properties;
 
 import javax.inject.Inject;
+import javax.naming.ConfigurationException;
 
+import com.cloud.utils.Ternary;
 import org.apache.cloudstack.api.ApiErrorCode;
 import org.apache.cloudstack.api.ListClustersMetricsCmd;
 import org.apache.cloudstack.api.ListDbMetricsCmd;
@@ -55,6 +57,7 @@ import org.apache.cloudstack.api.response.StoragePoolResponse;
 import org.apache.cloudstack.api.response.UserVmResponse;
 import org.apache.cloudstack.api.response.VolumeResponse;
 import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.cluster.ClusterDrsAlgorithm;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.management.ManagementServerHost.State;
 import org.apache.cloudstack.response.ClusterMetricsResponse;
@@ -762,10 +765,13 @@ public class MetricsServiceImpl extends 
MutualExclusiveIdsManagerBase implements
             final Long clusterId = cluster.getId();
 
             // CPU and memory capacities
-            final CapacityDaoImpl.SummedCapacity cpuCapacity = 
getCapacity((int) Capacity.CAPACITY_TYPE_CPU, null, clusterId);
-            final CapacityDaoImpl.SummedCapacity memoryCapacity = 
getCapacity((int) Capacity.CAPACITY_TYPE_MEMORY, null, clusterId);
+            final CapacityDaoImpl.SummedCapacity cpuCapacity = 
getCapacity(Capacity.CAPACITY_TYPE_CPU, null, clusterId);
+            final CapacityDaoImpl.SummedCapacity memoryCapacity = 
getCapacity(Capacity.CAPACITY_TYPE_MEMORY, null, clusterId);
             final HostMetrics hostMetrics = new HostMetrics(cpuCapacity, 
memoryCapacity);
 
+            List<Ternary<Long, Long, Long>> cpuList = new ArrayList<>();
+            List<Ternary<Long, Long, Long>> memoryList = new ArrayList<>();
+
             for (final Host host: hostDao.findByClusterId(clusterId)) {
                 if (host == null || host.getType() != Host.Type.Routing) {
                     continue;
@@ -774,7 +780,18 @@ public class MetricsServiceImpl extends 
MutualExclusiveIdsManagerBase implements
                     hostMetrics.incrUpResources();
                 }
                 hostMetrics.incrTotalResources();
-                updateHostMetrics(hostMetrics, 
hostJoinDao.findById(host.getId()));
+                HostJoinVO hostJoin = hostJoinDao.findById(host.getId());
+                updateHostMetrics(hostMetrics, hostJoin);
+
+                cpuList.add(new Ternary<>(hostJoin.getCpuUsedCapacity(), 
hostJoin.getCpuReservedCapacity(), hostJoin.getCpus() * hostJoin.getSpeed()));
+                memoryList.add(new Ternary<>(hostJoin.getMemUsedCapacity(), 
hostJoin.getMemReservedCapacity(), hostJoin.getTotalMemory()));
+            }
+
+            try {
+                Double imbalance = 
ClusterDrsAlgorithm.getClusterImbalance(clusterId, cpuList, memoryList, null);
+                metricsResponse.setDrsImbalance(imbalance.isNaN() ? null : 
100.0 * imbalance);
+            } catch (ConfigurationException e) {
+                LOGGER.warn("Failed to get cluster imbalance for cluster " + 
clusterId, e);
             }
 
             metricsResponse.setState(clusterResponse.getAllocationState(), 
clusterResponse.getManagedState());
diff --git 
a/plugins/metrics/src/main/java/org/apache/cloudstack/response/ClusterMetricsResponse.java
 
b/plugins/metrics/src/main/java/org/apache/cloudstack/response/ClusterMetricsResponse.java
index 18ea57d711c..5c25021ac6b 100644
--- 
a/plugins/metrics/src/main/java/org/apache/cloudstack/response/ClusterMetricsResponse.java
+++ 
b/plugins/metrics/src/main/java/org/apache/cloudstack/response/ClusterMetricsResponse.java
@@ -94,6 +94,10 @@ public class ClusterMetricsResponse extends ClusterResponse 
implements HostMetri
     @Param(description = "memory allocated disable threshold exceeded")
     private Boolean memoryAllocatedDisableThresholdExceeded;
 
+    @SerializedName("drsimbalance")
+    @Param(description = "DRS imbalance for the cluster")
+    private String drsImbalance;
+
     public void setState(final String allocationState, final String 
managedState) {
         this.state = allocationState;
         if (managedState.equals("Unmanaged")) {
@@ -208,4 +212,12 @@ public class ClusterMetricsResponse extends 
ClusterResponse implements HostMetri
             this.memoryAllocatedDisableThresholdExceeded = (1.0 * memAllocated 
/ memTotal) > threshold;
         }
     }
+
+    public void setDrsImbalance(Double drsImbalance) {
+        if (drsImbalance != null) {
+            this.drsImbalance = String.format("%.2f%%", drsImbalance);
+        } else {
+            this.drsImbalance = null;
+        }
+    }
 }
diff --git 
a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java 
b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
index 94d992f8636..d84673efd6b 100644
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -577,6 +577,7 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
         
weightBasedParametersForValidation.add(Config.VmUserDispersionWeight.key());
         
weightBasedParametersForValidation.add(CapacityManager.SecondaryStorageCapacityThreshold.key());
         
weightBasedParametersForValidation.add(ClusterDrsService.ClusterDrsImbalanceThreshold.key());
+        
weightBasedParametersForValidation.add(ClusterDrsService.ClusterDrsImbalanceSkipThreshold.key());
 
     }
 
diff --git 
a/server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java 
b/server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java
index f949233e8e8..9fe00fade61 100644
--- 
a/server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java
+++ 
b/server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java
@@ -353,10 +353,10 @@ public class ClusterDrsServiceImpl extends ManagerBase 
implements ClusterDrsServ
         List<HostJoinVO> hostJoinList = hostJoinDao.searchByIds(
                 hostList.stream().map(HostVO::getId).toArray(Long[]::new));
 
-        Map<Long, Long> hostCpuMap = 
hostJoinList.stream().collect(Collectors.toMap(HostJoinVO::getId,
-                hostJoin -> hostJoin.getCpus() * hostJoin.getSpeed() - 
hostJoin.getCpuReservedCapacity() - hostJoin.getCpuUsedCapacity()));
-        Map<Long, Long> hostMemoryMap = 
hostJoinList.stream().collect(Collectors.toMap(HostJoinVO::getId,
-                hostJoin -> hostJoin.getTotalMemory() - 
hostJoin.getMemUsedCapacity() - hostJoin.getMemReservedCapacity()));
+        Map<Long, Ternary<Long, Long, Long>> hostCpuMap = 
hostJoinList.stream().collect(Collectors.toMap(HostJoinVO::getId,
+                hostJoin -> new Ternary<>(hostJoin.getCpuUsedCapacity(), 
hostJoin.getCpuReservedCapacity(), hostJoin.getCpus() * hostJoin.getSpeed())));
+        Map<Long, Ternary<Long, Long, Long>> hostMemoryMap = 
hostJoinList.stream().collect(Collectors.toMap(HostJoinVO::getId,
+                hostJoin -> new Ternary<>(hostJoin.getMemUsedCapacity(), 
hostJoin.getMemReservedCapacity(), hostJoin.getTotalMemory())));
 
         Map<Long, ServiceOffering> vmIdServiceOfferingMap = new HashMap<>();
 
@@ -375,6 +375,8 @@ public class ClusterDrsServiceImpl extends ManagerBase 
implements ClusterDrsServ
                 logger.debug("VM migrating to it's original host or no host 
found for migration");
                 break;
             }
+            logger.debug(String.format("Plan for VM %s to migrate from host %s 
to host %s", vm.getUuid(),
+                    hostMap.get(vm.getHostId()).getUuid(), 
destHost.getUuid()));
 
             ServiceOffering serviceOffering = 
vmIdServiceOfferingMap.get(vm.getId());
             migrationPlan.add(new Ternary<>(vm, hostMap.get(vm.getHostId()), 
hostMap.get(destHost.getId())));
@@ -387,10 +389,11 @@ public class ClusterDrsServiceImpl extends ManagerBase 
implements ClusterDrsServ
             long vmCpu = (long) serviceOffering.getCpu() * 
serviceOffering.getSpeed();
             long vmMemory = serviceOffering.getRamSize() * 1024L * 1024L;
 
-            hostCpuMap.put(vm.getHostId(), hostCpuMap.get(vm.getHostId()) + 
vmCpu);
-            hostCpuMap.put(destHost.getId(), hostCpuMap.get(destHost.getId()) 
- vmCpu);
-            hostMemoryMap.put(vm.getHostId(), 
hostMemoryMap.get(vm.getHostId()) + vmMemory);
-            hostMemoryMap.put(destHost.getId(), 
hostMemoryMap.get(destHost.getId()) - vmMemory);
+            // Updating the map as per the migration
+            
hostCpuMap.get(vm.getHostId()).first(hostCpuMap.get(vm.getHostId()).first() - 
vmCpu);
+            
hostCpuMap.get(destHost.getId()).first(hostCpuMap.get(destHost.getId()).first() 
+ vmCpu);
+            
hostMemoryMap.get(vm.getHostId()).first(hostMemoryMap.get(vm.getHostId()).first()
 - vmMemory);
+            
hostMemoryMap.get(destHost.getId()).first(hostMemoryMap.get(destHost.getId()).first()
 + vmMemory);
             vm.setHostId(destHost.getId());
             iteration++;
         }
@@ -443,8 +446,8 @@ public class ClusterDrsServiceImpl extends ManagerBase 
implements ClusterDrsServ
     Pair<VirtualMachine, Host> getBestMigration(Cluster cluster, 
ClusterDrsAlgorithm algorithm,
             List<VirtualMachine> vmList,
             Map<Long, ServiceOffering> vmIdServiceOfferingMap,
-            Map<Long, Long> hostCpuCapacityMap,
-            Map<Long, Long> hostMemoryCapacityMap) {
+            Map<Long, Ternary<Long, Long, Long>> hostCpuCapacityMap,
+            Map<Long, Ternary<Long, Long, Long>> hostMemoryCapacityMap) throws 
ConfigurationException {
         double improvement = 0;
         Pair<VirtualMachine, Host> bestMigration = new Pair<>(null, null);
 
@@ -627,8 +630,9 @@ public class ClusterDrsServiceImpl extends ManagerBase 
implements ClusterDrsServ
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[]{ClusterDrsPlanExpireInterval, 
ClusterDrsEnabled, ClusterDrsInterval,
-                ClusterDrsMaxMigrations, ClusterDrsAlgorithm, 
ClusterDrsImbalanceThreshold, ClusterDrsMetric};
+        return new ConfigKey<?>[]{ClusterDrsPlanExpireInterval, 
ClusterDrsEnabled, ClusterDrsInterval, ClusterDrsMaxMigrations,
+                ClusterDrsAlgorithm, ClusterDrsImbalanceThreshold, 
ClusterDrsMetric, ClusterDrsMetricType, ClusterDrsMetricUseRatio,
+                ClusterDrsImbalanceSkipThreshold};
     }
 
     @Override
diff --git 
a/server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java
 
b/server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java
index e82b39a47ec..8aed790af0a 100644
--- 
a/server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java
+++ 
b/server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java
@@ -180,14 +180,12 @@ public class ClusterDrsServiceImplTest {
         Mockito.when(hostJoin1.getCpuUsedCapacity()).thenReturn(1000L);
         Mockito.when(hostJoin1.getCpuReservedCapacity()).thenReturn(0L);
         Mockito.when(hostJoin1.getMemUsedCapacity()).thenReturn(1024L);
-        Mockito.when(hostJoin1.getMemReservedCapacity()).thenReturn(512L);
 
         HostJoinVO hostJoin2 = Mockito.mock(HostJoinVO.class);
         Mockito.when(hostJoin2.getId()).thenReturn(2L);
         Mockito.when(hostJoin2.getCpuUsedCapacity()).thenReturn(1000L);
         Mockito.when(hostJoin2.getCpuReservedCapacity()).thenReturn(0L);
         Mockito.when(hostJoin2.getMemUsedCapacity()).thenReturn(1024L);
-        Mockito.when(hostJoin2.getMemReservedCapacity()).thenReturn(512L);
 
         List<VMInstanceVO> vmList = new ArrayList<>();
         vmList.add(vm1);
@@ -299,7 +297,7 @@ public class ClusterDrsServiceImplTest {
         
Mockito.when(clusterDrsService.getResponseObjectForMigrations(Mockito.anyList())).thenReturn(
                 List.of(migrationResponse));
 
-        try(MockedStatic<ActionEventUtils> ignored = 
Mockito.mockStatic(ActionEventUtils.class)) {
+        try (MockedStatic<ActionEventUtils> ignored = 
Mockito.mockStatic(ActionEventUtils.class)) {
             Mockito.when(ActionEventUtils.onActionEvent(Mockito.anyLong(), 
Mockito.anyLong(),
                     Mockito.anyLong(),
                     Mockito.anyString(), Mockito.anyString(),
@@ -350,7 +348,7 @@ public class ClusterDrsServiceImplTest {
     }
 
     @Test
-    public void testGetBestMigration() {
+    public void testGetBestMigration() throws ConfigurationException {
         ClusterVO cluster = Mockito.mock(ClusterVO.class);
         Mockito.when(cluster.getId()).thenReturn(1L);
 
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 71da3c6d0aa..ddb4aeac64c 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -782,6 +782,7 @@
 "label.dpd": "Dead peer detection",
 "label.driver": "Driver",
 "label.drs": "DRS",
+"label.drsimbalance": "DRS imbalance",
 "label.drs.plan": "DRS Plan",
 "label.drs.generate.plan": "Generate DRS plan",
 "label.drs.no.plan.generated": "No DRS plan has been generated as the cluster 
is not imbalanced according to the threshold set",
@@ -1298,7 +1299,7 @@
 "label.memoryallocatedgb": "Memory allocated",
 "label.memorylimit": "Memory limits (MiB)",
 "label.memorymaxdeviation": "Deviation",
-"label.memorytotal": "Memory allocated",
+"label.memorytotal": "Memory total",
 "label.memorytotalgb": "Memory total",
 "label.memoryused": "Used memory",
 "label.memoryusedgb": "Memory used",
diff --git a/ui/src/components/view/ListView.vue 
b/ui/src/components/view/ListView.vue
index 2beec672a3c..cf3d9361638 100644
--- a/ui/src/components/view/ListView.vue
+++ b/ui/src/components/view/ListView.vue
@@ -337,6 +337,9 @@
       <template v-if="column.key === 'templateversion'">
         <span>  {{ record.version }} </span>
       </template>
+      <template v-if="column.key === 'drsimbalance'">
+        <span>  {{ record.drsimbalance }} </span>
+      </template>
       <template v-if="column.key === 'softwareversion'">
         <span>  {{ record.softwareversion ? record.softwareversion : 'N/A' }} 
</span>
       </template>
diff --git a/ui/src/config/section/infra/clusters.js 
b/ui/src/config/section/infra/clusters.js
index 8fc4ebd54a9..ab8bea6b79d 100644
--- a/ui/src/config/section/infra/clusters.js
+++ b/ui/src/config/section/infra/clusters.js
@@ -26,7 +26,7 @@ export default {
   permission: ['listClustersMetrics'],
   columns: () => {
     const fields = ['name', 'state', 'allocationstate', 'clustertype', 
'hypervisortype', 'hosts']
-    const metricsFields = ['cpuused', 'cpumaxdeviation', 'cpuallocated', 
'cputotal', 'memoryused', 'memorymaxdeviation', 'memoryallocated', 
'memorytotal']
+    const metricsFields = ['cpuused', 'cpumaxdeviation', 'cpuallocated', 
'cputotal', 'memoryused', 'memorymaxdeviation', 'memoryallocated', 
'memorytotal', 'drsimbalance']
     if (store.getters.metrics) {
       fields.push(...metricsFields)
     }
@@ -34,7 +34,7 @@ export default {
     fields.push('zonename')
     return fields
   },
-  details: ['name', 'id', 'allocationstate', 'clustertype', 'managedstate', 
'hypervisortype', 'podname', 'zonename'],
+  details: ['name', 'id', 'allocationstate', 'clustertype', 'managedstate', 
'hypervisortype', 'podname', 'zonename', 'drsimbalance'],
   related: [{
     name: 'host',
     title: 'label.hosts',
diff --git a/ui/src/views/infra/ClusterDRSTab.vue 
b/ui/src/views/infra/ClusterDRSTab.vue
index 4ff255bf718..d53199ced87 100644
--- a/ui/src/views/infra/ClusterDRSTab.vue
+++ b/ui/src/views/infra/ClusterDRSTab.vue
@@ -57,7 +57,8 @@
         :columns="migrationColumns"
         :dataSource="record.migrations"
         :rowKey="(record, index) => index"
-        :pagination="{hideOnSinglePage: true, showSizeChanger: true}">
+        :pagination="{hideOnSinglePage: true, showSizeChanger: true}"
+        @resizeColumn="resizeColumn">
         <template #bodyCell="{ column, text, record }">
           <template v-if="column.key === 'vm'">
             <router-link :to="{ path: '/vm/' + record.virtualmachineid }">
@@ -117,7 +118,8 @@
       :columns="generatedPlanMigrationColumns"
       :dataSource="generatedMigrations"
       :rowKey="(record, index) => index"
-      :pagination="{ showTotal: (total, range) => [range[0], '-', range[1], 
$t('label.of'), total, $t('label.items')].join(' ') }" >
+      :pagination="{ showTotal: (total, range) => [range[0], '-', range[1], 
$t('label.of'), total, $t('label.items')].join(' ') }"
+      @resizeColumn="resizeColumn" >
       <template #bodyCell="{ column, text, record }">
         <template v-if="column.key === 'vm'">
           <router-link :to="{ path: '/vm/' + record.virtualmachineid }">
@@ -166,19 +168,22 @@ export default {
         key: 'vm',
         title: this.$t('label.vm'),
         dataIndex: 'vm',
-        ellipsis: true
+        ellipsis: true,
+        resizable: true
       },
       {
         key: 'sourcehost',
         title: this.$t('label.sourcehost'),
         dataIndex: 'sourcehost',
-        ellipsis: true
+        ellipsis: true,
+        resizable: true
       },
       {
         key: 'destinationhost',
         title: this.$t('label.desthost'),
         dataIndex: 'created',
-        ellipsis: true
+        ellipsis: true,
+        resizable: true
       }
     ]
     return {
@@ -291,6 +296,9 @@ export default {
     closeModal () {
       this.showModal = false
       this.generatedMigrations = reactive([])
+    },
+    resizeColumn (w, col) {
+      col.width = w
     }
   }
 }

Reply via email to