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:33 UTC
(cloudstack) 04/12: Refactor and add UI for createKubernetesCluster API advanced settings
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 959f2b6a30bfd8bf4e0ae20a4b6ca63895f7a63f
Author: nvazquez <ni...@gmail.com>
AuthorDate: Wed Jan 31 15:35:37 2024 -0300
Refactor and add UI for createKubernetesCluster API advanced settings
---
.../cluster/KubernetesClusterHelper.java | 2 +-
.../org/apache/cloudstack/api/ApiConstants.java | 3 +-
.../cluster/CreateKubernetesClusterCmd.java | 42 +++++++++-
.../cluster/CreateKubernetesClusterCmdTest.java | 55 +++++++++---
ui/public/locales/en.json | 4 +
ui/src/views/compute/CreateKubernetesCluster.vue | 97 ++++++++++++++++++++++
6 files changed, 190 insertions(+), 13 deletions(-)
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 896bdcfde57..4b61fe5ad03 100644
--- a/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java
+++ b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java
@@ -22,7 +22,7 @@ import org.apache.cloudstack.acl.ControlledEntity;
public interface KubernetesClusterHelper extends Adapter {
enum KubernetesClusterNodeType {
- WORKER, MASTER, ETCD
+ CONTROL, WORKER, ETCD
}
ControlledEntity findByUuid(String uuid);
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 ef21916c96d..350d8742fb2 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -1028,6 +1028,7 @@ public class ApiConstants {
public static final String MASTER_NODES = "masternodes";
public static final String NODE_IDS = "nodeids";
public static final String CONTROL_NODES = "controlnodes";
+ public static final String ETCD_NODES = "etcdnodes";
public static final String MIN_SEMANTIC_VERSION = "minimumsemanticversion";
public static final String MIN_KUBERNETES_VERSION_ID = "minimumkubernetesversionid";
public static final String NODE_ROOT_DISK_SIZE = "noderootdisksize";
@@ -1036,7 +1037,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 NODE_TYPE_OFFERING_MAP = "nodeofferings";
public static final String BOOT_TYPE = "boottype";
public static final String BOOT_MODE = "bootmode";
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 9b4067ce0a2..99943408fc6 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,7 +17,9 @@
package org.apache.cloudstack.api.command.user.kubernetes.cluster;
import java.security.InvalidParameterException;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import javax.inject.Inject;
@@ -94,13 +96,19 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd {
@ACL(accessType = AccessType.UseEntry)
@Parameter(name = ApiConstants.SERVICE_OFFERING_ID, type = CommandType.UUID, entityType = ServiceOfferingResponse.class,
description = "the ID of the service offering for the virtual machines in the cluster.")
- private Long serviceOfferingId;
+ protected 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.ETCD_NODES, type = CommandType.LONG,
+ description = "(Optional) Number of Kubernetes cluster etcd nodes, default is 0." +
+ "In case the number is greater than 0, etcd nodes are separate from master nodes and are provisioned accordingly")
+ protected Long etcdNodes;
+
@ACL(accessType = AccessType.UseEntry)
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "an optional account for the" +
" virtual machine. Must be used with domainId.")
@@ -220,6 +228,10 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd {
return controlNodes;
}
+ public Long getEtcdNodes() {
+ return etcdNodes == null ? 0L : etcdNodes;
+ }
+
public String getExternalLoadBalancerIpAddress() {
return externalLoadBalancerIpAddress;
}
@@ -307,10 +319,38 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd {
for (Map<String, String> entry : nodeTypeOfferingMap.values()) {
processNodeTypeOfferingEntryAndAddToMappingIfValid(entry, mapping);
}
+ addMissingNodeTypeDefaultOffering(mapping, serviceOfferingId, etcdNodes);
+ } else {
+ addDefaultNodeTypeOfferingEntries(serviceOfferingId, etcdNodes, mapping);
}
return mapping;
}
+ private void addMissingNodeTypeDefaultOffering(Map<String, Long> mapping, Long serviceOfferingId, Long etcdNodes) {
+ if (MapUtils.isEmpty(mapping)) {
+ return;
+ }
+ boolean addEtcdOffering = etcdNodes != null && etcdNodes > 0;
+ List<String> keys = Arrays.asList(KubernetesClusterNodeType.CONTROL.name(), KubernetesClusterNodeType.WORKER.name(), KubernetesClusterNodeType.ETCD.name());
+ for (String key : keys) {
+ if (mapping.containsKey(key)) {
+ continue;
+ }
+ if (!key.equalsIgnoreCase(KubernetesClusterNodeType.ETCD.name()) ||
+ (addEtcdOffering && key.equalsIgnoreCase(KubernetesClusterNodeType.ETCD.name()))) {
+ mapping.put(key, serviceOfferingId);
+ }
+ }
+ }
+
+ protected void addDefaultNodeTypeOfferingEntries(Long serviceOfferingId, Long etcdNodes, Map<String, Long> mapping) {
+ mapping.put(KubernetesClusterNodeType.CONTROL.name(), serviceOfferingId);
+ mapping.put(KubernetesClusterNodeType.WORKER.name(), serviceOfferingId);
+ if (etcdNodes != null && etcdNodes > 0) {
+ mapping.put(KubernetesClusterNodeType.ETCD.name(), serviceOfferingId);
+ }
+ }
+
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
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 25dbfae4d86..406516581a7 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
@@ -34,7 +34,8 @@ 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.CONTROL;
+import static com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.ETCD;
import static com.cloud.kubernetes.cluster.KubernetesClusterHelper.KubernetesClusterNodeType.WORKER;
@RunWith(MockitoJUnitRunner.class)
@@ -47,23 +48,29 @@ public class CreateKubernetesClusterCmdTest {
@Mock
ServiceOffering workerServiceOffering;
@Mock
- ServiceOffering masterServiceOffering;
+ ServiceOffering controlServiceOffering;
+ @Mock
+ ServiceOffering etcdServiceOffering;
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 String controlNodesOfferingId = UUID.randomUUID().toString();
+ private static final String etcdNodesOfferingId = UUID.randomUUID().toString();
private static final Long workerOfferingId = 1L;
- private static final Long masterOfferingId = 2L;
+ 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, masterNodesOfferingId)).thenReturn(masterServiceOffering);
+ 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(masterServiceOffering.getId()).thenReturn(masterOfferingId);
+ Mockito.when(controlServiceOffering.getId()).thenReturn(controlOfferingId);
+ Mockito.when(etcdServiceOffering.getId()).thenReturn(etcdOfferingId);
}
private Map<String, String> createMapEntry(KubernetesClusterHelper.KubernetesClusterNodeType nodeType,
@@ -75,18 +82,46 @@ public class CreateKubernetesClusterCmdTest {
}
@Test
- public void testNodeOfferingMap() {
+ public void testNodeOfferingMapMissingEtcd() {
cmd.nodeTypeOfferingMap = new HashMap<>();
Map<String, String> firstMap = createMapEntry(WORKER, workerNodesOfferingId);
- Map<String, String> secondMap = createMapEntry(MASTER, masterNodesOfferingId);
+ Map<String, String> secondMap = createMapEntry(CONTROL, controlNodesOfferingId);
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.assertTrue(map.containsKey(WORKER.name()) && map.containsKey(CONTROL.name()));
Assert.assertEquals(workerOfferingId, map.get(WORKER.name()));
- Assert.assertEquals(masterOfferingId, map.get(MASTER.name()));
+ Assert.assertEquals(controlOfferingId, map.get(CONTROL.name()));
+ }
+
+ @Test
+ public void testNodeOfferingMapNullMap() {
+ cmd.nodeTypeOfferingMap = null;
+ cmd.serviceOfferingId = controlOfferingId;
+ Map<String, Long> map = cmd.getNodeTypeOfferingMap();
+ Assert.assertNotNull(map);
+ Assert.assertEquals(2, map.size());
+ Assert.assertTrue(map.containsKey(WORKER.name()) && map.containsKey(CONTROL.name()));
+ Assert.assertEquals(controlOfferingId, map.get(WORKER.name()));
+ Assert.assertEquals(controlOfferingId, map.get(CONTROL.name()));
+ }
+
+ @Test
+ public void testNodeOfferingMapEtcdNodes() {
+ cmd.nodeTypeOfferingMap = new HashMap<>();
+ Map<String, String> firstMap = createMapEntry(ETCD, etcdNodesOfferingId);
+ cmd.nodeTypeOfferingMap.put("map1", firstMap);
+ cmd.etcdNodes = 2L;
+ cmd.serviceOfferingId = controlOfferingId;
+ Map<String, Long> map = cmd.getNodeTypeOfferingMap();
+ Assert.assertNotNull(map);
+ Assert.assertEquals(3, map.size());
+ Assert.assertTrue(map.containsKey(WORKER.name()) && map.containsKey(CONTROL.name()) && map.containsKey(ETCD.name()));
+ Assert.assertEquals(controlOfferingId, map.get(WORKER.name()));
+ Assert.assertEquals(controlOfferingId, map.get(CONTROL.name()));
+ Assert.assertEquals(etcdOfferingId, map.get(ETCD.name()));
}
@Test(expected = InvalidParameterValueException.class)
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 6098bec5346..2498d5688dc 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -451,9 +451,13 @@
"label.cisco.nexus1000v.password": "Nexus 1000v password",
"label.cisco.nexus1000v.username": "Nexus 1000v username",
"label.cks.cluster.autoscalingenabled": "Enable auto scaling on this cluster",
+"label.cks.cluster.control.nodes.offeringid": "Service Offering for Control Nodes",
+"label.cks.cluster.etcd.nodes": "Etcd Nodes",
+"label.cks.cluster.etcd.nodes.offeringid": "Service Offering for etcd Nodes",
"label.cks.cluster.maxsize": "Maximum cluster size (Worker nodes)",
"label.cks.cluster.minsize": "Minimum cluster size (Worker nodes)",
"label.cks.cluster.size": "Cluster size (Worker nodes)",
+"label.cks.cluster.worker.nodes.offeringid": "Service Offering for Worker Nodes",
"label.cleanup": "Clean up",
"label.clear": "Clear",
"label.clear.list": "Clear list",
diff --git a/ui/src/views/compute/CreateKubernetesCluster.vue b/ui/src/views/compute/CreateKubernetesCluster.vue
index ca1e424cca4..214b3022482 100644
--- a/ui/src/views/compute/CreateKubernetesCluster.vue
+++ b/ui/src/views/compute/CreateKubernetesCluster.vue
@@ -180,6 +180,81 @@
</a-select-option>
</a-select>
</a-form-item>
+
+ <!-- Advanced configurations -->
+ <a-form-item name="advancedmode" ref="advancedmode">
+ <template #label>
+ <tooltip-label :title="$t('label.isadvanced')" />
+ </template>
+ <a-switch v-model:checked="form.advancedmode" />
+ </a-form-item>
+ <a-form-item v-if="form.advancedmode" name="controlofferingid" ref="controlofferingid">
+ <template #label>
+ <tooltip-label :title="$t('label.cks.cluster.control.nodes.offeringid')" :tooltip="$t('label.cks.cluster.control.nodes.offeringid')"/>
+ </template>
+ <a-select
+ id="control-offering-selection"
+ v-model:value="form.controlofferingid"
+ showSearch
+ optionFilterProp="label"
+ :filterOption="(input, option) => {
+ return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+ }"
+ :loading="serviceOfferingLoading"
+ :placeholder="$t('label.cks.cluster.control.nodes.offeringid')">
+ <a-select-option v-for="(opt, optIndex) in serviceOfferings" :key="optIndex" :label="opt.name || opt.description">
+ {{ opt.name || opt.description }}
+ </a-select-option>
+ </a-select>
+ </a-form-item>
+ <a-form-item v-if="form.advancedmode" name="workerofferingid" ref="workerofferingid">
+ <template #label>
+ <tooltip-label :title="$t('label.cks.cluster.worker.nodes.offeringid')" :tooltip="$t('label.cks.cluster.worker.nodes.offeringid')"/>
+ </template>
+ <a-select
+ id="worker-offering-selection"
+ v-model:value="form.workerofferingid"
+ showSearch
+ optionFilterProp="label"
+ :filterOption="(input, option) => {
+ return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+ }"
+ :loading="serviceOfferingLoading"
+ :placeholder="$t('label.cks.cluster.worker.nodes.offeringid')">
+ <a-select-option v-for="(opt, optIndex) in serviceOfferings" :key="optIndex" :label="opt.name || opt.description">
+ {{ opt.name || opt.description }}
+ </a-select-option>
+ </a-select>
+ </a-form-item>
+ <a-form-item v-if="form.advancedmode" name="etcdnodes" ref="etcdnodes">
+ <template #label>
+ <tooltip-label :title="$t('label.cks.cluster.etcd.nodes')" :tooltip="apiParams.controlnodes.description"/>
+ </template>
+ <a-input
+ v-model:value="form.etcdnodes"
+ :placeholder="apiParams.controlnodes.description"/>
+ </a-form-item>
+ <a-form-item v-if="form.advancedmode && form.etcdnodes && form.etcdnodes > 0" name="etcdofferingid" ref="etcdofferingid">
+ <template #label>
+ <tooltip-label :title="$t('label.cks.cluster.etcd.nodes.offeringid')" :tooltip="$t('label.cks.cluster.etcd.nodes.offeringid')"/>
+ </template>
+ <a-select
+ id="etcd-offering-selection"
+ v-model:value="form.etcdofferingid"
+ showSearch
+ optionFilterProp="label"
+ :filterOption="(input, option) => {
+ return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+ }"
+ :loading="serviceOfferingLoading"
+ :placeholder="$t('label.cks.cluster.etcd.nodes.offeringid')">
+ <a-select-option v-for="(opt, optIndex) in serviceOfferings" :key="optIndex" :label="opt.name || opt.description">
+ {{ opt.name || opt.description }}
+ </a-select-option>
+ </a-select>
+ </a-form-item>
+
+ <!-- Experimentation Features -->
<div v-if="$store.getters.features.kubernetesclusterexperimentalfeaturesenabled">
<a-form-item name="privateregistry" ref="privateregistry" :label="$t('label.private.registry')">
<template #label>
@@ -401,6 +476,9 @@ export default {
this.serviceOfferingLoading = false
if (this.arrayHasItems(this.serviceOfferings)) {
this.form.serviceofferingid = 0
+ this.form.controlofferingid = undefined
+ this.form.workerofferingid = undefined
+ this.form.etcdofferingid = undefined
}
})
},
@@ -461,6 +539,25 @@ export default {
size: values.size,
clustertype: 'CloudManaged'
}
+ var advancedOfferings = 0
+ if (this.isValidValueForKey(values, 'advancedmode') && values.advancedmode && this.isValidValueForKey(values, 'controlofferingid') && this.arrayHasItems(this.serviceOfferings) && this.serviceOfferings[values.controlofferingid].id != null) {
+ params['nodeofferings[' + advancedOfferings + '].node'] = 'control'
+ params['nodeofferings[' + advancedOfferings + '].offering'] = this.serviceOfferings[values.controlofferingid].id
+ advancedOfferings++
+ }
+ if (this.isValidValueForKey(values, 'advancedmode') && values.advancedmode && this.isValidValueForKey(values, 'workerofferingid') && this.arrayHasItems(this.serviceOfferings) && this.serviceOfferings[values.workerofferingid].id != null) {
+ params['nodeofferings[' + advancedOfferings + '].node'] = 'worker'
+ params['nodeofferings[' + advancedOfferings + '].offering'] = this.serviceOfferings[values.workerofferingid].id
+ advancedOfferings++
+ }
+ if (this.isValidValueForKey(values, 'advancedmode') && values.advancedmode && this.isValidValueForKey(values, 'etcdnodes') && values.etcdnodes > 0) {
+ params.etcdnodes = values.etcdnodes
+ if (this.isValidValueForKey(values, 'etcdofferingid') && this.arrayHasItems(this.serviceOfferings) && this.serviceOfferings[values.etcdofferingid].id != null) {
+ params['nodeofferings[' + advancedOfferings + '].node'] = 'etcd'
+ params['nodeofferings[' + advancedOfferings + '].offering'] = this.serviceOfferings[values.etcdofferingid].id
+ advancedOfferings++
+ }
+ }
if (this.isValidValueForKey(values, 'noderootdisksize') && values.noderootdisksize > 0) {
params.noderootdisksize = values.noderootdisksize
}