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

pearl11594 pushed a commit to branch fr06-cks-template-register
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 2cf8d674a833ffb7064a05e94874ddab5d7e9dbc
Author: nvazquez <nicovazque...@gmail.com>
AuthorDate: Thu Feb 8 00:39:42 2024 -0300

    In progress scale kubernetes cluster
---
 .../cluster/KubernetesClusterHelper.java           |   5 +-
 .../cluster/KubernetesClusterHelperImpl.java       |  66 ++++++++++
 .../cluster/KubernetesClusterManagerImpl.java      |  97 ++++++++++-----
 .../KubernetesClusterScaleWorker.java              |   4 +
 .../cluster/CreateKubernetesClusterCmd.java        |  56 +--------
 .../cluster/ScaleKubernetesClusterCmd.java         |  13 ++
 .../cluster/KubernetesClusterHelperImplTest.java   | 100 +++++++++++++++
 .../cluster/KubernetesClusterManagerImplTest.java  |  51 ++++++++
 .../cluster/CreateKubernetesClusterCmdTest.java    | 136 ---------------------
 ui/src/views/compute/ScaleKubernetesCluster.vue    | 130 +++++++++++++++++---
 10 files changed, 418 insertions(+), 240 deletions(-)

diff --git 
a/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java 
b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java
index 4b61fe5ad03..548a016c1c5 100644
--- 
a/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java
+++ 
b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java
@@ -19,13 +19,16 @@ package com.cloud.kubernetes.cluster;
 import com.cloud.utils.component.Adapter;
 import org.apache.cloudstack.acl.ControlledEntity;
 
+import java.util.Map;
+
 public interface KubernetesClusterHelper extends Adapter {
 
     enum KubernetesClusterNodeType {
-        CONTROL, WORKER, ETCD
+        CONTROL, WORKER, ETCD, ALL
     }
 
     ControlledEntity findByUuid(String uuid);
     ControlledEntity findByVmId(long vmId);
     boolean isValidNodeType(String nodeType);
+    Map<String, Long> getServiceOfferingNodeTypeMap(Map<String, Map<String, 
String>> serviceOfferingNodeTypeMap);
 }
diff --git 
a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java
 
b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java
index 639e17bd560..43802c91e41 100644
--- 
a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java
+++ 
b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java
@@ -16,25 +16,37 @@
 // under the License.
 package com.cloud.kubernetes.cluster;
 
+import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao;
 import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.service.dao.ServiceOfferingDao;
 import com.cloud.utils.component.AdapterBase;
+import com.cloud.vm.VmDetailConstants;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
+import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
 import javax.inject.Inject;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 
 @Component
 public class KubernetesClusterHelperImpl extends AdapterBase implements 
KubernetesClusterHelper, Configurable {
 
+    public static final Logger LOGGER = 
Logger.getLogger(KubernetesClusterHelperImpl.class.getName());
+
     @Inject
     private KubernetesClusterDao kubernetesClusterDao;
     @Inject
     private KubernetesClusterVmMapDao kubernetesClusterVmMapDao;
+    @Inject
+    protected ServiceOfferingDao serviceOfferingDao;
 
     @Override
     public ControlledEntity findByUuid(String uuid) {
@@ -63,6 +75,60 @@ public class KubernetesClusterHelperImpl extends AdapterBase 
implements Kubernet
         }
     }
 
