imgStores = _imgStoreDao.findRegionImageStores();
@@ -3537,6 +3540,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
capabilities.put("allowUserExpungeRecoverVM", allowUserExpungeRecoverVM);
capabilities.put("allowUserExpungeRecoverVolume", allowUserExpungeRecoverVolume);
capabilities.put("allowUserViewAllDomainAccounts", allowUserViewAllDomainAccounts);
+ capabilities.put("kubernetesServiceEnabled", kubernetesServiceEnabled);
+ capabilities.put("kubernetesClusterExperimentalFeaturesEnabled", kubernetesClusterExperimentalFeaturesEnabled);
if (apiLimitEnabled) {
capabilities.put("apiLimitInterval", apiLimitInterval);
capabilities.put("apiLimitMax", apiLimitMax);
diff --git a/test/integration/smoke/test_kubernetes_clusters.py b/test/integration/smoke/test_kubernetes_clusters.py
new file mode 100644
index 00000000000..021a7490ff2
--- /dev/null
+++ b/test/integration/smoke/test_kubernetes_clusters.py
@@ -0,0 +1,729 @@
+# 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.
+""" Tests for Kubernetes supported version """
+
+#Import Local Modules
+from marvin.cloudstackTestCase import cloudstackTestCase, unittest
+from marvin.cloudstackAPI import (listInfrastructure,
+ listKubernetesSupportedVersions,
+ addKubernetesSupportedVersion,
+ deleteKubernetesSupportedVersion,
+ createKubernetesCluster,
+ stopKubernetesCluster,
+ deleteKubernetesCluster,
+ upgradeKubernetesCluster,
+ scaleKubernetesCluster)
+from marvin.cloudstackException import CloudstackAPIException
+from marvin.codes import FAILED
+from marvin.lib.base import (Template,
+ ServiceOffering,
+ Configurations)
+from marvin.lib.utils import (cleanup_resources,
+ random_gen)
+from marvin.lib.common import (get_zone)
+from marvin.sshClient import SshClient
+from nose.plugins.attrib import attr
+
+import time
+
+_multiprocess_shared_ = True
+
+class TestKubernetesCluster(cloudstackTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.testClient = super(TestKubernetesCluster, cls).getClsTestClient()
+ cls.apiclient = cls.testClient.getApiClient()
+ cls.services = cls.testClient.getParsedTestDataConfig()
+ cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+ cls.hypervisor = cls.testClient.getHypervisorInfo()
+ cls.mgtSvrDetails = cls.config.__dict__["mgtSvr"][0].__dict__
+ cls.cks_template_name_key = "cloud.kubernetes.cluster.template.name." + cls.hypervisor.lower()
+
+ cls.setup_failed = False
+
+ cls.initial_configuration_cks_enabled = Configurations.list(cls.apiclient,
+ name="cloud.kubernetes.service.enabled")[0].value
+ if cls.initial_configuration_cks_enabled not in ["true", True]:
+ cls.debug("Enabling CloudStack Kubernetes Service plugin and restarting management server")
+ Configurations.update(cls.apiclient,
+ "cloud.kubernetes.service.enabled",
+ "true")
+ cls.restartServer()
+
+ cls.cks_template = None
+ cls.initial_configuration_cks_template_name = None
+ cls.cks_service_offering = None
+
+ cls.kubernetes_version_ids = []
+ if cls.setup_failed == False:
+ try:
+ cls.kuberetes_version_1 = cls.addKubernetesSupportedVersion('1.14.9', 'http://staging.yadav.xyz/cks/binaries-iso/setup-1.14.9.iso')
+ cls.kubernetes_version_ids.append(cls.kuberetes_version_1.id)
+ except Exception as e:
+ cls.setup_failed = True
+ cls.debug("Failed to get Kubernetes version ISO in ready state, http://staging.yadav.xyz/cks/binaries-iso/setup-1.14.9.iso, %s" % e)
+ if cls.setup_failed == False:
+ try:
+ cls.kuberetes_version_2 = cls.addKubernetesSupportedVersion('1.15.0', 'http://staging.yadav.xyz/cks/binaries-iso/setup-1.15.0.iso')
+ cls.kubernetes_version_ids.append(cls.kuberetes_version_2.id)
+ except Exception as e:
+ cls.setup_failed = True
+ cls.debug("Failed to get Kubernetes version ISO in ready state, http://staging.yadav.xyz/cks/binaries-iso/setup-1.15.0.iso, %s" % e)
+ if cls.setup_failed == False:
+ try:
+ cls.kuberetes_version_3 = cls.addKubernetesSupportedVersion('1.16.0', 'http://staging.yadav.xyz/cks/binaries-iso/setup-1.16.0.iso')
+ cls.kubernetes_version_ids.append(cls.kuberetes_version_3.id)
+ except Exception as e:
+ cls.setup_failed = True
+ cls.debug("Failed to get Kubernetes version ISO in ready state, http://staging.yadav.xyz/cks/binaries-iso/setup-1.16.0.is, %s" % e)
+ if cls.setup_failed == False:
+ try:
+ cls.kuberetes_version_4 = cls.addKubernetesSupportedVersion('1.16.3', 'http://staging.yadav.xyz/cks/binaries-iso/setup-1.16.3.iso')
+ cls.kubernetes_version_ids.append(cls.kuberetes_version_4.id)
+ except Exception as e:
+ cls.setup_failed = True
+ cls.debug("Failed to get Kubernetes version ISO in ready state, http://staging.yadav.xyz/cks/binaries-iso/setup-1.16.3.is, %s" % e)
+
+ cks_template_data = {
+ "name": "Kubernetes-Service-Template",
+ "displaytext": "Kubernetes-Service-Template",
+ "format": "qcow2",
+ "hypervisor": "kvm",
+ "ostype": "CoreOS",
+ "url": "http://staging.yadav.xyz/cks/templates/coreos_production_cloudstack_image-kvm.qcow2.bz2",
+ "ispublic": "True",
+ "isextractable": "True"
+ }
+ # "http://dl.openvm.eu/cloudstack/coreos/x86_64/coreos_production_cloudstack_image-kvm.qcow2.bz2"
+ cks_template_data_details = []
+ if cls.hypervisor.lower() == "vmware":
+ cks_template_data["url"] = "http://staging.yadav.xyz/cks/templates/coreos_production_cloudstack_image-vmware.ova" # "http://dl.openvm.eu/cloudstack/coreos/x86_64/coreos_production_cloudstack_image-vmware.ova"
+ cks_template_data["format"] = "OVA"
+ cks_template_data_details = [{"keyboard":"us","nicAdapter":"Vmxnet3","rootDiskController":"pvscsi"}]
+ elif cls.hypervisor.lower() == "xenserver":
+ cks_template_data["url"] = "http://staging.yadav.xyz/cks/templates/coreos_production_cloudstack_image-xen.vhd.bz2" # "http://dl.openvm.eu/cloudstack/coreos/x86_64/coreos_production_cloudstack_image-xen.vhd.bz2"
+ cks_template_data["format"] = "VHD"
+ elif cls.hypervisor.lower() == "kvm":
+ cks_template_data["requireshvm"] = "True"
+ if cls.setup_failed == False:
+ cls.cks_template = Template.register(
+ cls.apiclient,
+ cks_template_data,
+ zoneid=cls.zone.id,
+ hypervisor=cls.hypervisor,
+ details=cks_template_data_details
+ )
+ cls.debug("Waiting for CKS template with ID %s to be ready" % cls.cks_template.id)
+ try:
+ cls.waitForTemplateReadyState(cls.cks_template.id)
+ except Exception as e:
+ cls.setup_failed = True
+ cls.debug("Failed to get CKS template in ready state, {}, {}".format(cks_template_data["url"], e))
+
+ cls.initial_configuration_cks_template_name = Configurations.list(cls.apiclient,
+ name=cls.cks_template_name_key)[0].value
+ Configurations.update(cls.apiclient,
+ cls.cks_template_name_key,
+ cls.cks_template.name)
+
+ cks_offering_data = {
+ "name": "CKS-Instance",
+ "displaytext": "CKS Instance",
+ "cpunumber": 2,
+ "cpuspeed": 1000,
+ "memory": 2048,
+ }
+ cks_offering_data["name"] = cks_offering_data["name"] + '-' + random_gen()
+ if cls.setup_failed == False:
+ cls.cks_service_offering = ServiceOffering.create(
+ cls.apiclient,
+ cks_offering_data
+ )
+
+ cls._cleanup = []
+ if cls.cks_template != None:
+ cls._cleanup.append(cls.cks_template)
+ if cls.cks_service_offering != None:
+ cls._cleanup.append(cls.cks_service_offering)
+ return
+
+ @classmethod
+ def tearDownClass(cls):
+ version_delete_failed = False
+ # Delete added Kubernetes supported version
+ for version_id in cls.kubernetes_version_ids:
+ try:
+ cls.deleteKubernetesSupportedVersion(version_id)
+ except Exception as e:
+ version_delete_failed = True
+ cls.debug("Error: Exception during cleanup for added Kubernetes supported versions: %s" % e)
+ try:
+ # Restore original CKS template
+ if cls.initial_configuration_cks_template_name != None:
+ Configurations.update(cls.apiclient,
+ cls.cks_template_name_key,
+ cls.initial_configuration_cks_template_name)
+ # Delete created CKS template
+ if cls.setup_failed == False and cls.cks_template != None:
+ cls.cks_template.delete(cls.apiclient,
+ cls.zone.id)
+ # Restore CKS enabled
+ if cls.initial_configuration_cks_enabled not in ["true", True]:
+ cls.debug("Restoring Kubernetes Service enabled value")
+ Configurations.update(cls.apiclient,
+ "cloud.kubernetes.service.enabled",
+ "false")
+ cls.restartServer()
+
+ cleanup_resources(cls.apiclient, cls._cleanup)
+ except Exception as e:
+ raise Exception("Warning: Exception during cleanup : %s" % e)
+ if version_delete_failed == True:
+ raise Exception("Warning: Exception during cleanup, unable to delete Kubernetes supported versions")
+ return
+
+ @classmethod
+ def restartServer(cls):
+ """Restart management server"""
+
+ cls.debug("Restarting management server")
+ sshClient = SshClient(
+ cls.mgtSvrDetails["mgtSvrIp"],
+ 22,
+ cls.mgtSvrDetails["user"],
+ cls.mgtSvrDetails["passwd"]
+ )
+ command = "service cloudstack-management stop"
+ sshClient.execute(command)
+
+ command = "service cloudstack-management start"
+ sshClient.execute(command)
+
+ #Waits for management to come up in 5 mins, when it's up it will continue
+ timeout = time.time() + 300
+ while time.time() < timeout:
+ if cls.isManagementUp() is True: return
+ time.sleep(5)
+ cls.setup_failed = True
+ cls.debug("Management server did not come up, failing")
+ return
+
+ @classmethod
+ def isManagementUp(cls):
+ try:
+ cls.apiclient.listInfrastructure(listInfrastructure.listInfrastructureCmd())
+ return True
+ except Exception:
+ return False
+
+ @classmethod
+ def waitForTemplateReadyState(cls, template_id, retries=30, interval=30):
+ """Check if template download will finish"""
+ while retries > -1:
+ time.sleep(interval)
+ template_response = Template.list(
+ cls.apiclient,
+ id=template_id,
+ zoneid=cls.zone.id,
+ templatefilter='self'
+ )
+
+ if isinstance(template_response, list):
+ template = template_response[0]
+ if not hasattr(template, 'status') or not template or not template.status:
+ retries = retries - 1
+ continue
+ if 'Failed' == template.status:
+ raise Exception("Failed to download template: status - %s" % template.status)
+ elif template.status == 'Download Complete' and template.isready:
+ return
+ retries = retries - 1
+ raise Exception("Template download timed out")
+
+ @classmethod
+ def waitForKubernetesSupportedVersionIsoReadyState(cls, version_id, retries=20, interval=30):
+ """Check if Kubernetes supported version ISO is in Ready state"""
+
+ while retries > -1:
+ time.sleep(interval)
+ list_versions_response = cls.listKubernetesSupportedVersion(version_id)
+ if not hasattr(list_versions_response, 'isostate') or not list_versions_response or not list_versions_response.isostate:
+ retries = retries - 1
+ continue
+ if 'Creating' == list_versions_response.isostate:
+ retries = retries - 1
+ elif 'Ready' == list_versions_response.isostate:
+ return
+ elif 'Failed' == list_versions_response.isostate:
+ raise Exception( "Failed to download template: status - %s" % template.status)
+ else:
+ raise Exception(
+ "Failed to download Kubernetes supported version ISO: status - %s" %
+ list_versions_response.isostate)
+ raise Exception("Kubernetes supported version Ready state timed out")
+
+ @classmethod
+ def listKubernetesSupportedVersion(cls, version_id):
+ listKubernetesSupportedVersionsCmd = listKubernetesSupportedVersions.listKubernetesSupportedVersionsCmd()
+ listKubernetesSupportedVersionsCmd.id = version_id
+ versionResponse = cls.apiclient.listKubernetesSupportedVersions(listKubernetesSupportedVersionsCmd)
+ return versionResponse[0]
+
+ @classmethod
+ def addKubernetesSupportedVersion(cls, semantic_version, iso_url):
+ addKubernetesSupportedVersionCmd = addKubernetesSupportedVersion.addKubernetesSupportedVersionCmd()
+ addKubernetesSupportedVersionCmd.semanticversion = semantic_version
+ addKubernetesSupportedVersionCmd.name = 'v' + semantic_version + '-' + random_gen()
+ addKubernetesSupportedVersionCmd.url = iso_url
+ addKubernetesSupportedVersionCmd.mincpunumber = 2
+ addKubernetesSupportedVersionCmd.minmemory = 2048
+ kubernetes_version = cls.apiclient.addKubernetesSupportedVersion(addKubernetesSupportedVersionCmd)
+ cls.debug("Waiting for Kubernetes version with ID %s to be ready" % kubernetes_version.id)
+ cls.waitForKubernetesSupportedVersionIsoReadyState(kubernetes_version.id)
+ kubernetes_version = cls.listKubernetesSupportedVersion(kubernetes_version.id)
+ return kubernetes_version
+
+ @classmethod
+ def deleteKubernetesSupportedVersion(cls, version_id):
+ deleteKubernetesSupportedVersionCmd = deleteKubernetesSupportedVersion.deleteKubernetesSupportedVersionCmd()
+ deleteKubernetesSupportedVersionCmd.id = version_id
+ deleteKubernetesSupportedVersionCmd.deleteiso = True
+ cls.apiclient.deleteKubernetesSupportedVersion(deleteKubernetesSupportedVersionCmd)
+
+ def setUp(self):
+ self.services = self.testClient.getParsedTestDataConfig()
+ self.apiclient = self.testClient.getApiClient()
+ self.dbclient = self.testClient.getDbConnection()
+ self.cleanup = []
+ return
+
+ def tearDown(self):
+ try:
+ #Clean up, terminate the created templates
+ cleanup_resources(self.apiclient, self.cleanup)
+
+ except Exception as e:
+ raise Exception("Warning: Exception during cleanup : %s" % e)
+ return
+
+ @attr(tags=["advanced", "smoke"], required_hardware="true")
+ def test_01_deploy_kubernetes_cluster(self):
+ """Test to deploy a new Kubernetes cluster
+
+ # Validate the following:
+ # 1. createKubernetesCluster should return valid info for new cluster
+ # 2. The Cloud Database contains the valid information
+ # 3. stopKubernetesCluster should stop the cluster
+ """
+ if self.hypervisor.lower() not in ["kvm", "vmware", "xenserver"]:
+ self.skipTest("CKS not supported for hypervisor: %s" % self.hypervisor.lower())
+ if self.setup_failed == True:
+ self.skipTest("Setup incomplete")
+ name = 'testcluster-' + random_gen()
+ self.debug("Creating for Kubernetes cluster with name %s" % name)
+
+ cluster_response = self.createKubernetesCluster(name, self.kuberetes_version_2.id)
+
+ self.verifyKubernetesCluster(cluster_response, name, self.kuberetes_version_2.id)
+
+ self.debug("Kubernetes cluster with ID: %s successfully deployed, now stopping it" % cluster_response.id)
+
+ self.stopAndVerifyKubernetesCluster(cluster_response.id)
+
+ self.debug("Kubernetes cluster with ID: %s successfully stopped, now deleting it" % cluster_response.id)
+
+ self.deleteAndVerifyKubernetesCluster(cluster_response.id)
+
+ self.debug("Kubernetes cluster with ID: %s successfully deleted" % cluster_response.id)
+
+ return
+
+ @attr(tags=["advanced", "smoke"], required_hardware="true")
+ def test_02_deploy_kubernetes_ha_cluster(self):
+ """Test to deploy a new Kubernetes cluster
+
+ # Validate the following:
+ # 1. createKubernetesCluster should return valid info for new cluster
+ # 2. The Cloud Database contains the valid information
+ """
+ if self.hypervisor.lower() not in ["kvm", "vmware", "xenserver"]:
+ self.skipTest("CKS not supported for hypervisor: %s" % self.hypervisor.lower())
+ if self.setup_failed == True:
+ self.skipTest("Setup incomplete")
+ name = 'testcluster-' + random_gen()
+ self.debug("Creating for Kubernetes cluster with name %s" % name)
+
+ cluster_response = self.createKubernetesCluster(name, self.kuberetes_version_3.id, 1, 2)
+
+ self.verifyKubernetesCluster(cluster_response, name, self.kuberetes_version_3.id, 1, 2)
+
+ self.debug("Kubernetes cluster with ID: %s successfully deployed, now deleting it" % cluster_response.id)
+
+ self.deleteAndVerifyKubernetesCluster(cluster_response.id)
+
+ self.debug("Kubernetes cluster with ID: %s successfully deleted" % cluster_response.id)
+
+ return
+
+ @attr(tags=["advanced", "smoke"], required_hardware="true")
+ def test_03_deploy_invalid_kubernetes_ha_cluster(self):
+ """Test to deploy a new Kubernetes cluster
+
+ # Validate the following:
+ # 1. createKubernetesCluster should return valid info for new cluster
+ # 2. The Cloud Database contains the valid information
+ """
+ if self.hypervisor.lower() not in ["kvm", "vmware", "xenserver"]:
+ self.skipTest("CKS not supported for hypervisor: %s" % self.hypervisor.lower())
+ if self.setup_failed == True:
+ self.skipTest("Setup incomplete")
+ name = 'testcluster-' + random_gen()
+ self.debug("Creating for Kubernetes cluster with name %s" % name)
+
+ try:
+ cluster_response = self.createKubernetesCluster(name, self.kuberetes_version_2.id, 1, 2)
+ self.debug("Invslid CKS Kubernetes HA cluster deployed with ID: %s. Deleting it and failing test." % cluster_response.id)
+ self.deleteKubernetesCluster(cluster_response.id)
+ self.fail("HA Kubernetes cluster deployed with Kubernetes supported version below version 1.16.0. Must be an error.")
+ except CloudstackAPIException as e:
+ self.debug("HA Kubernetes cluster with invalid Kubernetes supported version check successful, API failure: %s" % e)
+
+ return
+
+ @attr(tags=["advanced", "smoke"], required_hardware="true")
+ def test_04_deploy_and_upgrade_kubernetes_cluster(self):
+ """Test to deploy a new Kubernetes cluster and upgrade it to newer version
+
+ # Validate the following:
+ # 1. createKubernetesCluster should return valid info for new cluster
+ # 2. The Cloud Database contains the valid information
+ # 3. upgradeKubernetesCluster should return valid info for the cluster
+ """
+ if self.hypervisor.lower() not in ["kvm", "vmware", "xenserver"]:
+ self.skipTest("CKS not supported for hypervisor: %s" % self.hypervisor.lower())
+ if self.setup_failed == True:
+ self.skipTest("Setup incomplete")
+ name = 'testcluster-' + random_gen()
+ self.debug("Creating for Kubernetes cluster with name %s" % name)
+
+ cluster_response = self.createKubernetesCluster(name, self.kuberetes_version_2.id)
+
+ self.verifyKubernetesCluster(cluster_response, name, self.kuberetes_version_2.id)
+
+ self.debug("Kubernetes cluster with ID: %s successfully deployed, now upgrading it" % cluster_response.id)
+
+ try:
+ cluster_response = self.upgradeKubernetesCluster(cluster_response.id, self.kuberetes_version_3.id)
+ except Exception as e:
+ self.deleteKubernetesCluster(cluster_response.id)
+ self.fail("Failed to upgrade Kubernetes cluster due to: %s" % e)
+
+ self.verifyKubernetesClusterUpgrade(cluster_response, self.kuberetes_version_3.id)
+
+ self.debug("Kubernetes cluster with ID: %s successfully upgraded, now deleting it" % cluster_response.id)
+
+ self.deleteAndVerifyKubernetesCluster(cluster_response.id)
+
+ self.debug("Kubernetes cluster with ID: %s successfully deleted" % cluster_response.id)
+
+ return
+
+
+ @attr(tags=["advanced", "smoke"], required_hardware="true")
+ def test_05_deploy_and_upgrade_kubernetes_ha_cluster(self):
+ """Test to deploy a new HA Kubernetes cluster and upgrade it to newer version
+
+ # Validate the following:
+ # 1. createKubernetesCluster should return valid info for new cluster
+ # 2. The Cloud Database contains the valid information
+ # 3. upgradeKubernetesCluster should return valid info for the cluster
+ """
+ if self.hypervisor.lower() not in ["kvm", "vmware", "xenserver"]:
+ self.skipTest("CKS not supported for hypervisor: %s" % self.hypervisor.lower())
+ if self.setup_failed == True:
+ self.skipTest("Setup incomplete")
+ name = 'testcluster-' + random_gen()
+ self.debug("Creating for Kubernetes cluster with name %s" % name)
+
+ cluster_response = self.createKubernetesCluster(name, self.kuberetes_version_3.id, 1, 2)
+
+ self.verifyKubernetesCluster(cluster_response, name, self.kuberetes_version_3.id, 1, 2)
+
+ self.debug("Kubernetes cluster with ID: %s successfully deployed, now upgrading it" % cluster_response.id)
+
+ try:
+ cluster_response = self.upgradeKubernetesCluster(cluster_response.id, self.kuberetes_version_4.id)
+ except Exception as e:
+ self.deleteKubernetesCluster(cluster_response.id)
+ self.fail("Failed to upgrade Kubernetes HA cluster due to: %s" % e)
+
+ self.verifyKubernetesClusterUpgrade(cluster_response, self.kuberetes_version_4.id)
+
+ self.debug("Kubernetes cluster with ID: %s successfully upgraded, now deleting it" % cluster_response.id)
+
+ self.deleteAndVerifyKubernetesCluster(cluster_response.id)
+
+ self.debug("Kubernetes cluster with ID: %s successfully deleted" % cluster_response.id)
+
+ return
+
+ @attr(tags=["advanced", "smoke"], required_hardware="true")
+ def test_06_deploy_and_invalid_upgrade_kubernetes_cluster(self):
+ """Test to deploy a new Kubernetes cluster and check for failure while tying to upgrade it to a lower version
+
+ # Validate the following:
+ # 1. createKubernetesCluster should return valid info for new cluster
+ # 2. The Cloud Database contains the valid information
+ # 3. upgradeKubernetesCluster should fail
+ """
+ if self.hypervisor.lower() not in ["kvm", "vmware", "xenserver"]:
+ self.skipTest("CKS not supported for hypervisor: %s" % self.hypervisor.lower())
+ if self.setup_failed == True:
+ self.skipTest("Setup incomplete")
+ name = 'testcluster-' + random_gen()
+ self.debug("Creating for Kubernetes cluster with name %s" % name)
+
+ cluster_response = self.createKubernetesCluster(name, self.kuberetes_version_2.id)
+
+ self.verifyKubernetesCluster(cluster_response, name, self.kuberetes_version_2.id)
+
+ self.debug("Kubernetes cluster with ID: %s successfully deployed, now scaling it" % cluster_response.id)
+
+ try:
+ cluster_response = self.upgradeKubernetesCluster(cluster_response.id, self.kuberetes_version_1.id)
+ self.debug("Invalid CKS Kubernetes HA cluster deployed with ID: %s. Deleting it and failing test." % kuberetes_version_1.id)
+ self.deleteKubernetesCluster(cluster_response.id)
+ self.fail("Kubernetes cluster upgraded to a lower Kubernetes supported version. Must be an error.")
+ except Exception as e:
+ self.debug("Upgrading Kubernetes cluster with invalid Kubernetes supported version check successful, API failure: %s" % e)
+
+ self.debug("Deleting Kubernetes cluster with ID: %s" % cluster_response.id)
+
+ self.deleteAndVerifyKubernetesCluster(cluster_response.id)
+
+ self.debug("Kubernetes cluster with ID: %s successfully deleted" % cluster_response.id)
+
+ return
+
+ @attr(tags=["advanced", "smoke"], required_hardware="true")
+ def test_07_deploy_and_scale_kubernetes_cluster(self):
+ """Test to deploy a new Kubernetes cluster and check for failure while tying to scale it
+
+ # Validate the following:
+ # 1. createKubernetesCluster should return valid info for new cluster
+ # 2. The Cloud Database contains the valid information
+ # 3. scaleKubernetesCluster should return valid info for the cluster when it is scaled up
+ # 4. scaleKubernetesCluster should return valid info for the cluster when it is scaled down
+ """
+ if self.hypervisor.lower() not in ["kvm", "vmware", "xenserver"]:
+ self.skipTest("CKS not supported for hypervisor: %s" % self.hypervisor.lower())
+ if self.setup_failed == True:
+ self.skipTest("Setup incomplete")
+ name = 'testcluster-' + random_gen()
+ self.debug("Creating for Kubernetes cluster with name %s" % name)
+
+ cluster_response = self.createKubernetesCluster(name, self.kuberetes_version_2.id)
+
+ self.verifyKubernetesCluster(cluster_response, name, self.kuberetes_version_2.id)
+
+ self.debug("Kubernetes cluster with ID: %s successfully deployed, now upscaling it" % cluster_response.id)
+
+ try:
+ cluster_response = self.scaleKubernetesCluster(cluster_response.id, 2)
+ except Exception as e:
+ self.deleteKubernetesCluster(cluster_response.id)
+ self.fail("Failed to upscale Kubernetes cluster due to: %s" % e)
+
+ self.verifyKubernetesClusterScale(cluster_response, 2)
+
+ self.debug("Kubernetes cluster with ID: %s successfully upscaled, now downscaling it" % cluster_response.id)
+
+ try:
+ cluster_response = self.scaleKubernetesCluster(cluster_response.id, 1)
+ except Exception as e:
+ self.deleteKubernetesCluster(cluster_response.id)
+ self.fail("Failed to downscale Kubernetes cluster due to: %s" % e)
+
+ self.verifyKubernetesClusterScale(cluster_response)
+
+ self.debug("Kubernetes cluster with ID: %s successfully downscaled, now deleting it" % cluster_response.id)
+
+ self.deleteAndVerifyKubernetesCluster(cluster_response.id)
+
+ self.debug("Kubernetes cluster with ID: %s successfully deleted" % cluster_response.id)
+
+ return
+
+ def listKubernetesCluster(self, cluster_id):
+ listKubernetesClustersCmd = listKubernetesClusters.listKubernetesClustersCmd()
+ listKubernetesClustersCmd.id = cluster_id
+ clusterResponse = self.apiclient.listKubernetesClusters(listKubernetesClustersCmd)
+ return clusterResponse[0]
+
+ def createKubernetesCluster(self, name, version_id, size=1, master_nodes=1):
+ createKubernetesClusterCmd = createKubernetesCluster.createKubernetesClusterCmd()
+ createKubernetesClusterCmd.name = name
+ createKubernetesClusterCmd.description = name + "-description"
+ createKubernetesClusterCmd.kubernetesversionid = version_id
+ createKubernetesClusterCmd.size = size
+ createKubernetesClusterCmd.masternodes = master_nodes
+ createKubernetesClusterCmd.serviceofferingid = self.cks_service_offering.id
+ createKubernetesClusterCmd.zoneid = self.zone.id
+ createKubernetesClusterCmd.noderootdisksize = 10
+ clusterResponse = self.apiclient.createKubernetesCluster(createKubernetesClusterCmd)
+ if not clusterResponse:
+ self.cleanup.append(clusterResponse)
+ return clusterResponse
+
+ def stopKubernetesCluster(self, cluster_id):
+ stopKubernetesClusterCmd = stopKubernetesCluster.stopKubernetesClusterCmd()
+ stopKubernetesClusterCmd.id = cluster_id
+ response = self.apiclient.stopKubernetesCluster(stopKubernetesClusterCmd)
+ return response
+
+ def deleteKubernetesCluster(self, cluster_id):
+ deleteKubernetesClusterCmd = deleteKubernetesCluster.deleteKubernetesClusterCmd()
+ deleteKubernetesClusterCmd.id = cluster_id
+ response = self.apiclient.deleteKubernetesCluster(deleteKubernetesClusterCmd)
+ return response
+
+ def upgradeKubernetesCluster(self, cluster_id, version_id):
+ upgradeKubernetesClusterCmd = upgradeKubernetesCluster.upgradeKubernetesClusterCmd()
+ upgradeKubernetesClusterCmd.id = cluster_id
+ upgradeKubernetesClusterCmd.kubernetesversionid = version_id
+ response = self.apiclient.upgradeKubernetesCluster(upgradeKubernetesClusterCmd)
+ return response
+
+ def scaleKubernetesCluster(self, cluster_id, size):
+ scaleKubernetesClusterCmd = scaleKubernetesCluster.scaleKubernetesClusterCmd()
+ scaleKubernetesClusterCmd.id = cluster_id
+ scaleKubernetesClusterCmd.size = size
+ response = self.apiclient.scaleKubernetesCluster(scaleKubernetesClusterCmd)
+ return response
+
+ def verifyKubernetesCluster(self, cluster_response, name, version_id, size=1, master_nodes=1):
+ """Check if Kubernetes cluster is valid"""
+
+ self.verifyKubernetesClusterState(cluster_response, 'Running')
+
+ self.assertEqual(
+ cluster_response.name,
+ name,
+ "Check KubernetesCluster name {}, {}".format(cluster_response.name, name)
+ )
+
+ self.verifyKubernetesClusterVersion(cluster_response, version_id)
+
+ self.assertEqual(
+ cluster_response.zoneid,
+ self.zone.id,
+ "Check KubernetesCluster zone {}, {}".format(cluster_response.zoneid, self.zone.id)
+ )
+
+ self.verifyKubernetesClusterSize(cluster_response, size, master_nodes)
+
+ db_cluster_name = self.dbclient.execute("select name from kubernetes_cluster where uuid = '%s';" % cluster_response.id)[0][0]
+
+ self.assertEqual(
+ str(db_cluster_name),
+ name,
+ "Check KubernetesCluster name in DB {}, {}".format(db_cluster_name, name)
+ )
+
+ def verifyKubernetesClusterState(self, cluster_response, state):
+ """Check if Kubernetes cluster state is Running"""
+
+ self.assertEqual(
+ cluster_response.state,
+ 'Running',
+ "Check KubernetesCluster state {}, {}".format(cluster_response.state, state)
+ )
+
+ def verifyKubernetesClusterVersion(self, cluster_response, version_id):
+ """Check if Kubernetes cluster node sizes are valid"""
+
+ self.assertEqual(
+ cluster_response.kubernetesversionid,
+ version_id,
+ "Check KubernetesCluster version {}, {}".format(cluster_response.kubernetesversionid, version_id)
+ )
+
+ def verifyKubernetesClusterSize(self, cluster_response, size=1, master_nodes=1):
+ """Check if Kubernetes cluster node sizes are valid"""
+
+ self.assertEqual(
+ cluster_response.size,
+ size,
+ "Check KubernetesCluster size {}, {}".format(cluster_response.size, size)
+ )
+
+ self.assertEqual(
+ cluster_response.masternodes,
+ master_nodes,
+ "Check KubernetesCluster master nodes {}, {}".format(cluster_response.masternodes, master_nodes)
+ )
+
+ def verifyKubernetesClusterUpgrade(self, cluster_response, version_id):
+ """Check if Kubernetes cluster state and version are valid after upgrade"""
+
+ self.verifyKubernetesClusterState(cluster_response, 'Running')
+ self.verifyKubernetesClusterVersion(cluster_response, version_id)
+
+ def verifyKubernetesClusterScale(self, cluster_response, size=1, master_nodes=1):
+ """Check if Kubernetes cluster state and node sizes are valid after upgrade"""
+
+ self.verifyKubernetesClusterState(cluster_response, 'Running')
+ self.verifyKubernetesClusterSize(cluster_response, size, master_nodes)
+
+ def stopAndVerifyKubernetesCluster(self, cluster_id):
+ """Stop Kubernetes cluster and check if it is really stopped"""
+
+ stop_response = self.stopKubernetesCluster(cluster_id)
+
+ self.assertEqual(
+ stop_response.success,
+ True,
+ "Check KubernetesCluster stop response {}, {}".format(stop_response.success, True)
+ )
+
+ db_cluster_state = self.dbclient.execute("select state from kubernetes_cluster where uuid = '%s';" % cluster_id)[0][0]
+
+ self.assertEqual(
+ db_cluster_state,
+ 'Stopped',
+ "KubernetesCluster not stopped in DB, {}".format(db_cluster_state)
+ )
+
+ def deleteAndVerifyKubernetesCluster(self, cluster_id):
+ """Delete Kubernetes cluster and check if it is really deleted"""
+
+ delete_response = self.deleteKubernetesCluster(cluster_id)
+
+ self.assertEqual(
+ delete_response.success,
+ True,
+ "Check KubernetesCluster delete response {}, {}".format(delete_response.success, True)
+ )
+
+ db_cluster_removed = self.dbclient.execute("select removed from kubernetes_cluster where uuid = '%s';" % cluster_id)[0][0]
+
+ self.assertNotEqual(
+ db_cluster_removed,
+ None,
+ "KubernetesCluster not removed in DB, {}".format(db_cluster_removed)
+ )
diff --git a/test/integration/smoke/test_kubernetes_supported_versions.py b/test/integration/smoke/test_kubernetes_supported_versions.py
new file mode 100644
index 00000000000..3d699e4b676
--- /dev/null
+++ b/test/integration/smoke/test_kubernetes_supported_versions.py
@@ -0,0 +1,278 @@
+# 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.
+""" Tests for Kubernetes supported version """
+
+#Import Local Modules
+from marvin.cloudstackTestCase import cloudstackTestCase, unittest
+from marvin.cloudstackAPI import (listInfrastructure,
+ listKubernetesSupportedVersions,
+ addKubernetesSupportedVersion,
+ deleteKubernetesSupportedVersion)
+from marvin.cloudstackException import CloudstackAPIException
+from marvin.codes import FAILED
+from marvin.lib.base import Configurations
+from marvin.lib.utils import (cleanup_resources,
+ random_gen)
+from marvin.lib.common import get_zone
+from marvin.sshClient import SshClient
+from nose.plugins.attrib import attr
+
+import time
+
+_multiprocess_shared_ = True
+
+class TestKubernetesSupportedVersion(cloudstackTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.testClient = super(TestKubernetesSupportedVersion, cls).getClsTestClient()
+ cls.apiclient = cls.testClient.getApiClient()
+ cls.services = cls.testClient.getParsedTestDataConfig()
+ cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+ cls.mgtSvrDetails = cls.config.__dict__["mgtSvr"][0].__dict__
+ cls.kubernetes_version_iso_url = 'http://staging.yadav.xyz/cks/binaries-iso/setup-1.16.3.iso'
+
+ cls.initial_configuration_cks_enabled = Configurations.list(cls.apiclient,
+ name="cloud.kubernetes.service.enabled")[0].value
+ if cls.initial_configuration_cks_enabled not in ["true", True]:
+ cls.debug("Enabling CloudStack Kubernetes Service plugin and restarting management server")
+ Configurations.update(cls.apiclient,
+ "cloud.kubernetes.service.enabled",
+ "true")
+ cls.restartServer()
+
+ cls._cleanup = []
+ return
+
+ @classmethod
+ def tearDownClass(cls):
+ try:
+ # Restore CKS enabled
+ if cls.initial_configuration_cks_enabled not in ["true", True]:
+ cls.debug("Restoring Kubernetes Service enabled value")
+ Configurations.update(cls.apiclient,
+ "cloud.kubernetes.service.enabled",
+ "false")
+ cls.restartServer()
+ cleanup_resources(cls.apiclient, cls._cleanup)
+ except Exception as e:
+ raise Exception("Warning: Exception during cleanup : %s" % e)
+ return
+
+ @classmethod
+ def restartServer(cls):
+ """Restart management server"""
+
+ cls.debug("Restarting management server")
+ sshClient = SshClient(
+ cls.mgtSvrDetails["mgtSvrIp"],
+ 22,
+ cls.mgtSvrDetails["user"],
+ cls.mgtSvrDetails["passwd"]
+ )
+ command = "service cloudstack-management stop"
+ sshClient.execute(command)
+
+ command = "service cloudstack-management start"
+ sshClient.execute(command)
+
+ #Waits for management to come up in 5 mins, when it's up it will continue
+ timeout = time.time() + 300
+ while time.time() < timeout:
+ if cls.isManagementUp() is True: return
+ time.sleep(5)
+ return cls.fail("Management server did not come up, failing")
+
+ @classmethod
+ def isManagementUp(cls):
+ try:
+ cls.apiclient.listInfrastructure(listInfrastructure.listInfrastructureCmd())
+ return True
+ except Exception:
+ return False
+
+ def setUp(self):
+ self.services = self.testClient.getParsedTestDataConfig()
+ self.apiclient = self.testClient.getApiClient()
+ self.dbclient = self.testClient.getDbConnection()
+ self.cleanup = []
+ return
+
+ def tearDown(self):
+ try:
+ #Clean up, terminate the created templates
+ cleanup_resources(self.apiclient, self.cleanup)
+
+ except Exception as e:
+ raise Exception("Warning: Exception during cleanup : %s" % e)
+ return
+
+ @attr(tags=["advanced", "smoke"], required_hardware="true")
+ def test_01_add_delete_kubernetes_supported_version(self):
+ """Test to add a new Kubernetes supported version
+
+ # Validate the following:
+ # 1. addKubernetesSupportedVersion should return valid info for new version
+ # 2. The Cloud Database contains the valid information when listKubernetesSupportedVersions is called
+ """
+
+ version = '1.16.3'
+ name = 'v' + version + '-' + random_gen()
+
+ self.debug("Adding Kubernetes supported version with name: %s" % name)
+
+ version_response = self.addKubernetesSupportedVersion(version, name, self.zone.id, self.kubernetes_version_iso_url)
+
+ list_versions_response = self.listKubernetesSupportedVersion(version_response.id)
+
+ self.assertEqual(
+ list_versions_response.name,
+ name,
+ "Check KubernetesSupportedVersion name {}, {}".format(list_versions_response.name, name)
+ )
+
+ self.assertEqual(
+ list_versions_response.semanticversion,
+ version,
+ "Check KubernetesSupportedVersion version {}, {}".format(list_versions_response.semanticversion, version)
+ )
+ self.assertEqual(
+ list_versions_response.zoneid,
+ self.zone.id,
+ "Check KubernetesSupportedVersion zone {}, {}".format(list_versions_response.zoneid, self.zone.id)
+ )
+
+ db_version_name = self.dbclient.execute("select name from kubernetes_supported_version where uuid = '%s';" % version_response.id)[0][0]
+
+ self.assertEqual(
+ str(db_version_name),
+ name,
+ "Check KubernetesSupportedVersion name in DB {}, {}".format(db_version_name, name)
+ )
+
+ self.debug("Added Kubernetes supported version with ID: %s. Waiting for its ISO to be Ready" % version_response.id)
+
+ self.waitForKubernetesSupportedVersionIsoReadyState(version_response.id)
+
+ self.debug("Deleting Kubernetes supported version with ID: %s" % version_response.id)
+
+ delete_response = self.deleteKubernetesSupportedVersion(version_response.id, True)
+
+ self.assertEqual(
+ delete_response.success,
+ True,
+ "Check KubernetesSupportedVersion deletion in DB {}, {}".format(delete_response.success, True)
+ )
+
+ db_version_removed = self.dbclient.execute("select removed from kubernetes_supported_version where uuid = '%s';" % version_response.id)[0][0]
+
+ self.assertNotEqual(
+ db_version_removed,
+ None,
+ "KubernetesSupportedVersion not removed in DB"
+ )
+
+ return
+
+ @attr(tags=["advanced", "smoke"], required_hardware="true")
+ def test_02_add_unsupported_kubernetes_supported_version(self):
+ """Test to trying to add a new unsupported Kubernetes supported version
+
+ # Validate the following:
+ # 1. API should return an error
+ """
+
+ version = '1.1.1'
+ name = 'v' + version + '-' + random_gen()
+ try:
+ version_response = self.addKubernetesSupportedVersion(version, name, self.zone.id, self.kubernetes_version_iso_url)
+ self.debug("Unsupported CKS Kubernetes supported added with ID: %s. Deleting it and failing test." % version_response.id)
+ self.waitForKubernetesSupportedVersionIsoReadyState(version_response.id)
+ self.deleteKubernetesSupportedVersion(version_response.id, True)
+ self.fail("Kubernetes supported version below version 1.11.0 been added. Must be an error.")
+ except CloudstackAPIException as e:
+ self.debug("Unsupported version error check successful, API failure: %s" % e)
+ return
+
+ @attr(tags=["advanced", "smoke"], required_hardware="true")
+ def test_03_add_invalid_kubernetes_supported_version(self):
+ """Test to trying to add a new unsupported Kubernetes supported version
+
+ # Validate the following:
+ # 1. API should return an error
+ """
+
+ version = 'invalid'
+ name = 'v' + version + '-' + random_gen()
+ try:
+ version_response = self.addKubernetesSupportedVersion(version, name, self.zone.id, self.kubernetes_version_iso_url)
+ self.debug("Invalid Kubernetes supported added with ID: %s. Deleting it and failing test." % version_response.id)
+ self.waitForKubernetesSupportedVersionIsoReadyState(version_response.id)
+ self.deleteKubernetesSupportedVersion(version_response.id, True)
+ self.fail("Invalid Kubernetes supported version has been added. Must be an error.")
+ except CloudstackAPIException as e:
+ self.debug("Unsupported version error check successful, API failure: %s" % e)
+ return
+
+ def addKubernetesSupportedVersion(self, version, name, zoneId, isoUrl):
+ addKubernetesSupportedVersionCmd = addKubernetesSupportedVersion.addKubernetesSupportedVersionCmd()
+ addKubernetesSupportedVersionCmd.semanticversion = version
+ addKubernetesSupportedVersionCmd.name = name
+ addKubernetesSupportedVersionCmd.zoneid = zoneId
+ addKubernetesSupportedVersionCmd.url = isoUrl
+ addKubernetesSupportedVersionCmd.mincpunumber = 2
+ addKubernetesSupportedVersionCmd.minmemory = 2048
+ versionResponse = self.apiclient.addKubernetesSupportedVersion(addKubernetesSupportedVersionCmd)
+ if not versionResponse:
+ self.cleanup.append(versionResponse)
+ return versionResponse
+
+ def listKubernetesSupportedVersion(self, versionId):
+ listKubernetesSupportedVersionsCmd = listKubernetesSupportedVersions.listKubernetesSupportedVersionsCmd()
+ listKubernetesSupportedVersionsCmd.id = versionId
+ versionResponse = self.apiclient.listKubernetesSupportedVersions(listKubernetesSupportedVersionsCmd)
+ return versionResponse[0]
+
+ def deleteKubernetesSupportedVersion(self, cmd):
+ response = self.apiclient.deleteKubernetesSupportedVersion(cmd)
+ return response
+
+ def deleteKubernetesSupportedVersion(self, versionId, deleteIso):
+ deleteKubernetesSupportedVersionCmd = deleteKubernetesSupportedVersion.deleteKubernetesSupportedVersionCmd()
+ deleteKubernetesSupportedVersionCmd.id = versionId
+ deleteKubernetesSupportedVersionCmd.deleteiso = deleteIso
+ response = self.apiclient.deleteKubernetesSupportedVersion(deleteKubernetesSupportedVersionCmd)
+ return response
+
+ def waitForKubernetesSupportedVersionIsoReadyState(self, version_id, retries=20, interval=30):
+ """Check if Kubernetes supported version ISO is in Ready state"""
+
+ while retries > -1:
+ time.sleep(interval)
+ list_versions_response = self.listKubernetesSupportedVersion(version_id)
+ if not hasattr(list_versions_response, 'isostate') or not list_versions_response or not list_versions_response.isostate:
+ retries = retries - 1
+ continue
+ if 'Creating' == list_versions_response.isostate:
+ retries = retries - 1
+ elif 'Ready' == list_versions_response.isostate:
+ return
+ else:
+ raise Exception(
+ "Failed to download Kubernetes supported version ISO: status - %s" %
+ list_versions_response.isostate)
+ raise Exception("Kubernetes supported version Ready state timed out")
diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py
index 39a41239c2a..068b6850021 100644
--- a/tools/apidoc/gen_toc.py
+++ b/tools/apidoc/gen_toc.py
@@ -191,7 +191,9 @@ known_categories = {
'Management': 'Management',
'Backup' : 'Backup and Recovery',
'Restore' : 'Backup and Recovery',
- 'UnmanagedInstance': 'Virtual Machine'
+ 'UnmanagedInstance': 'Virtual Machine',
+ 'KubernetesSupportedVersion': 'Kubernetes Service',
+ 'KubernetesCluster': 'Kubernetes Service'
}
diff --git a/ui/l10n/en.js b/ui/l10n/en.js
index fcb6f1eb9aa..36f6d6d42f4 100644
--- a/ui/l10n/en.js
+++ b/ui/l10n/en.js
@@ -92,6 +92,7 @@ var dictionary = {
"label.about":"About",
"label.about.app":"About CloudStack",
"label.accept.project.invitation":"Accept project invitation",
+"label.access":"Access",
"label.account":"Account",
"label.accounts":"Accounts",
"label.account.and.security.group":"Account, Security group",
@@ -359,6 +360,8 @@ var dictionary = {
"label.add.isolated.guest.network":"Add Isolated Guest Network",
"label.add.isolated.guest.network.with.sourcenat":"Add Isolated Guest Network with SourceNat",
"label.add.isolated.network":"Add Isolated Network",
+"label.add.kubernetes.cluster":"Add Kubernetes Cluster",
+"label.add.kubernetes.version":"Add Kubernetes Version",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account":"Add LDAP account",
"label.add.list.name":"ACL List Name",
@@ -372,6 +375,7 @@ var dictionary = {
"label.add.network.device":"Add Network Device",
"label.add.network.offering":"Add network offering",
"label.add.new.F5":"Add new F5",
+"label.add.new.iso":"Add new ISO",
"label.add.new.NetScaler":"Add new NetScaler",
"label.add.new.PA":"Add new Palo Alto",
"label.add.new.SRX":"Add new SRX",
@@ -450,6 +454,7 @@ var dictionary = {
"label.allocated":"Allocated",
"label.allocation.state":"Allocation State",
"label.allow":"Allow",
+"label.all.zones":"All zones",
"label.annotated.by":"Annotator",
"label.annotation":"Annotation",
"label.anti.affinity":"Anti-affinity",
@@ -556,6 +561,8 @@ var dictionary = {
"label.cloud.managed":"Cloud.com Managed",
"label.cluster":"Cluster",
"label.cluster.name":"Cluster Name",
+"label.cluster.size":"Cluster size",
+"label.cluster.size.worker.nodes":"Cluster size (Worker nodes)",
"label.cluster.type":"Cluster Type",
"label.clusters":"Clusters",
"label.clvm":"CLVM",
@@ -614,6 +621,7 @@ var dictionary = {
"label.day":"Day",
"label.day.of.month":"Day of Month",
"label.day.of.week":"Day of Week",
+"label.dashboard.endpoint":"Dashboard endpoint",
"label.dc.name":"DC Name",
"label.dead.peer.detection":"Dead Peer Detection",
"label.decline.invitation":"Decline invitation",
@@ -650,6 +658,8 @@ var dictionary = {
"label.delete.events":"Delete events",
"label.delete.gateway":"Delete gateway",
"label.delete.internal.lb":"Delete Internal LB",
+"label.delete.iso":"Delete ISO",
+"label.delete.kubernetes.version":"Delete Kubernetes version",
"label.delete.portable.ip.range":"Delete Portable IP Range",
"label.delete.profile":"Delete Profile",
"label.delete.project":"Delete project",
@@ -667,6 +677,7 @@ var dictionary = {
"label.destination.physical.network.id":"Destination physical network ID",
"label.destination.zone":"Destination Zone",
"label.destroy":"Destroy",
+"label.destroy.kubernetes.cluster":"Destroy Kubernetes cluster",
"label.destroy.router":"Destroy router",
"label.destroy.vm.graceperiod":"Destroy VM Grace Period",
"label.detaching.disk":"Detaching Disk",
@@ -732,6 +743,7 @@ var dictionary = {
"label.domain.suffix":"DNS Domain Suffix (i.e., xyz.com)",
"label.done":"Done",
"label.double.quotes.are.not.allowed":"Double quotes are not allowed",
+"label.download.kubernetes.cluster.config":"Download Kubernetes cluster config",
"label.download.progress":"Download Progress",
"label.drag.new.position":"Drag to new position",
"label.duration.in.sec":"Duration (in sec)",
@@ -790,6 +802,7 @@ var dictionary = {
"label.expunge":"Expunge",
"label.external.id":"External ID",
"label.external.link":"External link",
+'label.external.loadbalancer.ip.address': "External load balancer IP address",
"label.extractable":"Extractable",
"label.extractable.lower":"extractable",
"label.f5":"F5",
@@ -964,6 +977,9 @@ var dictionary = {
"label.iscsi":"iSCSI",
"label.iso":"ISO",
"label.iso.boot":"ISO Boot",
+"label.iso.id":"ISO ID",
+"label.iso.name":"ISO name",
+"label.iso.state":"ISO state",
"label.isolated.networks":"Isolated networks",
"label.isolation.method":"Isolation method",
"label.isolation.mode":"Isolation Mode",
@@ -975,6 +991,11 @@ var dictionary = {
"label.key":"Key",
"label.keyboard.language":"Keyboard language",
"label.keyboard.type":"Keyboard type",
+"label.kubernetes.cluster":"Kubernetes cluster",
+"label.kubernetes.cluster.details":"Kubernetes cluster details",
+"label.kubernetes.service":"Kubernetes Service",
+"label.kubernetes.version":"Kubernetes version",
+"label.kubernetes.version.details":"Kubernetes version details",
"label.kvm.traffic.label":"KVM traffic label",
"label.label":"Label",
"label.lang.arabic":"Arabic",
@@ -1042,6 +1063,7 @@ var dictionary = {
"label.mac.address": "MAC Address",
"label.management.servers":"Management Servers",
"label.mac.address.changes":"MAC Address Changes",
+"label.master.nodes":"Master nodes",
"label.max.cpus":"Max. CPU cores",
"label.max.guest.limit":"Max guest limit",
"label.max.instances":"Max Instances",
@@ -1246,6 +1268,7 @@ var dictionary = {
"label.no.items":"No Available Items",
"label.no.security.groups":"No Available Security Groups",
"label.no.thanks":"No thanks",
+"label.node.root.disk.size.gb":"Node root disk size (in GB)",
"label.none":"None",
"label.not.found":"Not Found",
"label.notifications":"Notifications",
@@ -1352,6 +1375,7 @@ var dictionary = {
"label.private.key":"Private Key",
"label.private.network":"Private network",
"label.private.port":"Private Port",
+"label.private.registry":"Private registry",
"label.private.zone":"Private Zone",
"label.privatekey":"PKCS#8 Private Key",
"label.privatekey.name":"Private Key",
@@ -1549,6 +1573,7 @@ var dictionary = {
"label.save.and.continue":"Save and continue",
"label.save.changes":"Save changes",
"label.saving.processing":"Saving....",
+"label.scale.kubernetes.cluster":"Scale Kubernetes cluster",
"label.scale.up.policy":"SCALE UP POLICY",
"label.scaledown.policy":"ScaleDown Policy",
"label.scaleup.policy":"ScaleUp Policy",
@@ -1589,6 +1614,7 @@ var dictionary = {
"label.select.template":"Select Template",
"label.select.tier":"Select Tier",
"label.select.vm.for.static.nat":"Select VM for static NAT",
+"label.semantic.version":"Semantic version",
"label.sent":"Sent",
"label.server":"Server",
"label.service.capabilities":"Service Capabilities",
@@ -1639,6 +1665,7 @@ var dictionary = {
"label.sslcertificates":"SSL Certificates",
"label.standard.us.keyboard":"Standard (US) keyboard",
"label.start.IP":"Start IP",
+"label.start.kuberentes.cluster":"Start Kubernetes cluster",
"label.start.lb.vm":"Start LB VM",
"label.start.port":"Start Port",
"label.start.reserved.system.IP":"Start Reserved system IP",
@@ -1679,6 +1706,7 @@ var dictionary = {
"label.sticky.request-learn":"Request learn",
"label.sticky.tablesize":"Table size",
"label.stop":"Stop",
+"label.stop.kuberentes.cluster":"Stop Kubernetes cluster",
"label.stop.lb.vm":"Stop LB VM",
"label.stopped.vms":"Stopped VMs",
"label.storage":"Storage",
@@ -1757,11 +1785,13 @@ var dictionary = {
"label.unhealthy.threshold":"Unhealthy Threshold",
"label.unlimited":"Unlimited",
"label.untagged":"Untagged",
+"label.update.kubernetes.version":"Update Kubernetes Version",
"label.update.project.resources":"Update project resources",
"label.update.ssl":" SSL Certificate",
"label.update.ssl.cert":" SSL Certificate",
"label.update.vmware.datacenter":"Update VMware datacenter",
"label.updating":"Updating",
+"label.upgrade.kubernetes.cluster":"Upgrade Kubernetes cluster",
"label.upgrade.required":"Upgrade is required",
"label.upgrade.router.newer.template":"Upgrade Router to Use Newer Template",
"label.upload":"Upload",
@@ -1790,6 +1820,7 @@ var dictionary = {
"label.username.lower":"username",
"label.users":"Users",
"label.uuid":"UUID",
+"label.versions":"Versions",
"label.vSwitch.type":"vSwitch Type",
"label.value":"Value",
"label.vcdcname":"vCenter DC name",
@@ -2096,8 +2127,10 @@ var dictionary = {
"message.confirm.delete.ciscoASA1000v":"Please confirm you want to delete CiscoASA1000v",
"message.confirm.delete.ciscovnmc.resource":"Please confirm you want to delete CiscoVNMC resource",
"message.confirm.delete.internal.lb":"Please confirm you want to delete Internal LB",
+"message.confirm.delete.kubernetes.version":"Please confirm that you want to delete this Kubernetes version.",
"message.confirm.delete.secondary.staging.store":"Please confirm you want to delete Secondary Staging Store.",
"message.confirm.delete.ucs.manager":"Please confirm that you want to delete UCS Manager",
+"message.confirm.destroy.kubernetes.cluster":"Please confirm that you want to destroy this Kubernetes cluster.",
"message.confirm.destroy.router":"Please confirm that you would like to destroy this router",
"message.confirm.disable.host":"Please confirm that you want to disable the host",
"message.confirm.disable.network.offering":"Are you sure you want to disable this network offering?",
@@ -2130,7 +2163,9 @@ var dictionary = {
"message.confirm.scale.up.router.vm":"Do you really want to scale up the Router VM ?",
"message.confirm.scale.up.system.vm":"Do you really want to scale up the system VM ?",
"message.confirm.shutdown.provider":"Please confirm that you would like to shutdown this provider",
+"message.confirm.start.kubernetes.cluster":"Please confirm that you want to start this Kubernetes cluster.",
"message.confirm.start.lb.vm":"Please confirm you want to start LB VM",
+"message.confirm.stop.kubernetes.cluster":"Please confirm that you want to stop this Kubernetes cluster.",
"message.confirm.stop.lb.vm":"Please confirm you want to stop LB VM",
"message.confirm.upgrade.router.newer.template":"Please confirm that you want to upgrade router to use newer template",
"message.confirm.upgrade.routers.account.newtemplate":"Please confirm that you want to upgrade all routers in this account to use newer template",
diff --git a/ui/plugins/cks/cks.css b/ui/plugins/cks/cks.css
new file mode 100644
index 00000000000..acdd1e64cd7
--- /dev/null
+++ b/ui/plugins/cks/cks.css
@@ -0,0 +1,43 @@
+/*[fmt]1C20-1C0D-E*/
+/*
+* 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.
+*/
+
+.downloadKubernetesClusterKubeConfig .icon {
+ background-position: -35px -125px;
+}
+
+.downloadKubernetesClusterKubeConfig:hover .icon {
+ background-position: -35px -707px;
+}
+
+.scaleKubernetesCluster .icon {
+ background-position: -264px -2px;
+}
+
+.scaleKubernetesCluster:hover .icon {
+ background-position: -263px -583px;
+}
+
+.upgradeKubernetesCluster .icon {
+ background-position: -138px -65px;
+}
+
+.upgradeKubernetesCluster:hover .icon {
+ background-position: -138px -647px;
+}
diff --git a/ui/plugins/cks/cks.js b/ui/plugins/cks/cks.js
new file mode 100644
index 00000000000..c353c24d093
--- /dev/null
+++ b/ui/plugins/cks/cks.js
@@ -0,0 +1,1581 @@
+// 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.
+(function (cloudStack) {
+
+ var rootCaCert = "";
+ var downloadCaCert = function() {
+ var blob = new Blob([rootCaCert], {type: 'application/x-x509-ca-cert'});
+ var filename = "cloudstack-ca.pem";
+ if(window.navigator.msSaveOrOpenBlob) {
+ window.navigator.msSaveBlob(blob, filename);
+ } else{
+ var elem = window.document.createElement('a');
+ elem.href = window.URL.createObjectURL(blob);
+ elem.download = filename;
+ document.body.appendChild(elem)
+ elem.click();
+ document.body.removeChild(elem);
+ }
+ };
+ var clusterKubeConfig = "";
+ var downloadClusterKubeConfig = function() {
+ var blob = new Blob([clusterKubeConfig], {type: 'text/plain'});
+ var filename = "kube.conf";
+ if(window.navigator.msSaveOrOpenBlob) {
+ window.navigator.msSaveBlob(blob, filename);
+ } else{
+ var elem = window.document.createElement('a');
+ elem.href = window.URL.createObjectURL(blob);
+ elem.download = filename;
+ document.body.appendChild(elem)
+ elem.click();
+ document.body.removeChild(elem);
+ }
+ };
+ var minCpu = 0;
+ var minRamSize = 0;
+ cloudStack.plugins.cks = function(plugin) {
+ plugin.ui.addSection({
+ id: 'cks',
+ title: 'label.kubernetes.service',
+ preFilter: function(args) {
+ var pluginEnabled = false;
+ $.ajax({
+ url: createURL('listCapabilities'),
+ async: false,
+ success: function(json) {
+ pluginEnabled = json.listcapabilitiesresponse.capability.kubernetesserviceenabled;
+ },
+ error: function(XMLHttpResponse) {
+ pluginEnabled = false;
+ }
+ });
+ return pluginEnabled;
+ },
+ showOnNavigation: true,
+ sectionSelect: {
+ label: 'label.select-view',
+ preFilter: function() {
+ return ['kubernetesclusters', 'kubernetesversions'];
+ }
+ },
+ sections: {
+ kubernetesclusters: {
+ id: 'kubernetesclusters',
+ type: 'select',
+ title: "label.clusters",
+ listView: {
+ filters: {
+ all: {
+ label: 'ui.listView.filters.all'
+ },
+ running: {
+ label: 'state.Running'
+ },
+ stopped: {
+ label: 'state.Stopped'
+ },
+ destroyed: {
+ label: 'state.Destroyed'
+ }
+ },
+ fields: {
+ name: {
+ label: 'label.name'
+ },
+ zonename: {
+ label: 'label.zone.name'
+ },
+ size : {
+ label: 'label.size'
+ },
+ cpunumber: {
+ label: 'label.num.cpu.cores'
+ },
+ memory: {
+ label: 'label.memory.mb'
+ },
+ state: {
+ label: 'label.state',
+ indicator: {
+ 'Running': 'on',
+ 'Stopped': 'off',
+ 'Destroyed': 'off',
+ 'Error': 'off'
+ }
+ }
+ },
+ advSearchFields: {
+ name: {
+ label: 'label.name'
+ },
+ zoneid: {
+ label: 'label.zone',
+ select: function(args) {
+ $.ajax({
+ url: createURL('listZones'),
+ data: {
+ listAll: true
+ },
+ success: function(json) {
+ var zones = json.listzonesresponse.zone ? json.listzonesresponse.zone : [];
+
+ args.response.success({
+ data: $.map(zones, function(zone) {
+ return {
+ id: zone.id,
+ description: zone.name
+ };
+ })
+ });
+ }
+ });
+ }
+ },
+ },
+ // List view actions
+ actions: {
+ add: {
+ label: 'label.add.kubernetes.cluster',
+ createForm: {
+ title: 'label.add.kubernetes.cluster',
+ preFilter: function(args) {
+ args.$form.find('.form-item[rel=masternodes]').find('input[name=masternodes]').val('2');
+ args.$form.find('.form-item[rel=size]').find('input[name=size]').val('1');
+ var experimentalFeaturesEnabled = false;
+ $.ajax({
+ url: createURL('listCapabilities'),
+ async: false,
+ success: function(json) {
+ experimentalFeaturesEnabled = json.listcapabilitiesresponse.capability.kubernetesclusterexperimentalfeaturesenabled;
+ }
+ });
+ if (experimentalFeaturesEnabled == true) {
+ args.$form.find('.form-item[rel=supportPrivateRegistry]').css('display', 'inline-block');
+ }
+ },
+ fields: {
+ name: {
+ label: 'label.name',
+ //docID: 'Name of the cluster',
+ validation: {
+ required: true
+ }
+ },
+ description: {
+ label: 'label.description',
+ //docID: 'helpKubernetesClusterDesc',
+ validation: {
+ required: true
+ }
+ },
+ zone: {
+ label: 'label.zone',
+ //docID: 'helpKubernetesClusterZone',
+ validation: {
+ required: true
+ },
+ select: function(args) {
+ $.ajax({
+ url: createURL("listZones&available=true"),
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ var items = [];
+ var zoneObjs = json.listzonesresponse.zone;
+ if (zoneObjs != null) {
+ for (var i = 0; i < zoneObjs.length; i++) {
+ items.push({
+ id: zoneObjs[i].id,
+ description: zoneObjs[i].name
+ });
+ }
+ }
+ args.response.success({
+ data: items
+ });
+ }
+ });
+ }
+ },
+ kubernetesversion: {
+ label: 'label.kubernetes.version',
+ dependsOn: ['zone'],
+ //docID: 'helpKubernetesClusterZone',
+ validation: {
+ required: true
+ },
+ select: function(args) {
+ var versionObjs;
+ var filterData = { zoneid: args.zone };
+ $.ajax({
+ url: createURL("listKubernetesSupportedVersions"),
+ data: filterData,
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ var items = [];
+ versionObjs = json.listkubernetessupportedversionsresponse.kubernetessupportedversion;
+ if (versionObjs != null) {
+ for (var i = 0; i < versionObjs.length; i++) {
+ if (versionObjs[i].state == 'Enabled' && versionObjs[i].isostate == 'Ready') {
+ items.push({
+ id: versionObjs[i].id,
+ description: versionObjs[i].name
+ });
+ }
+ }
+ }
+ args.response.success({
+ data: items
+ });
+ }
+ });
+
+ args.$select.change(function() {
+ var $form = $(this).closest("form");
+ $form.find('.form-item[rel=multimaster]').find('input[name=multimaster]').prop('checked', false);
+ $form.find('.form-item[rel=multimaster]').hide();
+ $form.find('.form-item[rel=masternodes]').hide();
+ var currentVersionId = $(this).val();
+ if (currentVersionId != null && versionObjs != null) {
+ for (var i = 0; i < versionObjs.length; i++) {
+ if (currentVersionId == versionObjs[i].id) {
+ if (versionObjs[i].supportsha === true) {
+ $form.find('.form-item[rel=multimaster]').css('display', 'inline-block');
+ }
+ minCpu = 0;
+ if (versionObjs[i].mincpunumber != null && versionObjs[i].mincpunumber != undefined) {
+ minCpu = versionObjs[i].mincpunumber;
+ }
+ minRamSize = 0;
+ if (versionObjs[i].minmemory != null && versionObjs[i].minmemory != undefined) {
+ minRamSize = versionObjs[i].minmemory;
+ }
+ break;
+ }
+ }
+ }
+ });
+ }
+ },
+ serviceoffering: {
+ label: 'label.menu.service.offerings',
+ dependsOn: ['kubernetesversion'],
+ //docID: 'helpKubernetesClusterServiceOffering',
+ validation: {
+ required: true
+ },
+ select: function(args) {
+ $.ajax({
+ url: createURL("listServiceOfferings"),
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ var offeringObjs = [];
+ var items = json.listserviceofferingsresponse.serviceoffering;
+ if (items != null) {
+ for (var i = 0; i < items.length; i++) {
+ if (items[i].iscustomized == false &&
+ items[i].cpunumber >= minCpu && items[i].memory >= minRamSize) {
+ offeringObjs.push({
+ id: items[i].id,
+ description: items[i].name
+ });
+ }
+ }
+ }
+ args.response.success({
+ data: offeringObjs
+ });
+ }
+ });
+ }
+ },
+ noderootdisksize: {
+ label: 'label.node.root.disk.size.gb',
+ //docID: 'helpKubernetesClusterNodeRootDiskSize',
+ validation: {
+ number: true
+ }
+ },
+ network: {
+ label: 'label.network',
+ //docID: 'helpKubernetesClusterNetwork',
+ select: function(args) {
+ $.ajax({
+ url: createURL("listNetworks"),
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ var networkObjs = [];
+ networkObjs.push({
+ id: "",
+ description: ""
+ });
+ var items = json.listnetworksresponse.network;
+ if (items != null) {
+ for (var i = 0; i < items.length; i++) {
+ networkObjs.push({
+ id: items[i].id,
+ description: items[i].name
+ });
+ }
+ }
+ args.response.success({
+ data: networkObjs
+ });
+ }
+ });
+ }
+ },
+ multimaster: {
+ label: "label.ha.enabled",
+ dependsOn: 'kubernetesversion',
+ isBoolean: true,
+ isChecked: false,
+ },
+ masternodes: {
+ label: 'label.master.nodes',
+ //docID: 'helpKubernetesClusterSize',
+ validation: {
+ required: true,
+ multiplecountnumber: true
+ },
+ dependsOn: "multimaster",
+ isHidden: true,
+ },
+ externalloadbalanceripaddress: {
+ label: 'label.external.loadbalancer.ip.address',
+ validation: {
+ ipv4AndIpv6AddressValidator: true
+ },
+ dependsOn: "multimaster",
+ isHidden: true,
+ },
+ size: {
+ label: 'label.cluster.size.worker.nodes',
+ //docID: 'helpKubernetesClusterSize',
+ validation: {
+ required: true,
+ naturalnumber: true
+ },
+ },
+ sshkeypair: {
+ label: 'label.ssh.key.pair',
+ //docID: 'helpKubernetesClusterSSH',
+ select: function(args) {
+ $.ajax({
+ url: createURL("listSSHKeyPairs"),
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ var keypairObjs = [];
+ keypairObjs.push({
+ id: "",
+ description: ""
+ });
+ var items = json.listsshkeypairsresponse.sshkeypair;
+ if (items != null) {
+ for (var i = 0; i < items.length; i++) {
+ keypairObjs.push({
+ id: items[i].name,
+ description: items[i].name
+ });
+ }
+ }
+ args.response.success({
+ data: keypairObjs
+ });
+ }
+ });
+ }
+ },
+ supportPrivateRegistry: {
+ label: 'label.private.registry',
+ isBoolean: true,
+ isChecked: false,
+ isHidden: true
+ },
+ username: {
+ label: 'label.username',
+ dependsOn: 'supportPrivateRegistry',
+ validation: {
+ required: true
+ },
+ isHidden: true
+ },
+ password: {
+ label: 'label.password',
+ dependsOn: 'supportPrivateRegistry',
+ validation: {
+ required: true
+ },
+ isHidden: true,
+ isPassword: true
+ },
+ url: {
+ label: 'label.url',
+ dependsOn: 'supportPrivateRegistry',
+ validation: {
+ required: true
+ },
+ isHidden: true,
+ },
+ email: {
+ label: 'label.email',
+ dependsOn: 'supportPrivateRegistry',
+ validation: {
+ required: true
+ },
+ isHidden: true,
+ }
+ }
+ },
+
+ action: function(args) {
+ var data = {
+ name: args.data.name,
+ description: args.data.description,
+ zoneid: args.data.zone,
+ kubernetesversionid: args.data.kubernetesversion,
+ serviceofferingid: args.data.serviceoffering,
+ size: args.data.size,
+ keypair: args.data.sshkeypair
+ };
+
+ if (args.data.noderootdisksize != null && args.data.noderootdisksize != "" && args.data.noderootdisksize > 0) {
+ $.extend(data, {
+ noderootdisksize: args.data.noderootdisksize
+ });
+ }
+
+ var masterNodes = 1;
+ if (args.data.multimaster === 'on') {
+ masterNodes = args.data.masternodes;
+ if (args.data.externalloadbalanceripaddress != null && args.data.externalloadbalanceripaddress != "") {
+ $.extend(data, {
+ externalloadbalanceripaddress: args.data.externalloadbalanceripaddress
+ });
+ }
+ }
+ $.extend(data, {
+ masternodes: masterNodes
+ });
+
+ if (args.data.supportPrivateRegistry) {
+ $.extend(data, {
+ dockerregistryusername: args.data.username,
+ dockerregistrypassword: args.data.password,
+ dockerregistryurl: args.data.url,
+ dockerregistryemail: args.data.email
+ });
+ }
+
+ if (args.data.network != null && args.data.network.length > 0) {
+ $.extend(data, {
+ networkid: args.data.network
+ });
+ }
+ $.ajax({
+ url: createURL('createKubernetesCluster'),
+ data: data,
+ success: function(json) {
+ var jid = json.createkubernetesclusterresponse.jobid;
+ args.response.success({
+ _custom: {
+ jobId: jid
+ }
+ });
+ },
+ error: function(XMLHttpResponse) {
+ var errorMsg = parseXMLHttpResponse(XMLHttpResponse);
+ args.response.error(errorMsg);
+ }
+ });
+ },
+
+
+ messages: {
+ notification: function(args) {
+ return 'Kubernetes Cluster Add';
+ }
+ },
+ notification: {
+ poll: pollAsyncJobResult
+ }
+ }
+ },
+ dataProvider: function(args) {
+ var data = {
+ page: args.page,
+ pagesize: pageSize
+ };
+ listViewDataProvider(args, data);
+ if (args.filterBy != null) { //filter dropdown
+ if (args.filterBy.kind != null) {
+ switch (args.filterBy.kind) {
+ case "all":
+ break;
+ case "running":
+ $.extend(data, {
+ state: 'Running'
+ });
+ break;
+ case "stopped":
+ $.extend(data, {
+ state: 'Stopped'
+ });
+ break;
+ case "destroyed":
+ $.extend(data, {
+ state: 'Destroyed'
+ });
+ break;
+ }
+ }
+ }
+
+ $.ajax({
+ url: createURL("listKubernetesClusters"),
+ data: data,
+ dataType: "json",
+ sync: true,
+ success: function(json) {
+ var items = [];
+ if (json.listkubernetesclustersresponse.kubernetescluster != null) {
+ items = json.listkubernetesclustersresponse.kubernetescluster;
+ }
+ args.response.success({
+ actionFilter: cksActionfilter,
+ data: items
+ });
+ }
+ });
+ },
+
+ detailView: {
+ name: 'label.kubernetes.cluster.details',
+ isMaximized: true,
+ actions: {
+ start: {
+ label: 'label.start.kuberentes.cluster',
+ action: function(args) {
+ $.ajax({
+ url: createURL("startKubernetesCluster"),
+ data: {"id": args.context.kubernetesclusters[0].id},
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ var jid = json.startkubernetesclusterresponse.jobid;
+ args.response.success({
+ _custom: {
+ jobId: jid
+ }
+ });
+ }
+ });
+ },
+ messages: {
+ confirm: function(args) {
+ return 'message.confirm.start.kubernetes.cluster';
+ },
+ notification: function(args) {
+ return 'Started Kubernetes cluster.';
+ }
+ },
+ notification: {
+ poll: pollAsyncJobResult
+ }
+ },
+ stop: {
+ label: 'label.stop.kuberentes.cluster',
+ action: function(args) {
+ $.ajax({
+ url: createURL("stopKubernetesCluster"),
+ data: {"id": args.context.kubernetesclusters[0].id},
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ var jid = json.stopkubernetesclusterresponse.jobid;
+ args.response.success({
+ _custom: {
+ jobId: jid
+ }
+ });
+ }
+ });
+ },
+ messages: {
+ confirm: function(args) {
+ return 'message.confirm.stop.kubernetes.cluster';
+ },
+ notification: function(args) {
+ return 'Stopped Kubernetes cluster.';
+ }
+ },
+ notification: {
+ poll: pollAsyncJobResult
+ }
+ },
+ destroy: {
+ label: 'label.destroy.kubernetes.cluster',
+ compactLabel: 'label.destroy',
+ createForm: {
+ title: 'label.destroy.kubernetes.cluster',
+ desc: 'label.destroy.kubernetes.cluster',
+ isWarning: true,
+ fields: {
+ }
+ },
+ messages: {
+ confirm: function(args) {
+ return 'message.confirm.destroy.kubernetes.cluster';
+ },
+ notification: function(args) {
+ return 'Destroyed Kubernetes cluster.';
+ }
+ },
+ action: function(args) {
+ var data = {
+ id: args.context.kubernetesclusters[0].id
+ };
+ $.ajax({
+ url: createURL('deleteKubernetesCluster'),
+ data: data,
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ args.response.success({
+ _custom: {
+ jobId: json.deletekubernetesclusterresponse.jobid,
+ getUpdatedItem: function(json) {
+ return { 'toRemove': true };
+ }
+ }
+ });
+ }
+ });
+ },
+ notification: {
+ poll: pollAsyncJobResult
+ }
+ },
+ downloadKubernetesClusterKubeConfig: {
+ label: 'label.download.kubernetes.cluster.config',
+ messages: {
+ notification: function(args) {
+ return 'label.download.kubernetes.cluster.config';
+ }
+ },
+ action: function(args) {
+ var data = {
+ id: args.context.kubernetesclusters[0].id
+ }
+ $.ajax({
+ url: createURL("getKubernetesClusterConfig"),
+ dataType: "json",
+ data: data,
+ async: false,
+ success: function(json) {
+ var jsonObj;
+ if (json.getkubernetesclusterconfigresponse.clusterconfig != null &&
+ json.getkubernetesclusterconfigresponse.clusterconfig.configdata != null ) {
+ jsonObj = json.getkubernetesclusterconfigresponse.clusterconfig;
+ clusterKubeConfig = jsonObj.configdata;
+ downloadClusterKubeConfig();
+ args.response.success({});
+ } else {
+ args.response.error("Unable to retrieve Kubernetes cluster config");
+ }
+ },
+ error: function(XMLHttpResponse) {
+ var errorMsg = parseXMLHttpResponse(XMLHttpResponse);
+ args.response.error(errorMsg);
+ }
+ });
+ },
+ notification: {
+ poll: function(args) {
+ args.complete();
+ }
+ }
+ },
+ scaleKubernetesCluster: {
+ label: 'label.scale.kubernetes.cluster',
+ messages: {
+ notification: function(args) {
+ return 'label.scale.kubernetes.cluster';
+ }
+ },
+ createForm: {
+ title: 'label.scale.kubernetes.cluster',
+ desc: '',
+ preFilter: function(args) {
+ var options = args.$form.find('.form-item[rel=serviceoffering]').find('option');
+ $.each(options, function(optionIndex, option) {
+ if ($(option).val() === args.context.kubernetesclusters[0].serviceofferingid) {
+ $(option).attr('selected','selected');
+ }
+ });
+ args.$form.find('.form-item[rel=size]').find('input[name=size]').val(args.context.kubernetesclusters[0].size);
+ },
+ fields: {
+ serviceoffering: {
+ label: 'label.menu.service.offerings',
+ //docID: 'helpKubernetesClusterServiceOffering',
+ validation: {
+ required: true
+ },
+ select: function(args) {
+ $.ajax({
+ url: createURL("listKubernetesSupportedVersions"),
+ data: {id: args.context.kubernetesclusters[0].kubernetesversionid},
+ dataType: "json",
+ async: false,
+ success: function(json) {
+ var versionObjs = json.listkubernetessupportedversionsresponse.kubernetessupportedversion;
+ if (versionObjs != null && versionObjs.length > 0) {
+ minCpu = 0;
+ if (versionObjs[0].mincpunumber != null && versionObjs[0].mincpunumber != undefined) {
+ minCpu = versionObjs[0].mincpunumber;
+ }
+ minRamSize = 0;
+ if (versionObjs[0].minmemory != null && versionObjs[0].minmemory != undefined) {
+ minRamSize = versionObjs[0].minmemory;
+ }
+ }
+ }
+ });
+ $.ajax({
+ url: createURL("listServiceOfferings"),
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ var offeringObjs = [];
+ var items = json.listserviceofferingsresponse.serviceoffering;
+ if (items != null) {
+ for (var i = 0; i < items.length; i++) {
+ if (items[i].iscustomized == false &&
+ items[i].cpunumber >= minCpu && items[i].memory >= minRamSize) {
+ offeringObjs.push({
+ id: items[i].id,
+ description: items[i].name
+ });
+ }
+ }
+ }
+ args.response.success({
+ data: offeringObjs
+ });
+ }
+ });
+ }
+ },
+ size: {
+ label: 'label.cluster.size',
+ //docID: 'helpKubernetesClusterSize',
+ validation: {
+ required: true,
+ number: true
+ },
+ }
+ }
+ },
+ action: function(args) {
+ var data = {
+ id: args.context.kubernetesclusters[0].id,
+ serviceofferingid: args.data.serviceoffering,
+ size: args.data.size
+ };
+ $.ajax({
+ url: createURL('scaleKubernetesCluster'),
+ data: data,
+ dataType: "json",
+ success: function (json) {
+ var jid = json.scalekubernetesclusterresponse.jobid;
+ args.response.success({
+ _custom: {
+ jobId: jid,
+ getActionFilter: function() {
+ return cksActionfilter;
+ }
+ }
+ });
+ }
+ }); //end ajax
+ },
+ notification: {
+ poll: pollAsyncJobResult
+ }
+ },
+ upgradeKubernetesCluster: {
+ label: 'label.upgrade.kubernetes.cluster',
+ messages: {
+ notification: function(args) {
+ return 'label.upgrade.kubernetes.cluster';
+ }
+ },
+ createForm: {
+ title: 'label.upgrade.kubernetes.cluster',
+ desc: '',
+ preFilter: function(args) {},
+ fields: {
+ kubernetesversion: {
+ label: 'label.kubernetes.version',
+ //docID: 'helpKubernetesClusterZone',
+ validation: {
+ required: true
+ },
+ select: function(args) {
+ var filterData = { minimumkubernetesversionid: args.context.kubernetesclusters[0].kubernetesversionid };
+ $.ajax({
+ url: createURL("listKubernetesSupportedVersions"),
+ data: filterData,
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ var items = [];
+ var versionObjs = json.listkubernetessupportedversionsresponse.kubernetessupportedversion;
+ if (versionObjs != null) {
+ var clusterVersion = null;
+ for (var j = 0; j < versionObjs.length; j++) {
+ if (versionObjs[j].id == args.context.kubernetesclusters[0].kubernetesversionid) {
+ clusterVersion = versionObjs[j];
+ break;
+ }
+ }
+ for (var i = 0; i < versionObjs.length; i++) {
+ if (versionObjs[i].id != args.context.kubernetesclusters[0].kubernetesversionid &&
+ (clusterVersion == null || (clusterVersion != null && versionObjs[i].semanticversion != clusterVersion.semanticversion)) &&
+ versionObjs[i].state == 'Enabled' && versionObjs[i].isostate == 'Ready') {
+ items.push({
+ id: versionObjs[i].id,
+ description: versionObjs[i].name
+ });
+ }
+ }
+ }
+ args.response.success({
+ data: items
+ });
+ }
+ });
+ }
+ },
+ }
+ },
+ action: function(args) {
+ var data = {
+ id: args.context.kubernetesclusters[0].id,
+ kubernetesversionid: args.data.kubernetesversion
+ };
+ $.ajax({
+ url: createURL('upgradeKubernetesCluster'),
+ data: data,
+ dataType: "json",
+ success: function (json) {
+ var jid = json.upgradekubernetesclusterresponse.jobid;
+ args.response.success({
+ _custom: {
+ jobId: jid,
+ getActionFilter: function() {
+ return cksActionfilter;
+ }
+ }
+ });
+ }
+ }); //end ajax
+ },
+ notification: {
+ poll: pollAsyncJobResult
+ }
+ },
+ },
+ tabs: {
+ // Details tab
+ details: {
+ title: 'label.details',
+ fields: [{
+ id: {
+ label: 'label.id'
+ },
+ name: {
+ label: 'label.name'
+ },
+ zonename: {
+ label: 'label.zone.name'
+ },
+ kubernetesversionname: {
+ label: 'label.kubernetes.version'
+ },
+ masternodes : {
+ label: 'label.master.nodes'
+ },
+ size : {
+ label: 'label.cluster.size'
+ },
+ cpunumber: {
+ label: 'label.num.cpu.cores'
+ },
+ memory: {
+ label: 'label.memory.mb'
+ },
+ state: {
+ label: 'label.state',
+ },
+ serviceofferingname: {
+ label: 'label.compute.offering'
+ },
+ associatednetworkname: {
+ label: 'label.network'
+ },
+ keypair: {
+ label: 'label.ssh.key.pair'
+ }
+ }],
+
+ dataProvider: function(args) {
+ $.ajax({
+ url: createURL("listKubernetesClusters&id=" + args.context.kubernetesclusters[0].id),
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ var jsonObj;
+ if (json.listkubernetesclustersresponse.kubernetescluster != null && json.listkubernetesclustersresponse.kubernetescluster.length > 0) {
+ jsonObj = json.listkubernetesclustersresponse.kubernetescluster[0];
+ }
+ args.response.success({
+ actionFilter: cksActionfilter,
+ data: jsonObj
+ });
+ }
+ });
+ }
+ },
+ clusteraccess: {
+ title: 'label.access',
+ custom : function (args) {
+ var showAccess = function() {
+ var state = args.context.kubernetesclusters[0].state;
+ if (state == "Created") { // Created
+ return jQuery('
').html("Kubernetes cluster setup is under progress, please check again in few minutes.");
+ } else if (state == "Error") { // Error
+ return jQuery('
').html("Kubernetes cluster is in error state, it cannot be accessed.");
+ } else if (state == "Destroying") { // Destroying
+ return jQuery('
').html("Kubernetes cluster is in destroying state, it cannot be accessed.");
+ } else if (state == "Destroyed") { // Destroyed
+ return jQuery('
').html("Kubernetes cluster is already destroyed, it cannot be accessed.");
+ }
+ var data = {
+ id: args.context.kubernetesclusters[0].kubernetesversionid
+ }
+ var version = '';
+ $.ajax({
+ url: createURL("listKubernetesSupportedVersions"),
+ dataType: "json",
+ data: data,
+ async: false,
+ success: function(json) {
+ var jsonObj;
+ if (json.listkubernetessupportedversionsresponse.kubernetessupportedversion != null) {
+ version = json.listkubernetessupportedversionsresponse.kubernetessupportedversion[0].semanticversion;
+ }
+ }
+ });
+ return jQuery('
').html("Access Kubernetes cluster
Download cluster's kubeconfig file using action from Details tab.
Download kubectl tool for cluster's Kubernetes version from,
Linux: https://storage.googleapis.com/kubernetes-release/release/v" + version + "/bin/linux/amd64/kubectl
MacOS: https://storage.googleapis.com/kubernetes-release/release/v" + version + "/bin/darwin/amd64/kubectl
Windows: https://storage.googleapis.com/kubernetes-release/release/v" + version + "/bin/windows/amd64/kubectl.exe
Using kubectl and kubeconfig file to access cluster
kubectl --kubeconfig /custom/path/kube.conf {COMMAND}
List pods
kubectl --kubeconfig /custom/path/kube.conf get pods --all-namespaces
List nodes
kubectl --kubeconfig /custom/path/kube.conf get nodes --all-namespaces
List services
kubectl --kubeconfig /custom/path/kube.conf get services --all-namespaces
Access dashboard web UI
Run proxy locally
kubectl --kubeconfig /custom/path/kube.conf proxy
Open URL in browser
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
Token for dashboard login can be retrieved using following command
kubectl --kubeconfig /custom/path/kube.conf describe secret $(kubectl --kubeconfig /custom/path/kube.conf get secrets -n kubernetes-dashboard | grep kubernetes-dashboard-token | awk '{print $1}') -n kubernetes-dashboard
More about accessing dashboard UI, https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#accessing-the-dashboard-ui");
+ };
+ return showAccess();
+ }
+ },
+ clusterinstances: {
+ title: 'label.instances',
+ listView: {
+ section: 'clusterinstances',
+ preFilter: function(args) {
+ var hiddenFields = [];
+ if (!isAdmin()) {
+ hiddenFields.push('instancename');
+ }
+ return hiddenFields;
+ },
+ fields: {
+ name: {
+ label: 'label.name',
+ truncate: true
+ },
+ instancename: {
+ label: 'label.internal.name'
+ },
+ displayname: {
+ label: 'label.display.name',
+ truncate: true
+ },
+ ipaddress: {
+ label: 'label.ip.address'
+ },
+ zonename: {
+ label: 'label.zone.name'
+ },
+ state: {
+ label: 'label.state',
+ indicator: {
+ 'Running': 'on',
+ 'Stopped': 'off',
+ 'Destroyed': 'off',
+ 'Error': 'off'
+ }
+ }
+ },
+ dataProvider: function(args) {
+ var data = {};
+ listViewDataProvider(args, data);
+
+ $.ajax({
+ url: createURL("listKubernetesClusters"),
+ data: {"id": args.context.kubernetesclusters[0].id},
+ success: function(json) {
+ var items = json.listkubernetesclustersresponse.kubernetescluster;
+
+ var vmlist = [];
+ $.each(items, function(idx, item) {
+ if ("virtualmachineids" in item) {
+ vmlist = vmlist.concat(item.virtualmachineids);
+ }
+ });
+
+ $.extend(data, {
+ ids: vmlist.join()
+ });
+
+ if (items && items.length > 0 && items[0].projectid != null &&
+ items[0].projectid != undefined && items[0].projectid.length > 0) {
+ $.extend(data, {
+ projectid: items[0].projectid
+ });
+ }
+
+ if (data.ids.length == 0) {
+ args.response.success({
+ data: []
+ });
+ } else {
+ $.ajax({
+ url: createURL('listVirtualMachines'),
+ data: data,
+ success: function(json) {
+ var items = json.listvirtualmachinesresponse.virtualmachine;
+ if (items) {
+ $.each(items, function(idx, vm) {
+ if (vm.nic && vm.nic.length > 0 && vm.nic[0].ipaddress) {
+ items[idx].ipaddress = vm.nic[0].ipaddress;
+ }
+ });
+ }
+ args.response.success({
+ data: items
+ });
+ },
+ error: function(XMLHttpResponse) {
+ cloudStack.dialog.notice({
+ message: parseXMLHttpResponse(XMLHttpResponse)
+ });
+ args.response.error();
+ }
+ });
+ }
+ }
+ });
+ },
+ }
+ },
+ firewall: {
+ title: 'label.firewall',
+ custom: function(args) {
+ var data = {
+ id: args.context.kubernetesclusters[0].networkid,
+ listAll: true
+ }
+ if (args.context.kubernetesclusters[0].projectid != null &&
+ args.context.kubernetesclusters[0].projectid != undefined &&
+ args.context.kubernetesclusters[0].projectid.length > 0) {
+ $.extend(data, {
+ projectid: args.context.kubernetesclusters[0].projectid
+ });
+ $.extend(args.context, {"projectid": args.context.kubernetesclusters[0].projectid});
+ }
+ $.ajax({
+ url: createURL('listNetworks'),
+ data: data,
+ async: false,
+ dataType: "json",
+ success: function(json) {
+ var network = json.listnetworksresponse.network;
+ $.extend(args.context, {"networks": [network]});
+ }
+ });
+ data = {
+ associatedNetworkId: args.context.kubernetesclusters[0].networkid,
+ listAll: true,
+ forvirtualnetwork: true
+ }
+ if (args.context.kubernetesclusters[0].projectid != null &&
+ args.context.kubernetesclusters[0].projectid != undefined &&
+ args.context.kubernetesclusters[0].projectid.length > 0) {
+ $.extend(data, {
+ projectid: args.context.kubernetesclusters[0].projectid
+ });
+ }
+ $.ajax({
+ url: createURL('listPublicIpAddresses'),
+ data: data,
+ async: false,
+ dataType: "json",
+ success: function(json) {
+ var ips = json.listpublicipaddressesresponse.publicipaddress;
+ var fwip = ips[0];
+ $.each(ips, function(idx, ip) {
+ if (ip.issourcenat || ip.isstaticnat) {
+ fwip = ip;
+ return false;
+ }
+ });
+ $.extend(args.context, {"ipAddresses": [fwip]});
+ }
+ });
+ return cloudStack.sections.network.sections.ipAddresses.listView.detailView.tabs.ipRules.custom(args);
+ },
+ },
+ }
+ }
+ }
+ },
+ kubernetesversions: {
+ id: 'kubernetesversions',
+ type: 'select',
+ title: "label.versions",
+ listView: {
+ fields: {
+ name: {
+ label: 'label.name'
+ },
+ semanticversion: {
+ label: 'label.kubernetes.version'
+ },
+ zonename: {
+ label: 'label.zone.name'
+ },
+ isoname: {
+ label: 'label.iso.name'
+ },
+ isostate: {
+ label: 'label.iso.state'
+ },
+ mincpunumber: {
+ label: 'label.min.cpu.cores'
+ },
+ minmemory: {
+ label: 'label.memory.minimum.mb'
+ },
+ state: {
+ label: 'label.state',
+ indicator: {
+ 'Enabled': 'on',
+ 'Disabled': 'off'
+ }
+ }
+ },
+ advSearchFields: {
+ name: {
+ label: 'label.name'
+ },
+ zoneid: {
+ label: 'label.zone',
+ select: function(args) {
+ $.ajax({
+ url: createURL('listZones'),
+ data: {
+ listAll: true
+ },
+ success: function(json) {
+ var zones = json.listzonesresponse.zone ? json.listzonesresponse.zone : [];
+
+ args.response.success({
+ data: $.map(zones, function(zone) {
+ return {
+ id: zone.id,
+ description: zone.name
+ };
+ })
+ });
+ }
+ });
+ }
+ },
+ },
+ // List view actions
+ actions: {
+ add: {
+ label: 'label.add.kubernetes.version',
+ preFilter: function(args) { return isAdmin(); },
+ createForm: {
+ title: 'label.add.kubernetes.version',
+ preFilter: cloudStack.preFilter.createTemplate,
+ fields: {
+ version: {
+ label: 'label.semantic.version',
+ //docID: 'Name of the cluster',
+ validation: {
+ required: true
+ }
+ },
+ name: {
+ label: 'label.name',
+ //docID: 'Name of the cluster',
+ },
+ zone: {
+ label: 'label.zone',
+ //docID: 'helpKubernetesClusterZone',
+ validation: {
+ required: true
+ },
+ select: function(args) {
+ $.ajax({
+ url: createURL("listZones&available=true"),
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ var items = [];
+ var zoneObjs = json.listzonesresponse.zone;
+ if (zoneObjs != null) {
+ for (var i = 0; i < zoneObjs.length; i++) {
+ items.push({
+ id: zoneObjs[i].id,
+ description: zoneObjs[i].name
+ });
+ }
+ }
+ items.sort(function(a, b) {
+ return a.description.localeCompare(b.description);
+ });
+ items.unshift({
+ id: -1,
+ description: 'label.all.zones'
+ });
+ args.response.success({
+ data: items
+ });
+ }
+ });
+ }
+ },
+ isourl: {
+ label: 'label.url',
+ //docID: 'Name of the cluster',
+ validation: {
+ required: true
+ }
+ },
+ isochecksum: {
+ label: 'label.checksum',
+ //docID: 'Name of the cluster',
+ },
+ mincpunumber: {
+ label: 'label.min.cpu.cores',
+ validation: {
+ required: true,
+ number: true
+ },
+ },
+ minmemory: {
+ label: 'label.memory.minimum.mb',
+ validation: {
+ required: true,
+ number: true
+ }
+ }
+ }
+ },
+
+ action: function(args) {
+ var data = {
+ name: args.data.name,
+ semanticversion: args.data.version,
+ url: args.data.isourl,
+ checksum: args.data.isochecksum
+ };
+ if (args.data.zone != null && args.data.zone != -1) {
+ $.extend(data, {
+ zoneid: args.data.zone
+ });
+ }
+ if (args.data.mincpunumber != null && args.data.mincpunumber != "" && args.data.mincpunumber > 0) {
+ $.extend(data, {
+ mincpunumber: args.data.mincpunumber
+ });
+ }
+ if (args.data.minmemory != null && args.data.minmemory != "" && args.data.minmemory > 0) {
+ $.extend(data, {
+ minmemory: args.data.minmemory
+ });
+ }
+ $.ajax({
+ url: createURL('addKubernetesSupportedVersion'),
+ data: data,
+ success: function(json) {
+ var version = json.addkubernetessupportedversionresponse.kubernetessupportedversion;
+ args.response.success({
+ data: version
+ });
+ },
+ error: function(XMLHttpResponse) {
+ var errorMsg = parseXMLHttpResponse(XMLHttpResponse);
+ args.response.error(errorMsg);
+ }
+ });
+ },
+ messages: {
+ notification: function(args) {
+ return 'Kubernetes Supported Version Add';
+ }
+ }
+ }
+ },
+ dataProvider: function(args) {
+ var data = {
+ page: args.page,
+ pagesize: pageSize
+ };
+ listViewDataProvider(args, data);
+ $.ajax({
+ url: createURL("listKubernetesSupportedVersions"),
+ data: data,
+ dataType: "json",
+ sync: true,
+ success: function(json) {
+ var items = [];
+ if (json.listkubernetessupportedversionsresponse.kubernetessupportedversion != null) {
+ items = json.listkubernetessupportedversionsresponse.kubernetessupportedversion;
+ }
+ args.response.success({
+ data: items
+ });
+ }
+ });
+ },
+
+ detailView: {
+ name: 'label.kubernetes.version.details',
+ isMaximized: true,
+ actions: {
+ update: {
+ label: 'label.edit',
+ messages: {
+ notification: function(args) {
+ return 'label.update.kubernetes.version';
+ }
+ },
+ createForm: {
+ title: 'label.update.kubernetes.version',
+ desc: '',
+ preFilter: function(args) {
+ var formVersion = args.context.kubernetesversions[0];
+ $.ajax({
+ url: createURL('listKubernetesSupportedVersions'),
+ data: {
+ id: args.context.kubernetesversions[0].id
+ },
+ dataType: "json",
+ async: false,
+ success: function (json) {
+ if (json.listkubernetessupportedversionsresponse.kubernetessupportedversion != null &&
+ json.listkubernetessupportedversionsresponse.kubernetessupportedversion.length > 0) {
+ formVersion = json.listkubernetessupportedversionsresponse.kubernetessupportedversion[0];
+ }
+ }
+ });
+ if (formVersion.state != null) {
+ var options = args.$form.find('.form-item[rel=state]').find('option');
+ $.each(options, function(optionIndex, option) {
+ if ($(option).val() === formVersion.state) {
+ $(option).attr('selected','selected');
+ }
+ });
+ }
+ },
+ fields: {
+ state: {
+ label: 'label.state',
+ //docID: 'helpKubernetesClusterZone',
+ validation: {
+ required: true
+ },
+ select: function(args) {
+ var items = [];
+ items.push({
+ id: 'Enabled',
+ description: 'state.Enabled'
+ }, {
+ id: 'Disabled',
+ description: 'state.Disabled'
+ });
+ args.response.success({
+ data: items
+ });
+ }
+ },
+ }
+ },
+ action: function(args) {
+ var data = {
+ id: args.context.kubernetesversions[0].id,
+ state: args.data.state
+ };
+ $.ajax({
+ url: createURL('updateKubernetesSupportedVersion'),
+ data: data,
+ dataType: "json",
+ success: function (json) {
+ var jsonObj;
+ if (json.updatekubernetessupportedversionresponse.kubernetessupportedversion != null) {
+ jsonObj = json.updatekubernetessupportedversionresponse.kubernetessupportedversion;
+ }
+ args.response.success({
+ data: jsonObj
+ });
+ },
+ error: function(XMLHttpResponse) {
+ var errorMsg = parseXMLHttpResponse(XMLHttpResponse);
+ args.response.error(errorMsg);
+ }
+ }); //end ajax
+ }
+ },
+ destroy: {
+ label: 'label.delete.kubernetes.version',
+ compactLabel: 'label.delete',
+ preFilter: function(args) { return isAdmin(); },
+ createForm: {
+ title: 'label.delete.kubernetes.version',
+ desc: 'label.delete.kubernetes.version',
+ isWarning: true,
+ fields: {}
+ },
+ messages: {
+ confirm: function(args) {
+ return 'message.confirm.delete.kubernetes.version';
+ },
+ notification: function(args) {
+ return 'Deleted Kubernetes version.';
+ }
+ },
+ action: function(args) {
+ var data = {
+ id: args.context.kubernetesversions[0].id
+ };
+ $.ajax({
+ url: createURL('deleteKubernetesSupportedVersion'),
+ data: data,
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ args.response.success({
+ _custom: {
+ jobId: json.deletekubernetessupportedversionresponse.jobid,
+ getUpdatedItem: function(json) {
+ return { 'toRemove': true };
+ }
+ }
+ });
+ }
+ });
+ },
+ notification: {
+ poll: pollAsyncJobResult
+ }
+ }
+ },
+ tabs: {
+ // Details tab
+ details: {
+ title: 'label.details',
+ fields: [{
+ id: {
+ label: 'label.id'
+ },
+ name: {
+ label: 'label.name'
+ },
+ zonename: {
+ label: 'label.zone.name'
+ },
+ isoid: {
+ label: 'label.iso.id'
+ },
+ isoname: {
+ label: 'label.iso.name'
+ },
+ isostate: {
+ label: 'label.iso.name'
+ }
+ }],
+
+ dataProvider: function(args) {
+ $.ajax({
+ url: createURL("listKubernetesSupportedVersions&id=" + args.context.kubernetesversions[0].id),
+ dataType: "json",
+ async: true,
+ success: function(json) {
+ var jsonObj;
+ if (json.listkubernetessupportedversionsresponse.kubernetessupportedversion != null && json.listkubernetessupportedversionsresponse.kubernetessupportedversion.length > 0) {
+ jsonObj = json.listkubernetessupportedversionsresponse.kubernetessupportedversion[0];
+ }
+ args.response.success({
+ data: jsonObj
+ });
+ }
+ });
+ }
+ }
+ }
+ }
+ }
+ },
+ }
+ });
+ };
+
+ var cksActionfilter = cloudStack.actionFilter.cksActionfilter = function(args) {
+ var jsonObj = args.context.item;
+ var allowedActions = [];
+ if (jsonObj.state != "Destroyed" && jsonObj.state != "Destroying") {
+ if (jsonObj.state == "Stopped") {
+ allowedActions.push("start");
+ } else {
+ allowedActions.push("downloadKubernetesClusterKubeConfig");
+ allowedActions.push("stop");
+ }
+ if (jsonObj.state == "Created" || jsonObj.state == "Running") {
+ allowedActions.push("scaleKubernetesCluster");
+ allowedActions.push("upgradeKubernetesCluster");
+ }
+ allowedActions.push("destroy");
+ }
+ return allowedActions;
+ }
+
+}(cloudStack));
diff --git a/ui/plugins/cks/config.js b/ui/plugins/cks/config.js
new file mode 100644
index 00000000000..a5ea16358b1
--- /dev/null
+++ b/ui/plugins/cks/config.js
@@ -0,0 +1,25 @@
+// 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.
+(function (cloudStack) {
+ cloudStack.plugins.cks.config = {
+ title: 'Kubernetes Service',
+ desc: 'Kubernetes Service',
+ externalLink: 'http://www.cloudstack.org/',
+ authorName: 'Apache CloudStack',
+ authorEmail: 'dev@cloudstack.apache.org'
+ };
+}(cloudStack));
diff --git a/ui/plugins/cks/icon.png b/ui/plugins/cks/icon.png
new file mode 100644
index 00000000000..1d049675c27
Binary files /dev/null and b/ui/plugins/cks/icon.png differ
diff --git a/ui/plugins/plugins.js b/ui/plugins/plugins.js
index 6edfe88fe1d..30cdf4f5dac 100644
--- a/ui/plugins/plugins.js
+++ b/ui/plugins/plugins.js
@@ -18,6 +18,7 @@
cloudStack.plugins = [
//'testPlugin',
'cloudian',
- 'quota'
+ 'quota',
+ 'cks'
];
}(jQuery, cloudStack));
diff --git a/ui/scripts/sharedFunctions.js b/ui/scripts/sharedFunctions.js
index 6151a6acc6e..920dbd768f3 100644
--- a/ui/scripts/sharedFunctions.js
+++ b/ui/scripts/sharedFunctions.js
@@ -2800,16 +2800,37 @@ jQuery.validator.addMethod("ipv6CustomJqueryValidator", function(value, element)
return jQuery.validator.methods.ipv6.call(this, value, element);
}, "The specified IPv6 address is invalid.");
-
$.validator.addMethod("allzonesonly", function(value, element){
- if ((value.indexOf("-1") != -1) &&(value.length > 1))
+ if ((value.indexOf("-1") != -1) && (value.length > 1))
return false;
return true;
},
"All Zones cannot be combined with any other zone");
+$.validator.addMethod("naturalnumber", function(value, element){
+ if (this.optional(element) && value.length == 0)
+ return true;
+ if (isNaN(value))
+ return false;
+ value = parseInt(value);
+ return (typeof value === 'number') && (value > 0) && (Math.floor(value) === value) && value !== Infinity;
+
+},
+"Please enter a valid number, 1 or greater");
+
+$.validator.addMethod("multiplecountnumber", function(value, element){
+ if (this.optional(element) && value.length == 0)
+ return true;
+ if (isNaN(value))
+ return false;
+ value = parseInt(value);
+ return (typeof value === 'number') && (value > 1) && (Math.floor(value) === value) && value !== Infinity;
+
+},
+"Please enter a valid number, 2 or greater");
+
cloudStack.createTemplateMethod = function (isSnapshot){
return {
label: 'label.create.template',