You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by pe...@apache.org on 2024/02/08 13:21:34 UTC

(cloudstack) 05/12: Persist values for managed clusters

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 967b5029bea81e34d8f15a22224dee16090f9e98
Author: nvazquez <ni...@gmail.com>
AuthorDate: Sun Feb 4 01:32:07 2024 -0300

    Persist values for managed clusters
---
 .../cluster/KubernetesClusterManagerImpl.java      | 129 +++++++++++++++++----
 .../kubernetes/cluster/KubernetesClusterVO.java    |  44 +++++++
 .../cluster/CreateKubernetesClusterCmd.java        |  12 +-
 .../cluster/KubernetesClusterManagerImplTest.java  |  71 ++++++++++++
 .../cluster/CreateKubernetesClusterCmdTest.java    |  18 +--
 5 files changed, 234 insertions(+), 40 deletions(-)

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 fcb208c6718..c7de5dbcd2e 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,9 @@
 // under the License.
 package com.cloud.kubernetes.cluster;
 
+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;
 import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
 import static com.cloud.vm.UserVmManager.AllowUserExpungeRecoverVm;
 
@@ -72,6 +75,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;
@@ -192,6 +196,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
 
     protected StateMachine2<KubernetesCluster.State, KubernetesCluster.Event, KubernetesCluster> _stateMachine = KubernetesCluster.State.getStateMachine();
 
+    protected final static List<String> CLUSTER_NODES_TYPES_LIST = Arrays.asList(WORKER.name(), CONTROL.name(), ETCD.name());
+
     ScheduledExecutorService _gcExecutor;
     ScheduledExecutorService _stateScanner;
 
@@ -446,7 +452,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
         validateIsolatedNetwork(network, clusterTotalNodeCount);
     }
 
-    private boolean validateServiceOffering(final ServiceOffering serviceOffering, final KubernetesSupportedVersion version) {
+    protected void validateServiceOffering(final ServiceOffering serviceOffering, final KubernetesSupportedVersion version) throws InvalidParameterValueException {
         if (serviceOffering.isDynamic()) {
             throw new InvalidParameterValueException(String.format("Custom service offerings are not supported for creating clusters, service offering ID: %s", serviceOffering.getUuid()));
         }
@@ -459,7 +465,6 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
         if (serviceOffering.getRamSize() < version.getMinimumRamSize()) {
             throw new InvalidParameterValueException(String.format("Kubernetes cluster cannot be created with service offering ID: %s, associated Kubernetes version ID: %s needs minimum %d MB RAM", serviceOffering.getUuid(), version.getUuid(), version.getMinimumRamSize()));
         }
-        return true;
     }
 
     private void validateDockerRegistryParams(final String dockerRegistryUserName,
@@ -736,7 +741,6 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
         final String name = cmd.getName();
         final Long zoneId = cmd.getZoneId();
         final Long kubernetesVersionId = cmd.getKubernetesVersionId();
-        final Long serviceOfferingId = cmd.getServiceOfferingId();
         final Account owner = accountService.getActiveAccountById(cmd.getEntityOwnerId());
         final Long networkId = cmd.getNetworkId();
         final String sshKeyPair = cmd.getSSHKeyPairName();
@@ -747,6 +751,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
         final String dockerRegistryUrl = cmd.getDockerRegistryUrl();
         final Long nodeRootDiskSize = cmd.getNodeRootDiskSize();
         final String externalLoadBalancerIpAddress = cmd.getExternalLoadBalancerIpAddress();
+        final Map<String, Long> serviceOfferingNodeTypeMap = cmd.getServiceOfferingNodeTypeMap();
 
         if (name == null || name.isEmpty()) {
             throw new InvalidParameterValueException("Invalid name for the Kubernetes cluster name: " + name);
@@ -804,10 +809,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
             throw new InvalidParameterValueException(String.format("ISO associated with version ID: %s is not in Ready state for datacenter ID: %s",  clusterKubernetesVersion.getUuid(), zone.getUuid()));
         }
 
-        ServiceOffering serviceOffering = serviceOfferingDao.findById(serviceOfferingId);
-        if (serviceOffering == null) {
-            throw new InvalidParameterValueException("No service offering with ID: " + serviceOfferingId);
-        }
+        validateServiceOfferingsForNodeTypes(serviceOfferingNodeTypeMap, cmd.getEtcdNodes(), clusterKubernetesVersion);
 
         validateSshKeyPairForKubernetesCreateParameters(sshKeyPair, owner);
 
@@ -815,10 +817,6 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
             throw new InvalidParameterValueException(String.format("Invalid value for %s", ApiConstants.NODE_ROOT_DISK_SIZE));
         }
 
-        if (!validateServiceOffering(serviceOffering, clusterKubernetesVersion)) {
-            throw new InvalidParameterValueException("Given service offering ID: %s is not suitable for Kubernetes cluster");
-        }
-
         validateDockerRegistryParams(dockerRegistryUserName, dockerRegistryPassword, dockerRegistryUrl);
 
         Network network = validateAndGetNetworkForKubernetesCreateParameters(networkId);
@@ -840,6 +838,40 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
         }
     }
 
