From 21b76ea7e7640355d474b4871723fc8a55eefd6c Mon Sep 17 00:00:00 2001 From: Pearl1594 Date: Thu, 10 Sep 2026 15:56:51 -0400 Subject: [PATCH 1/2] CCKS: fix scaling failures for clusters without a default service offering --- .../cluster/KubernetesClusterManagerImpl.java | 33 ++++++++++++++----- .../KubernetesClusterScaleWorker.java | 26 ++++++--------- 2 files changed, 34 insertions(+), 25 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 061f6264d66c..e352a690ca66 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 @@ -1398,6 +1398,16 @@ protected void ensureResourceLimitsForScale(final KubernetesClusterVO cluster, } } + public static long getNodeCountForType(KubernetesClusterNodeType nodeType, KubernetesCluster kubernetesCluster) { + if (WORKER == nodeType) { + return kubernetesCluster.getNodeCount(); + } else if (CONTROL == nodeType) { + return kubernetesCluster.getControlNodeCount(); + } else if (ETCD == nodeType) { + return kubernetesCluster.getEtcdNodeCount(); + } + return kubernetesCluster.getTotalNodeCount(); + } protected void validateServiceOfferingsForNodeTypesScale(Map map, Long defaultServiceOfferingId, KubernetesClusterVO kubernetesCluster, KubernetesSupportedVersion clusterVersion) { for (String key : CLUSTER_NODES_TYPES_LIST) { @@ -1410,6 +1420,9 @@ protected void validateServiceOfferingsForNodeTypesScale(Map map, checkServiceOfferingForNodesScale(serviceOffering, kubernetesCluster, clusterVersion); Long nodeTypeOfferingId = getExistingServiceOfferingIdForNodeType(key, kubernetesCluster); if (nodeTypeOfferingId == null) { + if (getNodeCountForType(KubernetesClusterNodeType.valueOf(key), kubernetesCluster) <=0) { + continue; + } nodeTypeOfferingId = kubernetesCluster.getServiceOfferingId(); } final ServiceOffering existingServiceOffering = serviceOfferingDao.findById(nodeTypeOfferingId); @@ -2196,18 +2209,20 @@ public boolean scaleKubernetesCluster(ScaleKubernetesClusterCmd cmd) throws Clou protected Map createNodeTypeToServiceOfferingMap(Map idsMapping, Long serviceOfferingId, KubernetesClusterVO kubernetesCluster) { Map map = new HashMap<>(); - if (MapUtils.isEmpty(idsMapping)) { - ServiceOfferingVO offering = serviceOfferingId != null ? - serviceOfferingDao.findById(serviceOfferingId) : - serviceOfferingDao.findById(kubernetesCluster.getServiceOfferingId()); - map.put(DEFAULT.name(), offering); + if (MapUtils.isNotEmpty(idsMapping)) { + for (String key : CLUSTER_NODES_TYPES_LIST) { + if (idsMapping.containsKey(key)) { + map.put(key, serviceOfferingDao.findById(idsMapping.get(key))); + } + } return map; } - for (String key : CLUSTER_NODES_TYPES_LIST) { - if (!idsMapping.containsKey(key)) { - continue; + Long defaultOfferingId = serviceOfferingId != null ? serviceOfferingId : kubernetesCluster.getServiceOfferingId(); + if (defaultOfferingId != null) { + ServiceOfferingVO offering = serviceOfferingDao.findById(defaultOfferingId); + if (offering != null) { + map.put(DEFAULT.name(), offering); } - map.put(key, serviceOfferingDao.findById(idsMapping.get(key))); } return map; } 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 0ca27c29d8df..5a91831b6c53 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 @@ -225,7 +225,7 @@ protected Pair calculateNewClusterCountAndCapacity(Long newWorkerSiz cores = serviceOffering.getCpu() * totalClusterSize; memory = serviceOffering.getRamSize() * totalClusterSize; } else { - long nodeCount = getNodeCountForType(nodeType, kubernetesCluster); + long nodeCount = KubernetesClusterManagerImpl.getNodeCountForType(nodeType, kubernetesCluster); Long existingOfferingId = getExistingOfferingIdForNodeType(nodeType, kubernetesCluster); if (existingOfferingId == null) { existingOfferingId = serviceOffering.getId(); @@ -243,17 +243,6 @@ protected Pair calculateNewClusterCountAndCapacity(Long newWorkerSiz return new Pair<>(cores, memory); } - private long getNodeCountForType(KubernetesClusterNodeType nodeType, KubernetesCluster kubernetesCluster) { - if (WORKER == nodeType) { - return kubernetesCluster.getNodeCount(); - } else if (CONTROL == nodeType) { - return kubernetesCluster.getControlNodeCount(); - } else if (ETCD == nodeType) { - return kubernetesCluster.getEtcdNodeCount(); - } - return kubernetesCluster.getTotalNodeCount(); - } - protected Pair calculateClusterNewCapacity(KubernetesCluster kubernetesCluster, Pair previousNodeTypeCapacity, Pair newNodeTypeCapacity) { @@ -337,7 +326,9 @@ private void validateKubernetesClusterScaleSizeParameters() throws CloudRuntimeE // Check capacity and transition state final long newVmRequiredCount = clusterSize - originalClusterSize; final ServiceOffering clusterServiceOffering = serviceOfferingDao.findById(kubernetesCluster.getServiceOfferingId()); - if (clusterServiceOffering == null) { + final ServiceOffering existingWorkerOffering = serviceOfferingDao.findById(kubernetesCluster.getWorkerNodeServiceOfferingId()); + final ServiceOffering effectiveOffering = existingWorkerOffering != null ? existingWorkerOffering : clusterServiceOffering; + if (effectiveOffering == null) { logTransitStateToFailedIfNeededAndThrow(Level.WARN, String.format("Scaling failed for Kubernetes cluster : %s, cluster service offering not found", kubernetesCluster.getName())); } if (newVmRequiredCount > 0) { @@ -345,9 +336,9 @@ private void validateKubernetesClusterScaleSizeParameters() throws CloudRuntimeE VMTemplateVO clusterTemplate = templateDao.findById(kubernetesCluster.getTemplateId()); try { if (originalState.equals(KubernetesCluster.State.Running)) { - plan(newVmRequiredCount, zone, clusterServiceOffering, kubernetesCluster.getDomainId(), kubernetesCluster.getAccountId(), clusterTemplate.getHypervisorType(), clusterTemplate.getArch()); + plan(newVmRequiredCount, zone, effectiveOffering, kubernetesCluster.getDomainId(), kubernetesCluster.getAccountId(), clusterTemplate.getHypervisorType(), clusterTemplate.getArch()); } else { - plan(kubernetesCluster.getTotalNodeCount() + newVmRequiredCount, zone, clusterServiceOffering, kubernetesCluster.getDomainId(), kubernetesCluster.getAccountId(), clusterTemplate.getHypervisorType(), clusterTemplate.getArch()); + plan(kubernetesCluster.getTotalNodeCount() + newVmRequiredCount, zone, effectiveOffering, kubernetesCluster.getDomainId(), kubernetesCluster.getAccountId(), clusterTemplate.getHypervisorType(), clusterTemplate.getArch()); } } catch (InsufficientCapacityException e) { logTransitStateToFailedIfNeededAndThrow(Level.WARN, String.format("Scaling failed for Kubernetes cluster : %s in zone : %s, insufficient capacity", kubernetesCluster.getName(), zone.getName())); @@ -370,7 +361,7 @@ private void scaleKubernetesClusterOffering(KubernetesClusterNodeType nodeType, kubernetesCluster = updateKubernetesClusterEntryForNodeType(null, nodeType, serviceOffering, updateNodeOffering, updateClusterOffering); return; } - final long size = getNodeCountForType(nodeType, kubernetesCluster); + final long size = KubernetesClusterManagerImpl.getNodeCountForType(nodeType, kubernetesCluster); List vmList = kubernetesClusterVmMapDao.listByClusterIdAndVmType(kubernetesCluster.getId(), nodeType); final long tobeScaledVMCount = Math.min(vmList.size(), size); for (long i = 0; i < tobeScaledVMCount; i++) { @@ -610,6 +601,9 @@ public boolean scaleCluster() throws CloudRuntimeException { private ServiceOffering getExistingServiceOfferingForNodeType(KubernetesClusterNodeType nodeType, KubernetesCluster kubernetesCluster) { Long existingOfferingId = getExistingOfferingIdForNodeType(nodeType, kubernetesCluster); if (existingOfferingId == null) { + if (KubernetesClusterManagerImpl.getNodeCountForType(nodeType, kubernetesCluster) <= 0) { + return null; + } logAndThrow(Level.ERROR, String.format("The Kubernetes cluster %s does not have a service offering set for node type %s", kubernetesCluster.getName(), nodeType.name())); } From 779fa7967ead4a0fadba701a9314516b7315ae99 Mon Sep 17 00:00:00 2001 From: Pearl1594 Date: Fri, 11 Sep 2026 12:55:20 -0400 Subject: [PATCH 2/2] extract common code and check node-type offering if present during upgrade and scale --- .../cluster/KubernetesClusterManagerImpl.java | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 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 e352a690ca66..6220b3a85d89 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 @@ -1418,14 +1418,10 @@ protected void validateServiceOfferingsForNodeTypesScale(Map map, throw new InvalidParameterValueException("Failed to find service offering ID: " + serviceOfferingId); } checkServiceOfferingForNodesScale(serviceOffering, kubernetesCluster, clusterVersion); - Long nodeTypeOfferingId = getExistingServiceOfferingIdForNodeType(key, kubernetesCluster); - if (nodeTypeOfferingId == null) { - if (getNodeCountForType(KubernetesClusterNodeType.valueOf(key), kubernetesCluster) <=0) { - continue; - } - nodeTypeOfferingId = kubernetesCluster.getServiceOfferingId(); + final ServiceOffering existingServiceOffering = getEffectiveServiceOfferingForNodeType(key, kubernetesCluster); + if (existingServiceOffering == null) { + continue; } - final ServiceOffering existingServiceOffering = serviceOfferingDao.findById(nodeTypeOfferingId); 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", @@ -1446,6 +1442,33 @@ private Long getExistingServiceOfferingIdForNodeType(String key, KubernetesClust return kubernetesCluster.getServiceOfferingId(); } + // Resolves the offering backing a node type, falling back to the legacy cluster-wide offering; null if the cluster has no nodes of this type. + protected ServiceOffering getEffectiveServiceOfferingForNodeType(String key, KubernetesClusterVO kubernetesCluster) { + if (getNodeCountForType(KubernetesClusterNodeType.valueOf(key), kubernetesCluster) <= 0) { + return null; + } + Long offeringId = getExistingServiceOfferingIdForNodeType(key, kubernetesCluster); + if (offeringId == null) { + offeringId = kubernetesCluster.getServiceOfferingId(); + } + if (offeringId == null) { + return null; + } + return serviceOfferingDao.findByIdIncludingRemoved(offeringId); + } + + // Effective offerings for every node type actually present on the cluster. + protected List getEffectiveServiceOfferingsForCluster(KubernetesClusterVO kubernetesCluster) { + List offerings = new ArrayList<>(); + for (String key : CLUSTER_NODES_TYPES_LIST) { + ServiceOffering offering = getEffectiveServiceOfferingForNodeType(key, kubernetesCluster); + if (offering != null) { + offerings.add(offering); + } + } + return offerings; + } + 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())); @@ -1510,17 +1533,19 @@ private void validateKubernetesClusterUpgradeParameters(UpgradeKubernetesCluster throw new InvalidParameterValueException(String.format("Invalid Kubernetes version associated with cluster : %s", kubernetesCluster.getName())); } - final ServiceOffering serviceOffering = serviceOfferingDao.findByIdIncludingRemoved(kubernetesCluster.getServiceOfferingId()); - if (serviceOffering == null) { + final List effectiveServiceOfferings = getEffectiveServiceOfferingsForCluster(kubernetesCluster); + if (CollectionUtils.isEmpty(effectiveServiceOfferings)) { throw new CloudRuntimeException(String.format("Invalid service offering associated with Kubernetes cluster : %s", kubernetesCluster.getName())); } - if (serviceOffering.getCpu() < upgradeVersion.getMinimumCpu()) { - throw new InvalidParameterValueException(String.format("Kubernetes cluster : %s cannot be upgraded with Kubernetes version : %s which needs minimum %d vCPUs while associated service offering : %s offers only %d vCPUs", - kubernetesCluster.getName(), upgradeVersion.getName(), upgradeVersion.getMinimumCpu(), serviceOffering.getName(), serviceOffering.getCpu())); - } - if (serviceOffering.getRamSize() < upgradeVersion.getMinimumRamSize()) { - throw new InvalidParameterValueException(String.format("Kubernetes cluster : %s cannot be upgraded with Kubernetes version : %s which needs minimum %d MB RAM while associated service offering : %s offers only %d MB RAM", - kubernetesCluster.getName(), upgradeVersion.getName(), upgradeVersion.getMinimumRamSize(), serviceOffering.getName(), serviceOffering.getRamSize())); + for (ServiceOffering serviceOffering : effectiveServiceOfferings) { + if (serviceOffering.getCpu() < upgradeVersion.getMinimumCpu()) { + throw new InvalidParameterValueException(String.format("Kubernetes cluster : %s cannot be upgraded with Kubernetes version : %s which needs minimum %d vCPUs while associated service offering : %s offers only %d vCPUs", + kubernetesCluster.getName(), upgradeVersion.getName(), upgradeVersion.getMinimumCpu(), serviceOffering.getName(), serviceOffering.getCpu())); + } + if (serviceOffering.getRamSize() < upgradeVersion.getMinimumRamSize()) { + throw new InvalidParameterValueException(String.format("Kubernetes cluster : %s cannot be upgraded with Kubernetes version : %s which needs minimum %d MB RAM while associated service offering : %s offers only %d MB RAM", + kubernetesCluster.getName(), upgradeVersion.getName(), upgradeVersion.getMinimumRamSize(), serviceOffering.getName(), serviceOffering.getRamSize())); + } } // Check upgradeVersion is either patch upgrade or immediate minor upgrade try {