In progress scale kubernetes cluster

This commit is contained in:
nvazquez 2024-02-08 00:39:42 -03:00
parent b85b78d17e
commit 2cf8d674a8
No known key found for this signature in database
GPG Key ID: 656E1BCC8CB54F84
10 changed files with 421 additions and 243 deletions

View File

@ -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);
}

View File

@ -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();

View File

@ -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,39 +1102,55 @@ 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()));
}
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()));
}
}
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()));
}
}
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);
}
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()));
}
}
}
}
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) {
// Validate parameters
validateEndpointUrl();
@ -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()) {

View File

@ -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;

View File

@ -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);
}
/////////////////////////////////////////////////////

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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()));
}
}

View File

@ -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);
}
}

View File

@ -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
}
}
}