+    protected void checkNodeTypeOfferingEntryCompleteness(String nodeTypeStr, 
String serviceOfferingUuid) {
+        if (StringUtils.isAnyEmpty(nodeTypeStr, serviceOfferingUuid)) {
+            String error = String.format("Incomplete Node Type to Service 
Offering ID mapping: '%s' -> '%s'", nodeTypeStr, serviceOfferingUuid);
+            LOGGER.error(error);
+            throw new InvalidParameterValueException(error);
+        }
+    }
+
+    protected void checkNodeTypeOfferingEntryValues(String nodeTypeStr, 
ServiceOffering serviceOffering, String serviceOfferingUuid) {
+        if (!isValidNodeType(nodeTypeStr)) {
+            String error = String.format("The provided value '%s' for Node 
Type is invalid", nodeTypeStr);
+            LOGGER.error(error);
+            throw new InvalidParameterValueException(String.format(error));
+        }
+        if (serviceOffering == null) {
+            String error = String.format("Cannot find a service offering with 
ID %s", serviceOfferingUuid);
+            LOGGER.error(error);
+            throw new InvalidParameterValueException(error);
+        }
+    }
+
+    protected void addNodeTypeOfferingEntry(String nodeTypeStr, String 
serviceOfferingUuid, ServiceOffering serviceOffering, Map<String, Long> 
mapping) {
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug(String.format("Node Type: '%s' should use Service 
Offering ID: '%s'", nodeTypeStr, serviceOfferingUuid));
+        }
+        KubernetesClusterNodeType nodeType = 
KubernetesClusterNodeType.valueOf(nodeTypeStr.toUpperCase());
+        mapping.put(nodeType.name(), serviceOffering.getId());
+    }
+
+    protected void 
processNodeTypeOfferingEntryAndAddToMappingIfValid(Map<String, String> entry, 
Map<String, Long> mapping) {
+        if (MapUtils.isEmpty(entry)) {
+            return;
+        }
+        String nodeTypeStr = entry.get(VmDetailConstants.CKS_NODE_TYPE);
+        String serviceOfferingUuid = entry.get(VmDetailConstants.OFFERING);
+        checkNodeTypeOfferingEntryCompleteness(nodeTypeStr, 
serviceOfferingUuid);
+
+        ServiceOffering serviceOffering = 
serviceOfferingDao.findByUuid(serviceOfferingUuid);
+        checkNodeTypeOfferingEntryValues(nodeTypeStr, serviceOffering, 
serviceOfferingUuid);
+
+        addNodeTypeOfferingEntry(nodeTypeStr, serviceOfferingUuid, 
serviceOffering, mapping);
+    }
+
+    @Override
+    public Map<String, Long> getServiceOfferingNodeTypeMap(Map<String, 
Map<String, String>> serviceOfferingNodeTypeMap) {
+        Map<String, Long> mapping = new HashMap<>();
+        if (MapUtils.isNotEmpty(serviceOfferingNodeTypeMap)) {
+            for (Map<String, String> entry : 
serviceOfferingNodeTypeMap.values()) {
+                processNodeTypeOfferingEntryAndAddToMappingIfValid(entry, 
mapping);
+            }
+        }
+        return mapping;
+    }
+
     @Override
     public String getConfigComponentName() {
         return KubernetesClusterHelper.class.getSimpleName();
diff --git 
a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java
 
b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java
index 93dd48b34ae..47cedd996a2 100644
--- 
a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java
+++ 
b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java
@@ -16,6 +16,7 @@
 // under the License.
 package com.cloud.kubernetes.cluster;
 
+import static 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.ALL;
 import static 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.CONTROL;
 import static 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.ETCD;
 import static 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.WORKER;
@@ -76,6 +77,7 @@ import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.managed.context.ManagedContextRunnable;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
@@ -1008,12 +1010,13 @@ public class KubernetesClusterManagerImpl extends 
ManagerBase implements Kuberne
 
     private void 
validateKubernetesClusterScaleParameters(ScaleKubernetesClusterCmd cmd) {
         final Long kubernetesClusterId = cmd.getId();
-        final Long serviceOfferingId = cmd.getServiceOfferingId();
         final Long clusterSize = cmd.getClusterSize();
         final List<Long> nodeIds = cmd.getNodeIds();
         final Boolean isAutoscalingEnabled = cmd.isAutoscalingEnabled();
         final Long minSize = cmd.getMinSize();
         final Long maxSize = cmd.getMaxSize();
+        final Long defaultServiceOfferingId = cmd.getServiceOfferingId();
+        final Map<String, Long> serviceOfferingNodeTypeMap = 
cmd.getServiceOfferingNodeTypeMap();
 
         if (kubernetesClusterId == null || kubernetesClusterId < 1L) {
             throw new InvalidParameterValueException("Invalid Kubernetes 
cluster ID");
@@ -1029,7 +1032,8 @@ public class KubernetesClusterManagerImpl extends 
ManagerBase implements Kuberne
             logAndThrow(Level.WARN, String.format("Unable to find zone for 
Kubernetes cluster : %s", kubernetesCluster.getName()));
         }
 
-        if (serviceOfferingId == null && clusterSize == null && nodeIds == 
null && isAutoscalingEnabled == null) {
+        if (defaultServiceOfferingId == null && 
isAnyNodeOfferingEmpty(serviceOfferingNodeTypeMap)
+                && clusterSize == null && nodeIds == null && 
isAutoscalingEnabled == null) {
             throw new InvalidParameterValueException(String.format("Kubernetes 
cluster %s cannot be scaled, either service offering or cluster size or nodeids 
to be removed or autoscaling must be passed", kubernetesCluster.getName()));
         }
 
@@ -1076,8 +1080,9 @@ public class KubernetesClusterManagerImpl extends 
ManagerBase implements Kuberne
             }
         }
 
+        Long workerOfferingId = serviceOfferingNodeTypeMap != null ? 
serviceOfferingNodeTypeMap.getOrDefault(WORKER.name(), null) : null;
         if (nodeIds != null) {
-            if (clusterSize != null || serviceOfferingId != null) {
+            if (clusterSize != null || defaultServiceOfferingId != null || 
workerOfferingId != null) {
                 throw new InvalidParameterValueException("nodeids can not be 
passed along with clustersize or service offering");
             }
             List<KubernetesClusterVmMapVO> nodes = 
kubernetesClusterVmMapDao.listByClusterIdAndVmIdsIn(kubernetesCluster.getId(), 
nodeIds);
@@ -1097,37 +1102,53 @@ public class KubernetesClusterManagerImpl extends 
ManagerBase implements Kuberne
             }
         }
 
-        ServiceOffering serviceOffering = null;
-        if (serviceOfferingId != null) {
-            serviceOffering = serviceOfferingDao.findById(serviceOfferingId);
-            if (serviceOffering == null) {
-                throw new InvalidParameterValueException("Failed to find 
service offering ID: " + serviceOfferingId);
-            } else {
-                if (serviceOffering.isDynamic()) {
-                    throw new 
InvalidParameterValueException(String.format("Custom service offerings are not 
supported for Kubernetes clusters. Kubernetes cluster : %s, service offering : 
%s", kubernetesCluster.getName(), serviceOffering.getName()));
-                }
-                if (serviceOffering.getCpu() < MIN_KUBERNETES_CLUSTER_NODE_CPU 
|| serviceOffering.getRamSize() < MIN_KUBERNETES_CLUSTER_NODE_RAM_SIZE) {
-                    throw new 
InvalidParameterValueException(String.format("Kubernetes cluster : %s cannot be 
scaled with service offering : %s, Kubernetes cluster template(CoreOS) needs 
minimum %d vCPUs and %d MB RAM",
-                            kubernetesCluster.getName(), 
serviceOffering.getName(), MIN_KUBERNETES_CLUSTER_NODE_CPU, 
MIN_KUBERNETES_CLUSTER_NODE_RAM_SIZE));
-                }
-                if (serviceOffering.getCpu() < clusterVersion.getMinimumCpu()) 
{
-                    throw new 
InvalidParameterValueException(String.format("Kubernetes cluster : %s cannot be 
scaled with service offering : %s, associated Kubernetes version : %s needs 
minimum %d vCPUs",
-                            kubernetesCluster.getName(), 
serviceOffering.getName(), clusterVersion.getName(), 
clusterVersion.getMinimumCpu()));
+        validateServiceOfferingsForNodeTypesScale(serviceOfferingNodeTypeMap, 
defaultServiceOfferingId, kubernetesCluster, clusterVersion);
+
+        validateKubernetesClusterScaleSize(kubernetesCluster, clusterSize, 
maxClusterSize, zone);
+    }
+
+    protected void validateServiceOfferingsForNodeTypesScale(Map<String, Long> 
map, Long defaultServiceOfferingId, KubernetesClusterVO kubernetesCluster, 
KubernetesSupportedVersion clusterVersion) {
+        for (String key : CLUSTER_NODES_TYPES_LIST) {
+            Long serviceOfferingId = map.getOrDefault(key, 
defaultServiceOfferingId);
+            if (serviceOfferingId != null) {
+                ServiceOffering serviceOffering = 
serviceOfferingDao.findById(serviceOfferingId);
+                if (serviceOffering == null) {
+                    throw new InvalidParameterValueException("Failed to find 
service offering ID: " + serviceOfferingId);
                 }
-                if (serviceOffering.getRamSize() < 
clusterVersion.getMinimumRamSize()) {
-                    throw new 
InvalidParameterValueException(String.format("Kubernetes cluster : %s cannot be 
scaled with service offering : %s, associated Kubernetes version : %s needs 
minimum %d MB RAM",
-                            kubernetesCluster.getName(), 
serviceOffering.getName(), clusterVersion.getName(), 
clusterVersion.getMinimumRamSize()));
+                checkServiceOfferingForNodesScale(serviceOffering, 
kubernetesCluster, clusterVersion);
+                final ServiceOffering existingServiceOffering = 
serviceOfferingDao.findById(kubernetesCluster.getServiceOfferingId());
+                if 
(KubernetesCluster.State.Running.equals(kubernetesCluster.getState()) && 
(serviceOffering.getRamSize() < existingServiceOffering.getRamSize() ||
+                        serviceOffering.getCpu() * serviceOffering.getSpeed() 
< existingServiceOffering.getCpu() * existingServiceOffering.getSpeed())) {
+                    logAndThrow(Level.WARN, String.format("Kubernetes cluster 
cannot be scaled down for service offering. Service offering : %s offers lesser 
resources as compared to service offering : %s of Kubernetes cluster : %s",
+                            serviceOffering.getName(), 
existingServiceOffering.getName(), kubernetesCluster.getName()));
                 }
             }
-            final ServiceOffering existingServiceOffering = 
serviceOfferingDao.findById(kubernetesCluster.getServiceOfferingId());
-            if 
(KubernetesCluster.State.Running.equals(kubernetesCluster.getState()) && 
(serviceOffering.getRamSize() < existingServiceOffering.getRamSize() ||
-                    serviceOffering.getCpu() * serviceOffering.getSpeed() < 
existingServiceOffering.getCpu() * existingServiceOffering.getSpeed())) {
-                logAndThrow(Level.WARN, String.format("Kubernetes cluster 
cannot be scaled down for service offering. Service offering : %s offers lesser 
resources as compared to service offering : %s of Kubernetes cluster : %s",
-                        serviceOffering.getName(), 
existingServiceOffering.getName(), kubernetesCluster.getName()));
-            }
         }
+    }
 
-        validateKubernetesClusterScaleSize(kubernetesCluster, clusterSize, 
maxClusterSize, zone);
+    protected void checkServiceOfferingForNodesScale(ServiceOffering 
serviceOffering, KubernetesClusterVO kubernetesCluster, 
KubernetesSupportedVersion clusterVersion) {
+        if (serviceOffering.isDynamic()) {
+            throw new InvalidParameterValueException(String.format("Custom 
service offerings are not supported for Kubernetes clusters. Kubernetes cluster 
: %s, service offering : %s", kubernetesCluster.getName(), 
serviceOffering.getName()));
+        }
+        if (serviceOffering.getCpu() < MIN_KUBERNETES_CLUSTER_NODE_CPU || 
serviceOffering.getRamSize() < MIN_KUBERNETES_CLUSTER_NODE_RAM_SIZE) {
+            throw new InvalidParameterValueException(String.format("Kubernetes 
cluster : %s cannot be scaled with service offering : %s, Kubernetes cluster 
template(CoreOS) needs minimum %d vCPUs and %d MB RAM",
+                    kubernetesCluster.getName(), serviceOffering.getName(), 
MIN_KUBERNETES_CLUSTER_NODE_CPU, MIN_KUBERNETES_CLUSTER_NODE_RAM_SIZE));
+        }
+        if (serviceOffering.getCpu() < clusterVersion.getMinimumCpu()) {
+            throw new InvalidParameterValueException(String.format("Kubernetes 
cluster : %s cannot be scaled with service offering : %s, associated Kubernetes 
version : %s needs minimum %d vCPUs",
+                    kubernetesCluster.getName(), serviceOffering.getName(), 
clusterVersion.getName(), clusterVersion.getMinimumCpu()));
+        }
+        if (serviceOffering.getRamSize() < clusterVersion.getMinimumRamSize()) 
{
+            throw new InvalidParameterValueException(String.format("Kubernetes 
cluster : %s cannot be scaled with service offering : %s, associated Kubernetes 
version : %s needs minimum %d MB RAM",
+                    kubernetesCluster.getName(), serviceOffering.getName(), 
clusterVersion.getName(), clusterVersion.getMinimumRamSize()));
+        }
+    }
+
+    protected boolean isAnyNodeOfferingEmpty(Map<String, Long> map) {
+        if (MapUtils.isEmpty(map)) {
+            return false;
+        }
+        return map.values().stream().anyMatch(Objects::isNull);
     }
 
     private void 
validateKubernetesClusterUpgradeParameters(UpgradeKubernetesClusterCmd cmd) {
@@ -1652,12 +1673,14 @@ public class KubernetesClusterManagerImpl extends 
ManagerBase implements Kuberne
             logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled");
         }
         validateKubernetesClusterScaleParameters(cmd);
+        Map<String, ServiceOffering> nodeToOfferingMap = 
createNodeTypeToServiceOfferingMap(cmd.getServiceOfferingNodeTypeMap(), 
cmd.getServiceOfferingId());
 
         KubernetesClusterVO kubernetesCluster = 
kubernetesClusterDao.findById(cmd.getId());
         String[] keys = getServiceUserKeys(kubernetesCluster);
         KubernetesClusterScaleWorker scaleWorker =
             new 
KubernetesClusterScaleWorker(kubernetesClusterDao.findById(cmd.getId()),
                 serviceOfferingDao.findById(cmd.getServiceOfferingId()),
+                nodeToOfferingMap,
                 cmd.getClusterSize(),
                 cmd.getNodeIds(),
                 cmd.isAutoscalingEnabled(),
@@ -1669,6 +1692,22 @@ public class KubernetesClusterManagerImpl extends 
ManagerBase implements Kuberne
         return scaleWorker.scaleCluster();
     }
 
+    protected Map<String, ServiceOffering> 
createNodeTypeToServiceOfferingMap(Map<String, Long> idsMapping,
+                                                                              
Long serviceOfferingId) {
+        Map<String, ServiceOffering> map = new HashMap<>();
+        if (MapUtils.isEmpty(idsMapping) && serviceOfferingId != null) {
+            map.put(ALL.name(), 
serviceOfferingDao.findById(serviceOfferingId));
+            return map;
+        }
+        for (String key : CLUSTER_NODES_TYPES_LIST) {
+            if (!idsMapping.containsKey(key)) {
+                continue;
+            }
+            map.put(key, serviceOfferingDao.findById(idsMapping.get(key)));
+        }
+        return map;
+    }
+
     @Override
     public boolean upgradeKubernetesCluster(UpgradeKubernetesClusterCmd cmd) 
throws CloudRuntimeException {
         if (!KubernetesServiceEnabled.value()) {
diff --git 
a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterScaleWorker.java
 
b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterScaleWorker.java
index df94642a881..1468b1cca5f 100644
--- 
a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterScaleWorker.java
+++ 
b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterScaleWorker.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
@@ -64,6 +65,7 @@ public class KubernetesClusterScaleWorker extends 
KubernetesClusterResourceModif
     protected VMInstanceDao vmInstanceDao;
 
     private ServiceOffering serviceOffering;
+    private Map<String, ServiceOffering> serviceOfferingNodeTypeMap;
     private Long clusterSize;
     private List<Long> nodeIds;
     private KubernetesCluster.State originalState;
@@ -75,6 +77,7 @@ public class KubernetesClusterScaleWorker extends 
KubernetesClusterResourceModif
 
     public KubernetesClusterScaleWorker(final KubernetesCluster 
kubernetesCluster,
                                         final ServiceOffering serviceOffering,
+                                        final Map<String, ServiceOffering> 
serviceOfferingNodeTypeMap,
                                         final Long clusterSize,
                                         final List<Long> nodeIds,
                                         final Boolean isAutoscalingEnabled,
@@ -83,6 +86,7 @@ public class KubernetesClusterScaleWorker extends 
KubernetesClusterResourceModif
                                         final KubernetesClusterManagerImpl 
clusterManager) {
         super(kubernetesCluster, clusterManager);
         this.serviceOffering = serviceOffering;
+        this.serviceOfferingNodeTypeMap = serviceOfferingNodeTypeMap;
         this.nodeIds = nodeIds;
         this.isAutoscalingEnabled = isAutoscalingEnabled;
         this.minSize = minSize;
diff --git 
a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java
 
b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java
index 08ed1b1b280..466b55c0d2b 100644
--- 
a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java
+++ 
b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java
@@ -17,16 +17,12 @@
 package org.apache.cloudstack.api.command.user.kubernetes.cluster;
 
 import java.security.InvalidParameterException;
-import java.util.HashMap;
 import java.util.Map;
 
 import javax.inject.Inject;
 
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.kubernetes.cluster.KubernetesClusterHelper;
-import 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType;
-import com.cloud.offering.ServiceOffering;
-import com.cloud.vm.VmDetailConstants;
 import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
 import org.apache.cloudstack.api.ACL;
@@ -46,7 +42,6 @@ import org.apache.cloudstack.api.response.ProjectResponse;
 import org.apache.cloudstack.api.response.ServiceOfferingResponse;
 import org.apache.cloudstack.api.response.ZoneResponse;
 import org.apache.cloudstack.context.CallContext;
-import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
@@ -268,57 +263,8 @@ public class CreateKubernetesClusterCmd extends 
BaseAsyncCreateCmd {
         return clusterType;
     }
 
-    protected void checkNodeTypeOfferingEntryCompleteness(String nodeTypeStr, 
String serviceOfferingUuid) {
-        if (StringUtils.isAnyEmpty(nodeTypeStr, serviceOfferingUuid)) {
-            String error = String.format("Incomplete Node Type to Service 
Offering ID mapping: '%s' -> '%s'", nodeTypeStr, serviceOfferingUuid);
-            LOGGER.error(error);
-            throw new InvalidParameterValueException(error);
-        }
-    }
-
-    protected void checkNodeTypeOfferingEntryValues(String nodeTypeStr, 
ServiceOffering serviceOffering, String serviceOfferingUuid) {
-        if (!kubernetesClusterHelper.isValidNodeType(nodeTypeStr)) {
-            String error = String.format("The provided value '%s' for Node 
Type is invalid", nodeTypeStr);
-            LOGGER.error(error);
-            throw new InvalidParameterValueException(String.format(error));
-        }
-        if (serviceOffering == null) {
-            String error = String.format("Cannot find a service offering with 
ID %s", serviceOfferingUuid);
-            LOGGER.error(error);
-            throw new InvalidParameterValueException(error);
-        }
-    }
-
-    protected void addNodeTypeOfferingEntry(String nodeTypeStr, String 
serviceOfferingUuid, ServiceOffering serviceOffering, Map<String, Long> 
mapping) {
-        if (LOGGER.isDebugEnabled()) {
-            LOGGER.debug(String.format("Node Type: '%s' should use Service 
Offering ID: '%s'", nodeTypeStr, serviceOfferingUuid));
-        }
-        KubernetesClusterNodeType nodeType = 
KubernetesClusterNodeType.valueOf(nodeTypeStr.toUpperCase());
-        mapping.put(nodeType.name(), serviceOffering.getId());
-    }
-
-    protected void 
processNodeTypeOfferingEntryAndAddToMappingIfValid(Map<String, String> entry, 
Map<String, Long> mapping) {
-        if (MapUtils.isEmpty(entry)) {
-            return;
-        }
-        String nodeTypeStr = entry.get(VmDetailConstants.CKS_NODE_TYPE);
-        String serviceOfferingUuid = entry.get(VmDetailConstants.OFFERING);
-        checkNodeTypeOfferingEntryCompleteness(nodeTypeStr, 
serviceOfferingUuid);
-
-        ServiceOffering serviceOffering = 
_entityMgr.findByUuid(ServiceOffering.class, serviceOfferingUuid);
-        checkNodeTypeOfferingEntryValues(nodeTypeStr, serviceOffering, 
serviceOfferingUuid);
-
-        addNodeTypeOfferingEntry(nodeTypeStr, serviceOfferingUuid, 
serviceOffering, mapping);
-    }
-
     public Map<String, Long> getServiceOfferingNodeTypeMap() {
-        Map<String, Long> mapping = new HashMap<>();
-        if (MapUtils.isNotEmpty(serviceOfferingNodeTypeMap)) {
-            for (Map<String, String> entry : 
serviceOfferingNodeTypeMap.values()) {
-                processNodeTypeOfferingEntryAndAddToMappingIfValid(entry, 
mapping);
-            }
-        }
-        return mapping;
+        return 
kubernetesClusterHelper.getServiceOfferingNodeTypeMap(serviceOfferingNodeTypeMap);
     }
 
     /////////////////////////////////////////////////////
diff --git 
a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ScaleKubernetesClusterCmd.java
 
b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ScaleKubernetesClusterCmd.java
index e5a5c902f4d..ade4684e1cd 100644
--- 
a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ScaleKubernetesClusterCmd.java
+++ 
b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ScaleKubernetesClusterCmd.java
@@ -17,9 +17,11 @@
 package org.apache.cloudstack.api.command.user.kubernetes.cluster;
 
 import java.util.List;
+import java.util.Map;
 
 import javax.inject.Inject;
 
+import com.cloud.kubernetes.cluster.KubernetesClusterHelper;
 import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.acl.SecurityChecker;
 import org.apache.cloudstack.api.ACL;
@@ -55,6 +57,8 @@ public class ScaleKubernetesClusterCmd extends BaseAsyncCmd {
 
     @Inject
     public KubernetesClusterService kubernetesClusterService;
+    @Inject
+    protected KubernetesClusterHelper kubernetesClusterHelper;
 
     /////////////////////////////////////////////////////
     //////////////// API parameters /////////////////////
@@ -69,6 +73,11 @@ public class ScaleKubernetesClusterCmd extends BaseAsyncCmd {
         description = "the ID of the service offering for the virtual machines 
in the cluster.")
     private Long serviceOfferingId;
 
+    @ACL(accessType = SecurityChecker.AccessType.UseEntry)
+    @Parameter(name = ApiConstants.NODE_TYPE_OFFERING_MAP, type = 
CommandType.MAP,
+            description = "(Optional) Node Type to Service Offering ID 
mapping. If provided, it overrides the serviceofferingid parameter")
+    protected Map<String, Map<String, String>> serviceOfferingNodeTypeMap;
+
     @Parameter(name=ApiConstants.SIZE, type = CommandType.LONG,
         description = "number of Kubernetes cluster nodes")
     private Long clusterSize;
@@ -104,6 +113,10 @@ public class ScaleKubernetesClusterCmd extends 
BaseAsyncCmd {
         return serviceOfferingId;
     }
 
+    public Map<String, Long> getServiceOfferingNodeTypeMap() {
+        return 
kubernetesClusterHelper.getServiceOfferingNodeTypeMap(this.serviceOfferingNodeTypeMap);
+    }
+
     public Long getClusterSize() {
         return clusterSize;
     }
diff --git 
a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImplTest.java
 
b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImplTest.java
index bab58d20604..ba54ff38d38 100644
--- 
a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImplTest.java
+++ 
b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImplTest.java
@@ -16,16 +16,58 @@
 // under the License.
 package com.cloud.kubernetes.cluster;
 
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.service.ServiceOfferingVO;
+import com.cloud.service.dao.ServiceOfferingDao;
+import com.cloud.vm.VmDetailConstants;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import static 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.CONTROL;
+import static 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.ETCD;
+import static 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.WORKER;
+
 @RunWith(MockitoJUnitRunner.class)
 public class KubernetesClusterHelperImplTest {
 
+    @Mock
+    private ServiceOfferingDao serviceOfferingDao;
+    @Mock
+    private ServiceOfferingVO workerServiceOffering;
+    @Mock
+    private ServiceOfferingVO controlServiceOffering;
+    @Mock
+    private ServiceOfferingVO etcdServiceOffering;
+
+    private static final String workerNodesOfferingId = 
UUID.randomUUID().toString();
+    private static final String controlNodesOfferingId = 
UUID.randomUUID().toString();
+    private static final String etcdNodesOfferingId = 
UUID.randomUUID().toString();
+    private static final Long workerOfferingId = 1L;
+    private static final Long controlOfferingId = 2L;
+    private static final Long etcdOfferingId = 3L;
+
     private final KubernetesClusterHelperImpl helper = new 
KubernetesClusterHelperImpl();
 
+    @Before
+    public void setUp() {
+        helper.serviceOfferingDao = serviceOfferingDao;
+        
Mockito.when(serviceOfferingDao.findByUuid(workerNodesOfferingId)).thenReturn(workerServiceOffering);
+        
Mockito.when(serviceOfferingDao.findByUuid(controlNodesOfferingId)).thenReturn(controlServiceOffering);
+        
Mockito.when(serviceOfferingDao.findByUuid(etcdNodesOfferingId)).thenReturn(etcdServiceOffering);
+        
Mockito.when(workerServiceOffering.getId()).thenReturn(workerOfferingId);
+        
Mockito.when(controlServiceOffering.getId()).thenReturn(controlOfferingId);
+        Mockito.when(etcdServiceOffering.getId()).thenReturn(etcdOfferingId);
+    }
+
     @Test
     public void testIsValidNodeTypeEmptyNodeType() {
         Assert.assertFalse(helper.isValidNodeType(null));
@@ -42,4 +84,62 @@ public class KubernetesClusterHelperImplTest {
         String nodeType = 
KubernetesClusterHelper.KubernetesClusterNodeType.WORKER.name().toLowerCase();
         Assert.assertTrue(helper.isValidNodeType(nodeType));
     }
+
+    private Map<String, String> 
createMapEntry(KubernetesClusterHelper.KubernetesClusterNodeType nodeType,
+                                               String nodeTypeOfferingUuid) {
+        Map<String, String> map = new HashMap<>();
+        map.put(VmDetailConstants.CKS_NODE_TYPE, 
nodeType.name().toLowerCase());
+        map.put(VmDetailConstants.OFFERING, nodeTypeOfferingUuid);
+        return map;
+    }
+
+    @Test
+    public void testNodeOfferingMap() {
+        Map<String, Map<String, String>> serviceOfferingNodeTypeMap = new 
HashMap<>();
+        Map<String, String> firstMap = createMapEntry(WORKER, 
workerNodesOfferingId);
+        Map<String, String> secondMap = createMapEntry(CONTROL, 
controlNodesOfferingId);
+        serviceOfferingNodeTypeMap.put("map1", firstMap);
+        serviceOfferingNodeTypeMap.put("map2", secondMap);
+        Map<String, Long> map = 
helper.getServiceOfferingNodeTypeMap(serviceOfferingNodeTypeMap);
+        Assert.assertNotNull(map);
+        Assert.assertEquals(2, map.size());
+        Assert.assertTrue(map.containsKey(WORKER.name()) && 
map.containsKey(CONTROL.name()));
+        Assert.assertEquals(workerOfferingId, map.get(WORKER.name()));
+        Assert.assertEquals(controlOfferingId, map.get(CONTROL.name()));
+    }
+
+    @Test
+    public void testNodeOfferingMapNullMap() {
+        Map<String, Long> map = helper.getServiceOfferingNodeTypeMap(null);
+        Assert.assertTrue(map.isEmpty());
+    }
+
+    @Test
+    public void testNodeOfferingMapEtcdNodes() {
+        Map<String, Map<String, String>> serviceOfferingNodeTypeMap = new 
HashMap<>();
+        Map<String, String> firstMap = createMapEntry(ETCD, 
etcdNodesOfferingId);
+        serviceOfferingNodeTypeMap.put("map1", firstMap);
+        Map<String, Long> map = 
helper.getServiceOfferingNodeTypeMap(serviceOfferingNodeTypeMap);
+        Assert.assertNotNull(map);
+        Assert.assertEquals(1, map.size());
+        Assert.assertTrue(map.containsKey(ETCD.name()));
+        Assert.assertEquals(etcdOfferingId, map.get(ETCD.name()));
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testCheckNodeTypeOfferingEntryCompletenessInvalidParameters() {
+        helper.checkNodeTypeOfferingEntryCompleteness(WORKER.name(), null);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testCheckNodeTypeOfferingEntryValuesInvalidNodeType() {
+        String invalidNodeType = "invalidNodeTypeName";
+        helper.checkNodeTypeOfferingEntryValues(invalidNodeType, 
workerServiceOffering, workerNodesOfferingId);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testCheckNodeTypeOfferingEntryValuesEmptyOffering() {
+        String nodeType = WORKER.name();
+        helper.checkNodeTypeOfferingEntryValues(nodeType, null, 
workerNodesOfferingId);
+    }
 }
diff --git 
a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java
 
b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java
index 8ba91269244..49bddb969e4 100644
--- 
a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java
+++ 
b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java
@@ -33,6 +33,7 @@ import com.cloud.network.dao.FirewallRulesDao;
 import com.cloud.network.rules.FirewallRule;
 import com.cloud.network.rules.FirewallRuleVO;
 import com.cloud.network.vpc.NetworkACL;
+import com.cloud.offering.ServiceOffering;
 import com.cloud.service.ServiceOfferingVO;
 import com.cloud.service.dao.ServiceOfferingDao;
 import com.cloud.storage.VMTemplateVO;
@@ -48,6 +49,7 @@ import 
org.apache.cloudstack.api.command.user.kubernetes.cluster.AddVirtualMachi
 import 
org.apache.cloudstack.api.command.user.kubernetes.cluster.RemoveVirtualMachinesFromKubernetesClusterCmd;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.commons.collections.MapUtils;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -68,6 +70,7 @@ import java.util.List;
 import java.util.Map;
 
 import static 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.CONTROL;
+import static 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.ETCD;
 import static 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.WORKER;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -363,4 +366,52 @@ public class KubernetesClusterManagerImplTest {
         Assert.assertEquals(expectedCpu, pair.first());
         Assert.assertEquals(expectedMemory, pair.second());
     }
+
+    @Test
+    public void testIsAnyNodeOfferingEmptyNullMap() {
+        
Assert.assertFalse(kubernetesClusterManager.isAnyNodeOfferingEmpty(null));
+    }
+
+    @Test
+    public void testIsAnyNodeOfferingEmptyNullValue() {
+        Map<String, Long> map = new HashMap<>();
+        map.put(WORKER.name(), 1L);
+        map.put(CONTROL.name(), null);
+        map.put(ETCD.name(), 2L);
+        
Assert.assertTrue(kubernetesClusterManager.isAnyNodeOfferingEmpty(map));
+    }
+
+    @Test
+    public void testIsAnyNodeOfferingEmpty() {
+        Map<String, Long> map = new HashMap<>();
+        map.put(WORKER.name(), 1L);
+        map.put(CONTROL.name(), 2L);
+        
Assert.assertFalse(kubernetesClusterManager.isAnyNodeOfferingEmpty(map));
+    }
+
+    @Test
+    public void testCreateNodeTypeToServiceOfferingMapNullMap() {
+        Map<String, ServiceOffering> mapping = 
kubernetesClusterManager.createNodeTypeToServiceOfferingMap(null, null);
+        Assert.assertTrue(MapUtils.isEmpty(mapping));
+    }
+
+    @Test
+    public void testCreateNodeTypeToServiceOfferingMap() {
+        Map<String, Long> idsMap = new HashMap<>();
+        long workerOfferingId = 1L;
+        long controlOfferingId = 2L;
+        idsMap.put(WORKER.name(), workerOfferingId);
+        idsMap.put(CONTROL.name(), controlOfferingId);
+
+        ServiceOfferingVO workerOffering = 
Mockito.mock(ServiceOfferingVO.class);
+        
Mockito.when(serviceOfferingDao.findById(workerOfferingId)).thenReturn(workerOffering);
+        ServiceOfferingVO controlOffering = 
Mockito.mock(ServiceOfferingVO.class);
+        
Mockito.when(serviceOfferingDao.findById(controlOfferingId)).thenReturn(controlOffering);
+
+        Map<String, ServiceOffering> mapping = 
kubernetesClusterManager.createNodeTypeToServiceOfferingMap(idsMap, null);
+        Assert.assertEquals(2, mapping.size());
+        Assert.assertTrue(mapping.containsKey(WORKER.name()) && 
mapping.containsKey(CONTROL.name()));
+        Assert.assertEquals(workerOffering, mapping.get(WORKER.name()));
+        Assert.assertEquals(controlOffering, mapping.get(CONTROL.name()));
+    }
 }
diff --git 
a/plugins/integrations/kubernetes-service/src/test/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmdTest.java
 
b/plugins/integrations/kubernetes-service/src/test/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmdTest.java
deleted file mode 100644
index b5c6cf257a4..00000000000
--- 
a/plugins/integrations/kubernetes-service/src/test/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmdTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-// 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.api.command.user.kubernetes.cluster;
-
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.kubernetes.cluster.KubernetesClusterHelper;
-import com.cloud.kubernetes.cluster.KubernetesClusterHelperImpl;
-import com.cloud.offering.ServiceOffering;
-import com.cloud.utils.db.EntityManager;
-import com.cloud.vm.VmDetailConstants;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-import static 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.CONTROL;
-import static 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.ETCD;
-import static 
com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.WORKER;
-
-@RunWith(MockitoJUnitRunner.class)
-public class CreateKubernetesClusterCmdTest {
-
-    @Mock
-    EntityManager entityManager;
-    KubernetesClusterHelper helper = new KubernetesClusterHelperImpl();
-
-    @Mock
-    ServiceOffering workerServiceOffering;
-    @Mock
-    ServiceOffering controlServiceOffering;
-    @Mock
-    ServiceOffering etcdServiceOffering;
-
-    private final CreateKubernetesClusterCmd cmd = new 
CreateKubernetesClusterCmd();
-
-    private static final String workerNodesOfferingId = 
UUID.randomUUID().toString();
-    private static final String controlNodesOfferingId = 
UUID.randomUUID().toString();
-    private static final String etcdNodesOfferingId = 
UUID.randomUUID().toString();
-    private static final Long workerOfferingId = 1L;
-    private static final Long controlOfferingId = 2L;
-    private static final Long etcdOfferingId = 3L;
-
-    @Before
-    public void setUp() {
-        cmd._entityMgr = entityManager;
-        cmd.kubernetesClusterHelper = helper;
-        Mockito.when(entityManager.findByUuid(ServiceOffering.class, 
workerNodesOfferingId)).thenReturn(workerServiceOffering);
-        Mockito.when(entityManager.findByUuid(ServiceOffering.class, 
controlNodesOfferingId)).thenReturn(controlServiceOffering);
-        Mockito.when(entityManager.findByUuid(ServiceOffering.class, 
etcdNodesOfferingId)).thenReturn(etcdServiceOffering);
-        
Mockito.when(workerServiceOffering.getId()).thenReturn(workerOfferingId);
-        
Mockito.when(controlServiceOffering.getId()).thenReturn(controlOfferingId);
-        Mockito.when(etcdServiceOffering.getId()).thenReturn(etcdOfferingId);
-    }
-
-    private Map<String, String> 
createMapEntry(KubernetesClusterHelper.KubernetesClusterNodeType nodeType,
-                                String nodeTypeOfferingUuid) {
-        Map<String, String> map = new HashMap<>();
-        map.put(VmDetailConstants.CKS_NODE_TYPE, 
nodeType.name().toLowerCase());
-        map.put(VmDetailConstants.OFFERING, nodeTypeOfferingUuid);
-        return map;
-    }
-
-    @Test
-    public void testNodeOfferingMap() {
-        cmd.serviceOfferingNodeTypeMap = new HashMap<>();
-        Map<String, String> firstMap = createMapEntry(WORKER, 
workerNodesOfferingId);
-        Map<String, String> secondMap = createMapEntry(CONTROL, 
controlNodesOfferingId);
-        cmd.serviceOfferingNodeTypeMap.put("map1", firstMap);
-        cmd.serviceOfferingNodeTypeMap.put("map2", secondMap);
-        Map<String, Long> map = cmd.getServiceOfferingNodeTypeMap();
-        Assert.assertNotNull(map);
-        Assert.assertEquals(2, map.size());
-        Assert.assertTrue(map.containsKey(WORKER.name()) && 
map.containsKey(CONTROL.name()));
-        Assert.assertEquals(workerOfferingId, map.get(WORKER.name()));
-        Assert.assertEquals(controlOfferingId, map.get(CONTROL.name()));
-    }
-
-    @Test
-    public void testNodeOfferingMapNullMap() {
-        cmd.serviceOfferingNodeTypeMap = null;
-        cmd.serviceOfferingId = controlOfferingId;
-        Map<String, Long> map = cmd.getServiceOfferingNodeTypeMap();
-        Assert.assertTrue(map.isEmpty());
-    }
-
-    @Test
-    public void testNodeOfferingMapEtcdNodes() {
-        cmd.serviceOfferingNodeTypeMap = new HashMap<>();
-        Map<String, String> firstMap = createMapEntry(ETCD, 
etcdNodesOfferingId);
-        cmd.serviceOfferingNodeTypeMap.put("map1", firstMap);
-        cmd.etcdNodes = 2L;
-        Map<String, Long> map = cmd.getServiceOfferingNodeTypeMap();
-        Assert.assertNotNull(map);
-        Assert.assertEquals(1, map.size());
-        Assert.assertTrue(map.containsKey(ETCD.name()));
-        Assert.assertEquals(etcdOfferingId, map.get(ETCD.name()));
-    }
-
-    @Test(expected = InvalidParameterValueException.class)
-    public void testCheckNodeTypeOfferingEntryCompletenessInvalidParameters() {
-        cmd.checkNodeTypeOfferingEntryCompleteness(WORKER.name(), null);
-    }
-
-    @Test(expected = InvalidParameterValueException.class)
-    public void testCheckNodeTypeOfferingEntryValuesInvalidNodeType() {
-        String invalidNodeType = "invalidNodeTypeName";
-        cmd.checkNodeTypeOfferingEntryValues(invalidNodeType, 
workerServiceOffering, workerNodesOfferingId);
-    }
-
-    @Test(expected = InvalidParameterValueException.class)
-    public void testCheckNodeTypeOfferingEntryValuesEmptyOffering() {
-        String nodeType = WORKER.name();
-        cmd.checkNodeTypeOfferingEntryValues(nodeType, null, 
workerNodesOfferingId);
-    }
-}
diff --git a/ui/src/views/compute/ScaleKubernetesCluster.vue 
b/ui/src/views/compute/ScaleKubernetesCluster.vue
index 8d73ac861d7..a0fdf4a470c 100644
--- a/ui/src/views/compute/ScaleKubernetesCluster.vue
+++ b/ui/src/views/compute/ScaleKubernetesCluster.vue
@@ -28,7 +28,7 @@
         :rules="rules"
         @finish="handleSubmit"
         layout="vertical">
-        <a-form-item name="serviceofferingid" ref="serviceofferingid">
+        <a-form-item name="serviceofferingid" ref="serviceofferingid" 
v-if="!this.resource.workerofferingid && !this.resource.controlofferingid && 
!this.resource.etcdofferingid">
           <template #label>
             <tooltip-label :title="$t('label.serviceofferingid')" 
:tooltip="apiParams.serviceofferingid.description"/>
           </template>
@@ -47,6 +47,63 @@
             </a-select-option>
           </a-select>
         </a-form-item>
+        <a-form-item name="workerofferingid" ref="workerofferingid">
+          <template #label>
+            <tooltip-label :title="$t('label.service.offering.workernodes')" 
:tooltip="apiParams.serviceofferingid.description"/>
+          </template>
+          <a-select
+            id="offering-selection-worker"
+            v-model:value="form.workerofferingid"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return 
option.label.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            :loading="serviceOfferingLoading"
+            :placeholder="apiParams.serviceofferingid.description">
+            <a-select-option v-for="(opt, optIndex) in workerOfferings" 
:key="optIndex" :label="opt.name || opt.description">
+              {{ opt.name || opt.description }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item name="controlofferingid" ref="controlofferingid">
+          <template #label>
+            <tooltip-label :title="$t('label.service.offering.controlnodes')" 
:tooltip="apiParams.serviceofferingid.description"/>
+          </template>
+          <a-select
+            id="offering-selection-control"
+            v-model:value="form.controlofferingid"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return 
option.label.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            :loading="serviceOfferingLoading"
+            :placeholder="apiParams.serviceofferingid.description">
+            <a-select-option v-for="(opt, optIndex) in controlOfferings" 
:key="optIndex" :label="opt.name || opt.description">
+              {{ opt.name || opt.description }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item name="etcdofferingid" ref="etcdofferingid" 
v-if="this.resource.etcdnodes && this.resource.etcdnodes > 0 && 
this.resource.etcdofferingid">
+          <template #label>
+            <tooltip-label :title="$t('label.service.offering.etcdnodes')" 
:tooltip="apiParams.serviceofferingid.description"/>
+          </template>
+          <a-select
+            id="offering-selection-etcd"
+            v-model:value="form.etcdofferingid"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return 
option.label.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            :loading="serviceOfferingLoading"
+            :placeholder="apiParams.serviceofferingid.description">
+            <a-select-option v-for="(opt, optIndex) in etcdOfferings" 
:key="optIndex" :label="opt.name || opt.description">
+              {{ opt.name || opt.description }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
         <a-form-item name="autoscalingenabled" ref="autoscalingenabled" 
v-if="apiParams.autoscalingenabled">
           <template #label>
             <tooltip-label :title="$t('label.cks.cluster.autoscalingenabled')" 
:tooltip="apiParams.autoscalingenabled.description"/>
@@ -118,7 +175,10 @@ export default {
       originalSize: 1,
       autoscalingenabled: null,
       minsize: null,
-      maxsize: null
+      maxsize: null,
+      controlOfferings: [],
+      workerOfferings: [],
+      etcdOfferings: []
     }
   },
   beforeCreate () {
@@ -153,7 +213,12 @@ export default {
     },
     fetchData () {
       if (this.resource.state === 'Running') {
-        this.fetchKubernetesClusterServiceOfferingData()
+        
this.fetchKubernetesClusterServiceOfferingData(this.resource.serviceofferingid, 
'default')
+        
this.fetchKubernetesClusterServiceOfferingData(this.resource.workerofferingid, 
'worker')
+        
this.fetchKubernetesClusterServiceOfferingData(this.resource.controlofferingid, 
'control')
+        if (this.resource.etcdofferingid && this.resource.etcdnodes && 
this.resource.etcdnodes > 0) {
+          
this.fetchKubernetesClusterServiceOfferingData(this.resource.controlofferingid, 
'etcd')
+        }
         return
       }
       this.fetchKubernetesVersionData()
@@ -167,19 +232,21 @@ export default {
     isObjectEmpty (obj) {
       return !(obj !== null && obj !== undefined && Object.keys(obj).length > 
0 && obj.constructor === Object)
     },
-    fetchKubernetesClusterServiceOfferingData () {
+    fetchKubernetesClusterServiceOfferingData (offeringId, type) {
       const params = {}
       if (!this.isObjectEmpty(this.resource)) {
-        params.id = this.resource.serviceofferingid
+        params.id = offeringId
       }
+      var minCpu = 0
+      var minMemory = 0
       api('listServiceOfferings', params).then(json => {
         var items = json?.listserviceofferingsresponse?.serviceoffering || []
         if (this.arrayHasItems(items) && !this.isObjectEmpty(items[0])) {
-          this.minCpu = items[0].cpunumber
-          this.minMemory = items[0].memory
+          minCpu = items[0].cpunumber
+          minMemory = items[0].memory
         }
       }).finally(() => {
-        this.fetchServiceOfferingData()
+        this.fetchServiceOfferingData(minCpu, minMemory, type)
       })
     },
     fetchKubernetesVersionData () {
@@ -187,21 +254,28 @@ export default {
       if (!this.isObjectEmpty(this.resource)) {
         params.id = this.resource.kubernetesversionid
       }
+      var minCpu = 0
+      var minMemory = 0
       api('listKubernetesSupportedVersions', params).then(json => {
         const versionObjs = 
json?.listkubernetessupportedversionsresponse?.kubernetessupportedversion || []
         if (this.arrayHasItems(versionObjs) && 
!this.isObjectEmpty(versionObjs[0])) {
-          this.minCpu = versionObjs[0].mincpunumber
-          this.minMemory = versionObjs[0].minmemory
+          minCpu = versionObjs[0].mincpunumber
+          minMemory = versionObjs[0].minmemory
         }
       }).finally(() => {
-        this.fetchServiceOfferingData()
+        this.fetchServiceOfferingData(minCpu, minMemory, 'default')
+        this.fetchServiceOfferingData(minCpu, minMemory, 'worker')
+        this.fetchServiceOfferingData(minCpu, minMemory, 'control')
+        if (this.resource.etcdofferingid && this.resource.etcdnodes && 
this.resource.etcdnodes > 0) {
+          this.fetchServiceOfferingData(minCpu, minMemory, 'etcd')
+        }
       })
     },
-    fetchServiceOfferingData () {
-      this.serviceOfferings = []
+    fetchServiceOfferingData (minCpu, minMemory, type) {
+      var offerings = []
       const params = {
-        cpunumber: this.minCpu,
-        memory: this.minMemory
+        cpunumber: minCpu,
+        memory: minMemory
       }
       this.serviceOfferingLoading = true
       api('listServiceOfferings', params).then(json => {
@@ -209,17 +283,35 @@ export default {
         if (this.arrayHasItems(items)) {
           for (var i = 0; i < items.length; i++) {
             if (items[i].iscustomized === false) {
-              this.serviceOfferings.push(items[i])
+              offerings.push(items[i])
             }
           }
         }
       }).finally(() => {
         this.serviceOfferingLoading = false
-        if (this.arrayHasItems(this.serviceOfferings)) {
-          for (var i = 0; i < this.serviceOfferings.length; i++) {
-            if (this.serviceOfferings[i].id === 
this.resource.serviceofferingid) {
+        if (this.arrayHasItems(offerings)) {
+          if (type === 'default') {
+            this.serviceOfferings = offerings
+          } else if (type === 'worker') {
+            this.workerOfferings = offerings
+          } else if (type === 'control') {
+            this.controlOfferings = offerings
+          } else if (type === 'etcd') {
+            this.etcdOfferings = offerings
+          }
+          for (var i = 0; i < offerings.length; i++) {
+            if (type === 'default' && offerings[i].id === 
this.resource.serviceofferingid) {
               this.form.serviceofferingid = i
               break
+            } else if (type === 'worker' && offerings[i].id === 
this.resource.workerofferingid) {
+              this.form.workerofferingid = i
+              break
+            } else if (type === 'control' && offerings[i].id === 
this.resource.controlofferingid) {
+              this.form.controlofferingid = i
+              break
+            } else if (type === 'etcd' && offerings[i].id === 
this.resource.etcdofferingid) {
+              this.form.etcdofferingid = i
+              break
             }
           }
         }

Reply via email to