This is an automated email from the ASF dual-hosted git repository. weizhou pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 70a4503ea16db57dbcd2535a55f90dbad538a52f Merge: b46e29dc677 38006b2e03d Author: Wei Zhou <weiz...@apache.org> AuthorDate: Thu Sep 11 14:04:52 2025 +0200 Merge remote-tracking branch 'apache/4.20' .../main/java/com/cloud/hypervisor/Hypervisor.java | 6 ++- .../kubernetes/cluster/KubernetesCluster.java | 4 ++ .../com/cloud/agent/manager/AgentManagerImpl.java | 2 +- .../kvm/resource/LibvirtComputingResource.java | 14 ++++++ ...LibvirtGetUnmanagedInstancesCommandWrapper.java | 8 +++- .../KubernetesClusterScaleWorker.java | 5 +- .../main/java/com/cloud/vm/UserVmManagerImpl.java | 3 +- .../resource/NfsSecondaryStorageResource.java | 22 +++++++-- .../resource/NfsSecondaryStorageResourceTest.java | 56 +++++++++++++++++++--- systemvm/debian/opt/cloud/bin/cs/CsNetfilter.py | 2 + .../views/compute/wizard/MultiNetworkSelection.vue | 16 ++++++- 11 files changed, 119 insertions(+), 19 deletions(-) diff --cc api/src/main/java/com/cloud/kubernetes/cluster/KubernetesCluster.java index 8b5551d6a8e,379a0db2ccd..571d993c7a1 --- a/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesCluster.java +++ b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesCluster.java @@@ -60,9 -58,8 +60,10 @@@ public interface KubernetesCluster exte Stopping("Resources for the Kubernetes cluster are being destroyed"), Stopped("All resources for the Kubernetes cluster are destroyed, Kubernetes cluster may still have ephemeral resource like persistent volumes provisioned"), Scaling("Transient state in which resources are either getting scaled up/down"), + ScalingStoppedCluster("Transient state in which the service offerings of stopped clusters are getting scaled"), Upgrading("Transient state in which cluster is getting upgraded"), + Importing("Transient state in which additional nodes are added as worker nodes to a cluster"), + RemovingNodes("Transient state in which additional nodes are removed from a cluster"), Alert("State to represent Kubernetes clusters which are not in expected desired state (operationally in active control place, stopped cluster VM's etc)."), Recovering("State in which Kubernetes cluster is recovering from alert state"), Destroyed("End state of Kubernetes cluster in which all resources are destroyed, cluster will not be usable further"), diff --cc plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterScaleWorker.java index 3461d5c0634,f6828e3b203..0938fd44554 --- 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 @@@ -355,10 -282,10 +355,11 @@@ public class KubernetesClusterScaleWork } } - private void scaleKubernetesClusterOffering() throws CloudRuntimeException { + private void scaleKubernetesClusterOffering(KubernetesClusterNodeType nodeType, ServiceOffering serviceOffering, + boolean updateNodeOffering, boolean updateClusterOffering) throws CloudRuntimeException { validateKubernetesClusterScaleOfferingParameters(); - if (!kubernetesCluster.getState().equals(KubernetesCluster.State.Scaling)) { + List<KubernetesCluster.State> scalingStates = List.of(KubernetesCluster.State.Scaling, KubernetesCluster.State.ScalingStoppedCluster); + if (!scalingStates.contains(kubernetesCluster.getState())) { stateTransitTo(kubernetesCluster.getId(), KubernetesCluster.Event.ScaleUpRequested); } if (KubernetesCluster.State.Created.equals(originalState)) { @@@ -532,58 -447,38 +533,60 @@@ } scaleTimeoutTime = System.currentTimeMillis() + KubernetesClusterService.KubernetesClusterScaleTimeout.value() * 1000; final long originalClusterSize = kubernetesCluster.getNodeCount(); - final ServiceOffering existingServiceOffering = serviceOfferingDao.findById(kubernetesCluster.getServiceOfferingId()); - if (existingServiceOffering == null) { - logAndThrow(Level.ERROR, String.format("Scaling Kubernetes cluster %s failed, service offering for the Kubernetes cluster not found!", kubernetesCluster)); + + // DEFAULT node type means only the global service offering has been set for the Kubernetes cluster + boolean scaleClusterDefaultOffering = serviceOfferingNodeTypeMap.containsKey(DEFAULT.name()); + if (scaleClusterDefaultOffering) { + final ServiceOffering existingServiceOffering = serviceOfferingDao.findById(kubernetesCluster.getServiceOfferingId()); + final ServiceOffering existingControlOffering = serviceOfferingDao.findById(kubernetesCluster.getControlNodeServiceOfferingId()); + final ServiceOffering existingWorkerOffering = serviceOfferingDao.findById(kubernetesCluster.getWorkerNodeServiceOfferingId()); + if (existingServiceOffering == null && ObjectUtils.anyNull(existingControlOffering, existingWorkerOffering)) { + logAndThrow(Level.ERROR, String.format("Scaling Kubernetes cluster : %s failed, service offering for the Kubernetes cluster not found!", kubernetesCluster.getName())); + } } + final boolean autoscalingChanged = isAutoscalingChanged(); - final boolean serviceOfferingScalingNeeded = serviceOffering != null && serviceOffering.getId() != existingServiceOffering.getId(); + ServiceOffering defaultServiceOffering = serviceOfferingNodeTypeMap.getOrDefault(DEFAULT.name(), null); + + for (KubernetesClusterNodeType nodeType : Arrays.asList(CONTROL, ETCD, WORKER)) { + boolean isWorkerNode = WORKER == nodeType; + final long newVMRequired = (!isWorkerNode || clusterSize == null) ? 0 : clusterSize - originalClusterSize; + if (!scaleClusterDefaultOffering && !serviceOfferingNodeTypeMap.containsKey(nodeType.name()) && newVMRequired == 0) { + continue; + } + + ServiceOffering existingServiceOffering = getExistingServiceOfferingForNodeType(nodeType, kubernetesCluster); + ServiceOffering scalingServiceOffering = serviceOfferingNodeTypeMap.getOrDefault(nodeType.name(), defaultServiceOffering); + boolean isNodeOfferingScalingNeeded = isServiceOfferingScalingNeededForNodeType(existingServiceOffering, scalingServiceOffering); + boolean updateNodeOffering = serviceOfferingNodeTypeMap.containsKey(nodeType.name()) || isNodeOfferingScalingNeeded; - if (autoscalingChanged) { - boolean autoScaled = autoscaleCluster(this.isAutoscalingEnabled, minSize, maxSize); - if (autoScaled && serviceOfferingScalingNeeded) { - scaleKubernetesClusterOffering(); + boolean updateClusterOffering = isWorkerNode && scaleClusterDefaultOffering; + if (isWorkerNode && autoscalingChanged) { + boolean autoScaled = autoscaleCluster(this.isAutoscalingEnabled, minSize, maxSize); + if (autoScaled && isNodeOfferingScalingNeeded) { + scaleKubernetesClusterOffering(nodeType, scalingServiceOffering, updateNodeOffering, updateClusterOffering); + } + stateTransitTo(kubernetesCluster.getId(), KubernetesCluster.Event.OperationSucceeded); + return autoScaled; } - stateTransitTo(kubernetesCluster.getId(), KubernetesCluster.Event.OperationSucceeded); - return autoScaled; - } - final boolean clusterSizeScalingNeeded = clusterSize != null && clusterSize != originalClusterSize; - final long newVMRequired = clusterSize == null ? 0 : clusterSize - originalClusterSize; - if (serviceOfferingScalingNeeded && clusterSizeScalingNeeded) { - if (newVMRequired > 0) { - scaleKubernetesClusterOffering(); - scaleKubernetesClusterSize(); + final boolean clusterSizeScalingNeeded = isWorkerNode && clusterSize != null && clusterSize != originalClusterSize; + if (isNodeOfferingScalingNeeded && clusterSizeScalingNeeded) { + if (newVMRequired > 0) { + scaleKubernetesClusterOffering(nodeType, scalingServiceOffering, updateNodeOffering, updateClusterOffering); + scaleKubernetesClusterSize(nodeType); + } else { + scaleKubernetesClusterSize(nodeType); + scaleKubernetesClusterOffering(nodeType, scalingServiceOffering, updateNodeOffering, updateClusterOffering); + } + } else if (isNodeOfferingScalingNeeded) { + scaleKubernetesClusterOffering(nodeType, scalingServiceOffering, updateNodeOffering, updateClusterOffering); + } else if (clusterSizeScalingNeeded) { + scaleKubernetesClusterSize(nodeType); + } else { - scaleKubernetesClusterSize(); - scaleKubernetesClusterOffering(); ++ return true; } - } else if (serviceOfferingScalingNeeded) { - scaleKubernetesClusterOffering(); - } else if (clusterSizeScalingNeeded) { - scaleKubernetesClusterSize(); - } else { - return true; } + stateTransitTo(kubernetesCluster.getId(), KubernetesCluster.Event.OperationSucceeded); return true; } diff --cc services/secondary-storage/server/src/test/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResourceTest.java index 6d474ddcc92,9f510f25b12..17414c60f20 --- a/services/secondary-storage/server/src/test/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResourceTest.java +++ b/services/secondary-storage/server/src/test/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResourceTest.java @@@ -18,10 -18,10 +18,9 @@@ */ package org.apache.cloudstack.storage.resource; - import org.apache.logging.log4j.Logger; import static org.mockito.ArgumentMatchers.any; - import org.mockito.Mock; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.spy; + import static org.mockito.Mockito.times; import java.io.File; import java.nio.file.Files;