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 2397c22f4a042df5940ec06e331a2d92e0573d34 Author: nvazquez <[email protected]> AuthorDate: Tue Jan 30 23:40:37 2024 -0300 Add node type to service offering mapping parameter on createKubernetesCluster API --- .../cluster/KubernetesClusterHelper.java | 5 ++ .../main/java/com/cloud/vm/VmDetailConstants.java | 2 + .../org/apache/cloudstack/api/ApiConstants.java | 1 + .../cluster/KubernetesClusterHelperImpl.java | 14 ++++ .../cluster/CreateKubernetesClusterCmd.java | 35 +++++++++ .../cluster/KubernetesClusterHelperImplTest.java | 45 +++++++++++ .../cluster/CreateKubernetesClusterCmdTest.java | 90 ++++++++++++++++++++++ 7 files changed, 192 insertions(+) diff --git a/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java index e160227749d..896bdcfde57 100644 --- a/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java +++ b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java @@ -21,6 +21,11 @@ import org.apache.cloudstack.acl.ControlledEntity; public interface KubernetesClusterHelper extends Adapter { + enum KubernetesClusterNodeType { + WORKER, MASTER, ETCD + } + ControlledEntity findByUuid(String uuid); ControlledEntity findByVmId(long vmId); + boolean isValidNodeType(String nodeType); } diff --git a/api/src/main/java/com/cloud/vm/VmDetailConstants.java b/api/src/main/java/com/cloud/vm/VmDetailConstants.java index 9338cc11cd4..d4e23659ec3 100644 --- a/api/src/main/java/com/cloud/vm/VmDetailConstants.java +++ b/api/src/main/java/com/cloud/vm/VmDetailConstants.java @@ -87,6 +87,8 @@ public interface VmDetailConstants { String DEPLOY_AS_IS_CONFIGURATION = "configurationId"; String KEY_PAIR_NAMES = "keypairnames"; String CKS_CONTROL_NODE_LOGIN_USER = "controlNodeLoginUser"; + String CKS_NODE_TYPE = "node"; + String OFFERING = "offering"; // VMware to KVM VM migrations specific String VMWARE_TO_KVM_PREFIX = "vmware-to-kvm"; diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 51523b1863e..ef21916c96d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1036,6 +1036,7 @@ public class ApiConstants { public static final String AUTOSCALING_ENABLED = "autoscalingenabled"; public static final String MIN_SIZE = "minsize"; public static final String MAX_SIZE = "maxsize"; + public static final String NODE_TYPE_OFFERING_MAP = "nodetypeofferingmap"; public static final String BOOT_TYPE = "boottype"; public static final String BOOT_MODE = "bootmode"; diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java index 60bd81c7c5a..639e17bd560 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java @@ -22,6 +22,7 @@ import com.cloud.utils.component.AdapterBase; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import javax.inject.Inject; @@ -49,6 +50,19 @@ public class KubernetesClusterHelperImpl extends AdapterBase implements Kubernet return kubernetesClusterDao.findById(clusterVmMapVO.getClusterId()); } + @Override + public boolean isValidNodeType(String nodeType) { + if (StringUtils.isBlank(nodeType)) { + return false; + } + try { + KubernetesClusterNodeType.valueOf(nodeType.toUpperCase()); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + @Override public String getConfigComponentName() { return KubernetesClusterHelper.class.getSimpleName(); 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 12a50c9e88f..493d9b61707 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java @@ -17,10 +17,15 @@ 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.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; @@ -40,6 +45,7 @@ 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; @@ -62,6 +68,8 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd { @Inject public KubernetesClusterService kubernetesClusterService; + @Inject + protected KubernetesClusterHelper kubernetesClusterHelper; ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -87,6 +95,11 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd { description = "the ID of the service offering for the virtual machines in the cluster.") private Long serviceOfferingId; + @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; + @ACL(accessType = AccessType.UseEntry) @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "an optional account for the" + " virtual machine. Must be used with domainId.") @@ -244,6 +257,28 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd { return clusterType; } + public Map<String, Long> getNodeTypeOfferingMap() { + Map<String, Long> mapping = new HashMap<>(); + if (MapUtils.isNotEmpty(nodeTypeOfferingMap)) { + for (Map<String, String> entry : nodeTypeOfferingMap.values()) { + String nodeTypeStr = entry.get(VmDetailConstants.CKS_NODE_TYPE); + String serviceOfferingUuid = entry.get(VmDetailConstants.OFFERING); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("Node Type: '%s' should use Service Offering ID: '%s'", nodeTypeStr, serviceOfferingUuid)); + } + ServiceOffering serviceOffering = _entityMgr.findByUuid(ServiceOffering.class, serviceOfferingUuid); + if (StringUtils.isAnyEmpty(nodeTypeStr, serviceOfferingUuid) || + !kubernetesClusterHelper.isValidNodeType(nodeTypeStr) || + serviceOffering == null) { + throw new InvalidParameterValueException(String.format("Service Offering ID: %s for Node Type: %s is invalid", serviceOfferingUuid, nodeTypeStr)); + } + KubernetesClusterHelper.KubernetesClusterNodeType nodeType = KubernetesClusterHelper.KubernetesClusterNodeType.valueOf(nodeTypeStr.toUpperCase()); + mapping.put(nodeType.name(), serviceOffering.getId()); + } + } + return mapping; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImplTest.java b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImplTest.java new file mode 100644 index 00000000000..bab58d20604 --- /dev/null +++ b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImplTest.java @@ -0,0 +1,45 @@ +// 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 com.cloud.kubernetes.cluster; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class KubernetesClusterHelperImplTest { + + private final KubernetesClusterHelperImpl helper = new KubernetesClusterHelperImpl(); + + @Test + public void testIsValidNodeTypeEmptyNodeType() { + Assert.assertFalse(helper.isValidNodeType(null)); + } + + @Test + public void testIsValidNodeTypeInvalidNodeType() { + String nodeType = "invalidNodeType"; + Assert.assertFalse(helper.isValidNodeType(nodeType)); + } + + @Test + public void testIsValidNodeTypeValidNodeTypeLowercase() { + String nodeType = KubernetesClusterHelper.KubernetesClusterNodeType.WORKER.name().toLowerCase(); + Assert.assertTrue(helper.isValidNodeType(nodeType)); + } +} 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 new file mode 100644 index 00000000000..cd61baedfaa --- /dev/null +++ b/plugins/integrations/kubernetes-service/src/test/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmdTest.java @@ -0,0 +1,90 @@ +// 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.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.MASTER; +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 masterServiceOffering; + + private final CreateKubernetesClusterCmd cmd = new CreateKubernetesClusterCmd(); + + private static final String workerNodesOfferingId = UUID.randomUUID().toString(); + private static final String masterNodesOfferingId = UUID.randomUUID().toString(); + private static final Long workerOfferingId = 1L; + private static final Long masterOfferingId = 2L; + + @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, masterNodesOfferingId)).thenReturn(masterServiceOffering); + Mockito.when(workerServiceOffering.getId()).thenReturn(workerOfferingId); + Mockito.when(masterServiceOffering.getId()).thenReturn(masterOfferingId); + } + + 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.nodeTypeOfferingMap = new HashMap<>(); + Map<String, String> firstMap = createMapEntry(WORKER, workerNodesOfferingId); + Map<String, String> secondMap = createMapEntry(MASTER, masterNodesOfferingId); + cmd.nodeTypeOfferingMap.put("map1", firstMap); + cmd.nodeTypeOfferingMap.put("map2", secondMap); + Map<String, Long> map = cmd.getNodeTypeOfferingMap(); + Assert.assertNotNull(map); + Assert.assertEquals(2, map.size()); + Assert.assertTrue(map.containsKey(WORKER.name()) && map.containsKey(MASTER.name())); + Assert.assertEquals(workerOfferingId, map.get(WORKER.name())); + Assert.assertEquals(masterOfferingId, map.get(MASTER.name())); + } +}
