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

Reply via email to