+    protected void validateServiceOfferingsForNodeTypes(Map<String, Long> map, Long etcdNodes, KubernetesSupportedVersion clusterKubernetesVersion) {
+        if (MapUtils.isEmpty(map)) {
+            throw new InvalidParameterValueException("Please specify at least one service offering for the cluster");
+        }
+        if (!map.containsKey(WORKER.name()) || !map.containsKey(CONTROL.name())) {
+            throw new InvalidParameterValueException("Please specify a service offering for worker and control nodes");
+        }
+        if (etcdNodes != null && etcdNodes > 0 && !map.containsKey(ETCD.name())) {
+            throw new InvalidParameterValueException("Please specify a service offering for the etcd nodes");
+        }
+        for (String key : CLUSTER_NODES_TYPES_LIST) {
+            validateServiceOfferingForNode(map, key, etcdNodes, clusterKubernetesVersion);
+        }
+    }
+
+    protected void validateServiceOfferingForNode(Map<String, Long> map, String key, Long etcdNodes, KubernetesSupportedVersion clusterKubernetesVersion) {
+        if (ETCD.name().equalsIgnoreCase(key) && (etcdNodes == null || etcdNodes == 0)) {
+            return;
+        }
+        Long serviceOfferingId = map.getOrDefault(key, null);
+        ServiceOffering serviceOffering = serviceOfferingId != null ? serviceOfferingDao.findById(serviceOfferingId) : null;
+        if (serviceOffering == null) {
+            throw new InvalidParameterValueException("No service offering found with ID: " + serviceOfferingId);
+        }
+        try {
+            validateServiceOffering(serviceOffering, clusterKubernetesVersion);
+        } catch (InvalidParameterValueException e) {
+            String msg = String.format("Given service offering ID: %s for %s nodes is not suitable for the Kubernetes cluster version %s - %s",
+                    serviceOffering, key, clusterKubernetesVersion, e.getMessage());
+            LOGGER.error(msg);
+            throw new InvalidParameterValueException(msg);
+        }
+    }
+
     private Network getKubernetesClusterNetworkIfMissing(final String clusterName, final DataCenter zone,  final Account owner, final int controlNodesCount,
                          final int nodesCount, final String externalLoadBalancerIpAddress, final Long networkId) throws CloudRuntimeException {
         Network network = null;
@@ -1154,6 +1186,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
         final long controlNodeCount = cmd.getControlNodes();
         final long clusterSize = Objects.requireNonNullElse(cmd.getClusterSize(), 0L);
         final ServiceOffering serviceOffering = serviceOfferingDao.findById(cmd.getServiceOfferingId());
+        Map<String, Long> nodeTypeOfferingMap = cmd.getServiceOfferingNodeTypeMap();
         final Account owner = accountService.getActiveAccountById(cmd.getEntityOwnerId());
         final KubernetesSupportedVersion clusterKubernetesVersion = kubernetesSupportedVersionDao.findById(cmd.getKubernetesVersionId());
 
@@ -1203,20 +1236,14 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
         final DataCenter zone = dataCenterDao.findById(cmd.getZoneId());
         final long controlNodeCount = cmd.getControlNodes();
         final long clusterSize = cmd.getClusterSize();
-        final long totalNodeCount = controlNodeCount + clusterSize;
-        final ServiceOffering serviceOffering = serviceOfferingDao.findById(cmd.getServiceOfferingId());
+        final long etcdNodes = cmd.getEtcdNodes();
+        final Map<String, Long> nodeTypeCount = Map.of(WORKER.name(), clusterSize,
+                CONTROL.name(), controlNodeCount, ETCD.name(), etcdNodes);
         final Account owner = accountService.getActiveAccountById(cmd.getEntityOwnerId());
         final KubernetesSupportedVersion clusterKubernetesVersion = kubernetesSupportedVersionDao.findById(cmd.getKubernetesVersionId());
 
-        DeployDestination deployDestination = null;
-        try {
-            deployDestination = plan(totalNodeCount, zone, serviceOffering);
-        } catch (InsufficientCapacityException e) {
-            logAndThrow(Level.ERROR, String.format("Creating Kubernetes cluster failed due to insufficient capacity for %d nodes cluster in zone : %s with service offering : %s", totalNodeCount, zone.getName(), serviceOffering.getName()));
-        }
-        if (deployDestination == null || deployDestination.getCluster() == null) {
-            logAndThrow(Level.ERROR, String.format("Creating Kubernetes cluster failed due to error while finding suitable deployment plan for cluster in zone : %s", zone.getName()));
-        }
+        Map<String, Long> serviceOfferingNodeTypeMap = cmd.getServiceOfferingNodeTypeMap();
+        Hypervisor.HypervisorType hypervisorType = validateDeploymentAndSelectDestinationHypervisor(serviceOfferingNodeTypeMap, nodeTypeCount, zone);
 
         SecurityGroup securityGroup = null;
         if (zone.isSecurityGroupEnabled()) {
@@ -1224,9 +1251,12 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
         }
 
         final Network defaultNetwork = getKubernetesClusterNetworkIfMissing(cmd.getName(), zone, owner, (int)controlNodeCount, (int)clusterSize, cmd.getExternalLoadBalancerIpAddress(), cmd.getNetworkId());
-        final VMTemplateVO finalTemplate = getKubernetesServiceTemplate(zone, deployDestination.getCluster().getHypervisorType());
-        final long cores = serviceOffering.getCpu() * (controlNodeCount + clusterSize);
-        final long memory = serviceOffering.getRamSize() * (controlNodeCount + clusterSize);
+        final VMTemplateVO finalTemplate = getKubernetesServiceTemplate(zone, hypervisorType);
+        // Set the service_offering_id as the ID of the worker nodes offering for backwards compatibility
+        final ServiceOffering serviceOffering = serviceOfferingDao.findById(serviceOfferingNodeTypeMap.get(WORKER.name()));
+        Pair<Long, Long> capacityPair = calculateClusterCapacity(serviceOfferingNodeTypeMap, nodeTypeCount);
+        final long cores = capacityPair.first();
+        final long memory = capacityPair.second();
 
         final SecurityGroup finalSecurityGroup = securityGroup;
         final KubernetesClusterVO cluster = Transaction.execute(new TransactionCallback<KubernetesClusterVO>() {
@@ -1236,6 +1266,12 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
                         serviceOffering.getId(), finalTemplate.getId(), defaultNetwork.getId(), owner.getDomainId(),
                         owner.getAccountId(), controlNodeCount, clusterSize, KubernetesCluster.State.Created, cmd.getSSHKeyPairName(), cores, memory,
                         cmd.getNodeRootDiskSize(), "", KubernetesCluster.ClusterType.CloudManaged);
+                newCluster.setWorkerServiceOfferingId(serviceOfferingNodeTypeMap.getOrDefault(WORKER.name(), null));
+                newCluster.setControlServiceOfferingId(serviceOfferingNodeTypeMap.getOrDefault(CONTROL.name(), null));
+                if (etcdNodes > 0) {
+                    newCluster.setEtcdNodeCount(etcdNodes);
+                    newCluster.setEtcdServiceOfferingId(serviceOfferingNodeTypeMap.getOrDefault(ETCD.name(), null));
+                }
                 if (zone.isSecurityGroupEnabled()) {
                     newCluster.setSecurityGroupId(finalSecurityGroup.getId());
                 }
@@ -1252,6 +1288,49 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
         return cluster;
     }
 
+    protected Pair<Long, Long> calculateClusterCapacity(Map<String, Long> map, Map<String, Long> nodeTypeCount) {
+        long cores = 0L;
+        long memory = 0L;
+        for (String key : CLUSTER_NODES_TYPES_LIST) {
+            if (!map.containsKey(key)) {
+                continue;
+            }
+            ServiceOffering serviceOffering = serviceOfferingDao.findById(map.get(key));
+            Long nodes = nodeTypeCount.get(key);
+            cores = cores + (serviceOffering.getCpu() * nodes);
+            memory = memory + (serviceOffering.getRamSize() * nodes);
+        }
+        return new Pair<>(cores, memory);
+    }
+
+    protected Hypervisor.HypervisorType validateDeploymentAndSelectDestinationHypervisor(Map<String, Long> serviceOfferingNodeTypeMap,
+                                                                                         Map<String, Long> nodeTypeCount, DataCenter zone) {
+        Hypervisor.HypervisorType hypervisorType = null;
+        List<ServiceOffering> serviceOfferingList = new ArrayList<>();
+        for (String nodeType : CLUSTER_NODES_TYPES_LIST) {
+            ServiceOffering serviceOffering = null;
+            Long nodes = nodeTypeCount.get(nodeType);
+            try {
+                if (nodeType.equalsIgnoreCase(ETCD.name()) &&
+                        (!serviceOfferingNodeTypeMap.containsKey(ETCD.name()) || nodes == 0)) {
+                    continue;
+                }
+                serviceOffering = serviceOfferingDao.findById(serviceOfferingNodeTypeMap.get(nodeType));
+                DeployDestination deployDestination = plan(nodes, zone, serviceOffering);
+                if (deployDestination == null || deployDestination.getCluster() == null) {
+                    logAndThrow(Level.ERROR, String.format("Creating Kubernetes cluster failed due to error while finding suitable deployment plan for cluster in zone : %s", zone.getName()));
+                }
+                if (hypervisorType == null) {
+                    hypervisorType = deployDestination.getCluster().getHypervisorType();
+                }
+                serviceOfferingList.add(serviceOffering);
+            } catch (InsufficientCapacityException e) {
+                logAndThrow(Level.ERROR, String.format("Creating Kubernetes cluster failed due to insufficient capacity for %d nodes cluster in zone : %s with service offering : %s", nodes, zone.getName(), serviceOffering.getName()));
+            }
+        }
+        return hypervisorType;
+    }
+
     private SecurityGroup getOrCreateSecurityGroupForAccount(Account owner) {
         String securityGroupName = String.format("%s-%s", KubernetesClusterActionWorker.CKS_CLUSTER_SECURITY_GROUP_NAME, owner.getUuid());
         String securityGroupDesc = String.format("%s and account %s", KubernetesClusterActionWorker.CKS_SECURITY_GROUP_DESCRIPTION, owner.getName());
diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterVO.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterVO.java
index 270916aab7e..deeca36e389 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterVO.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterVO.java
@@ -117,6 +117,18 @@ public class KubernetesClusterVO implements KubernetesCluster {
     @Column(name = "cluster_type")
     private ClusterType clusterType;
 
+    @Column(name = "control_service_offering_id")
+    private Long controlServiceOfferingId;
+
+    @Column(name = "worker_service_offering_id")
+    private Long workerServiceOfferingId;
+
+    @Column(name = "etcd_service_offering_id")
+    private Long etcdServiceOfferingId;
+
+    @Column(name = "etcd_node_count")
+    private Long etcdNodeCount;
+
     @Override
     public long getId() {
         return id;
@@ -406,4 +418,36 @@ public class KubernetesClusterVO implements KubernetesCluster {
     public Class<?> getEntityType() {
         return KubernetesCluster.class;
     }
+
+    public Long getControlServiceOfferingId() {
+        return controlServiceOfferingId;
+    }
+
+    public void setControlServiceOfferingId(Long controlServiceOfferingId) {
+        this.controlServiceOfferingId = controlServiceOfferingId;
+    }
+
+    public Long getWorkerServiceOfferingId() {
+        return workerServiceOfferingId;
+    }
+
+    public void setWorkerServiceOfferingId(Long workerServiceOfferingId) {
+        this.workerServiceOfferingId = workerServiceOfferingId;
+    }
+
+    public Long getEtcdServiceOfferingId() {
+        return etcdServiceOfferingId;
+    }
+
+    public void setEtcdServiceOfferingId(Long etcdServiceOfferingId) {
+        this.etcdServiceOfferingId = etcdServiceOfferingId;
+    }
+
+    public Long getEtcdNodeCount() {
+        return etcdNodeCount;
+    }
+
+    public void setEtcdNodeCount(Long etcdNodeCount) {
+        this.etcdNodeCount = etcdNodeCount;
+    }
 }
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 99943408fc6..b950f071bce 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
@@ -101,7 +101,7 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd {
     @ACL(accessType = 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>> nodeTypeOfferingMap;
+    protected Map<String, Map<String, String>> serviceOfferingNodeTypeMap;
 
     @ACL(accessType = AccessType.UseEntry)
     @Parameter(name = ApiConstants.ETCD_NODES, type = CommandType.LONG,
@@ -228,8 +228,8 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd {
         return controlNodes;
     }
 
-    public Long getEtcdNodes() {
-        return etcdNodes == null ? 0L : etcdNodes;
+    public long getEtcdNodes() {
+        return etcdNodes == null ? 0 : etcdNodes;
     }
 
     public String getExternalLoadBalancerIpAddress() {
@@ -313,10 +313,10 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd {
         addNodeTypeOfferingEntry(nodeTypeStr, serviceOfferingUuid, serviceOffering, mapping);
     }
 
-    public Map<String, Long> getNodeTypeOfferingMap() {
+    public Map<String, Long> getServiceOfferingNodeTypeMap() {
         Map<String, Long> mapping = new HashMap<>();
-        if (MapUtils.isNotEmpty(nodeTypeOfferingMap)) {
-            for (Map<String, String> entry : nodeTypeOfferingMap.values()) {
+        if (MapUtils.isNotEmpty(serviceOfferingNodeTypeMap)) {
+            for (Map<String, String> entry : serviceOfferingNodeTypeMap.values()) {
                 processNodeTypeOfferingEntryAndAddToMappingIfValid(entry, mapping);
             }
             addMissingNodeTypeDefaultOffering(mapping, serviceOfferingId, etcdNodes);
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 a6d46ffc9aa..e5eafc03512 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
@@ -27,16 +27,20 @@ import com.cloud.exception.PermissionDeniedException;
 import com.cloud.kubernetes.cluster.actionworkers.KubernetesClusterActionWorker;
 import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao;
 import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao;
+import com.cloud.kubernetes.version.KubernetesSupportedVersion;
 import com.cloud.network.Network;
 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.service.ServiceOfferingVO;
+import com.cloud.service.dao.ServiceOfferingDao;
 import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.dao.VMTemplateDao;
 import com.cloud.user.Account;
 import com.cloud.user.AccountManager;
 import com.cloud.user.User;
+import com.cloud.utils.Pair;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.dao.VMInstanceDao;
 import org.apache.cloudstack.api.BaseCmd;
@@ -59,7 +63,12 @@ import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 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.WORKER;
 
 @RunWith(MockitoJUnitRunner.class)
 public class KubernetesClusterManagerImplTest {
@@ -85,6 +94,9 @@ public class KubernetesClusterManagerImplTest {
     @Mock
     private AccountManager accountManager;
 
+    @Mock
+    private ServiceOfferingDao serviceOfferingDao;
+
     @Spy
     @InjectMocks
     KubernetesClusterManagerImpl kubernetesClusterManager;
@@ -292,4 +304,63 @@ public class KubernetesClusterManagerImplTest {
         Mockito.when(kubernetesClusterDao.findById(Mockito.anyLong())).thenReturn(cluster);
         Assert.assertTrue(kubernetesClusterManager.removeVmsFromCluster(cmd).size() > 0);
     }
+
+    @Test
+    public void testValidateServiceOfferingNodeType() {
+        Map<String, Long> map = new HashMap<>();
+        map.put(WORKER.name(), 1L);
+        map.put(CONTROL.name(), 2L);
+        ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class);
+        Mockito.when(serviceOfferingDao.findById(1L)).thenReturn(serviceOffering);
+        Mockito.when(serviceOffering.isDynamic()).thenReturn(false);
+        Mockito.when(serviceOffering.getCpu()).thenReturn(2);
+        Mockito.when(serviceOffering.getRamSize()).thenReturn(2048);
+        KubernetesSupportedVersion version = Mockito.mock(KubernetesSupportedVersion.class);
+        Mockito.when(version.getMinimumCpu()).thenReturn(2);
+        Mockito.when(version.getMinimumRamSize()).thenReturn(2048);
+        kubernetesClusterManager.validateServiceOfferingForNode(map, WORKER.name(), null, version);
+        Mockito.verify(kubernetesClusterManager).validateServiceOffering(serviceOffering, version);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidateServiceOfferingNodeTypeInvalidOffering() {
+        Map<String, Long> map = new HashMap<>();
+        map.put(WORKER.name(), 1L);
+        map.put(CONTROL.name(), 2L);
+        ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class);
+        Mockito.when(serviceOfferingDao.findById(1L)).thenReturn(serviceOffering);
+        Mockito.when(serviceOffering.isDynamic()).thenReturn(true);
+        kubernetesClusterManager.validateServiceOfferingForNode(map, WORKER.name(), null, null);
+    }
+
+    @Test
+    public void testClusterCapacity() {
+        long workerOfferingId = 1L;
+        long controlOfferingId = 2L;
+        long workerCount = 2L;
+        long controlCount = 2L;
+
+        int workerOfferingCpus = 4;
+        int workerOfferingMemory = 4096;
+        int controlOfferingCpus = 2;
+        int controlOfferingMemory = 2048;
+
+        Map<String, Long> map = Map.of(WORKER.name(), workerOfferingId, CONTROL.name(), controlOfferingId);
+        Map<String, Long> nodeCount = Map.of(WORKER.name(), workerCount, CONTROL.name(), controlCount);
+
+        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);
+        Mockito.when(workerOffering.getCpu()).thenReturn(workerOfferingCpus);
+        Mockito.when(workerOffering.getRamSize()).thenReturn(workerOfferingMemory);
+        Mockito.when(controlOffering.getCpu()).thenReturn(controlOfferingCpus);
+        Mockito.when(controlOffering.getRamSize()).thenReturn(controlOfferingMemory);
+
+        Pair<Long, Long> pair = kubernetesClusterManager.calculateClusterCapacity(map, nodeCount);
+        Long expectedCpu = (workerOfferingCpus * workerCount) + (controlOfferingCpus * controlCount);
+        Long expectedMemory = (workerOfferingMemory * workerCount) + (controlOfferingMemory * controlCount);
+        Assert.assertEquals(expectedCpu, pair.first());
+        Assert.assertEquals(expectedMemory, pair.second());
+    }
 }
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
index 406516581a7..6dbbc53ec22 100644
--- 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
@@ -83,12 +83,12 @@ public class CreateKubernetesClusterCmdTest {
 
     @Test
     public void testNodeOfferingMapMissingEtcd() {
-        cmd.nodeTypeOfferingMap = new HashMap<>();
+        cmd.serviceOfferingNodeTypeMap = new HashMap<>();
         Map<String, String> firstMap = createMapEntry(WORKER, workerNodesOfferingId);
         Map<String, String> secondMap = createMapEntry(CONTROL, controlNodesOfferingId);
-        cmd.nodeTypeOfferingMap.put("map1", firstMap);
-        cmd.nodeTypeOfferingMap.put("map2", secondMap);
-        Map<String, Long> map = cmd.getNodeTypeOfferingMap();
+        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()));
@@ -98,9 +98,9 @@ public class CreateKubernetesClusterCmdTest {
 
     @Test
     public void testNodeOfferingMapNullMap() {
-        cmd.nodeTypeOfferingMap = null;
+        cmd.serviceOfferingNodeTypeMap = null;
         cmd.serviceOfferingId = controlOfferingId;
-        Map<String, Long> map = cmd.getNodeTypeOfferingMap();
+        Map<String, Long> map = cmd.getServiceOfferingNodeTypeMap();
         Assert.assertNotNull(map);
         Assert.assertEquals(2, map.size());
         Assert.assertTrue(map.containsKey(WORKER.name()) && map.containsKey(CONTROL.name()));
@@ -110,12 +110,12 @@ public class CreateKubernetesClusterCmdTest {
 
     @Test
     public void testNodeOfferingMapEtcdNodes() {
-        cmd.nodeTypeOfferingMap = new HashMap<>();
+        cmd.serviceOfferingNodeTypeMap = new HashMap<>();
         Map<String, String> firstMap = createMapEntry(ETCD, etcdNodesOfferingId);
-        cmd.nodeTypeOfferingMap.put("map1", firstMap);
+        cmd.serviceOfferingNodeTypeMap.put("map1", firstMap);
         cmd.etcdNodes = 2L;
         cmd.serviceOfferingId = controlOfferingId;
-        Map<String, Long> map = cmd.getNodeTypeOfferingMap();
+        Map<String, Long> map = cmd.getServiceOfferingNodeTypeMap();
         Assert.assertNotNull(map);
         Assert.assertEquals(3, map.size());
         Assert.assertTrue(map.containsKey(WORKER.name()) && map.containsKey(CONTROL.name()) && map.containsKey(ETCD.name()));