3535 destroyVirtualMachine ,
3636 deleteNetwork ,
3737 addVirtualMachinesToKubernetesCluster ,
38- removeVirtualMachinesFromKubernetesCluster )
38+ removeVirtualMachinesFromKubernetesCluster ,
39+ addNodesToKubernetesCluster ,
40+ removeNodesFromKubernetesCluster )
3941from marvin .cloudstackException import CloudstackAPIException
4042from marvin .codes import PASS , FAILED
4143from marvin .lib .base import (Template ,
4951 VPC ,
5052 NetworkACLList ,
5153 NetworkACL ,
52- VirtualMachine )
54+ VirtualMachine ,
55+ PublicIPAddress ,
56+ FireWallRule ,
57+ NATRule )
5358from marvin .lib .utils import (cleanup_resources ,
5459 validateList ,
5560 random_gen )
5661from marvin .lib .common import (get_zone ,
5762 get_domain ,
58- get_template )
63+ get_template ,
64+ get_test_template )
5965from marvin .sshClient import SshClient
6066from nose .plugins .attrib import attr
6167from marvin .lib .decoratorGenerators import skipTestIf
6268
6369from kubernetes import client , config
64- import time , io , yaml
70+ import time , io , yaml , random
6571
6672_multiprocess_shared_ = True
6773
6874k8s_cluster = None
75+ k8s_cluster_node_offerings = None
6976VPC_DATA = {
7077 "cidr" : "10.1.0.0/22" ,
7178 "tier1_gateway" : "10.1.1.1" ,
7279 "tier_netmask" : "255.255.255.0"
7380}
81+ RAND_SUFFIX = random_gen ()
82+ NODES_TEMPLATE = {
83+ "kvm" : {
84+ "name" : "cks-u2204-kvm-" + RAND_SUFFIX ,
85+ "displaytext" : "cks-u2204-kvm-" + RAND_SUFFIX ,
86+ "format" : "qcow2" ,
87+ "hypervisor" : "kvm" ,
88+ "ostypeid" : "5d83ac5d-d03c-4743-9629-7d70b5928f7f" ,
89+ "url" : "https://download.cloudstack.org/testing/custom_templates/ubuntu/22.04/cks-ubuntu-2204-kvm.qcow2.bz2" ,
90+ "requireshvm" : "True" ,
91+ "ispublic" : "True" ,
92+ "isextractable" : "True" ,
93+ "forcks" : "True"
94+ },
95+ "xenserver" : {
96+ "name" : "cks-u2204-hyperv-" + RAND_SUFFIX ,
97+ "displaytext" : "cks-u2204-hyperv-" + RAND_SUFFIX ,
98+ "format" : "vhd" ,
99+ "hypervisor" : "xenserver" ,
100+ "ostypeid" : "5d83ac5d-d03c-4743-9629-7d70b5928f7f" ,
101+ "url" : "https://download.cloudstack.org/testing/custom_templates/ubuntu/22.04/cks-ubuntu-2204-hyperv.vhd.zip" ,
102+ "requireshvm" : "True" ,
103+ "ispublic" : "True" ,
104+ "isextractable" : "True" ,
105+ "forcks" : "True"
106+ },
107+ "hyperv" : {
108+ "name" : "cks-u2204-hyperv-" + RAND_SUFFIX ,
109+ "displaytext" : "cks-u2204-hyperv-" + RAND_SUFFIX ,
110+ "format" : "vhd" ,
111+ "hypervisor" : "hyperv" ,
112+ "ostypeid" : "5d83ac5d-d03c-4743-9629-7d70b5928f7f" ,
113+ "url" : "https://download.cloudstack.org/testing/custom_templates/ubuntu/22.04/cks-ubuntu-2204-hyperv.vhd.zip" ,
114+ "requireshvm" : "True" ,
115+ "ispublic" : "True" ,
116+ "isextractable" : "True" ,
117+ "forcks" : "True"
118+ },
119+ "vmware" : {
120+ "name" : "cks-u2204-vmware-" + RAND_SUFFIX ,
121+ "displaytext" : "cks-u2204-vmware-" + RAND_SUFFIX ,
122+ "format" : "ova" ,
123+ "hypervisor" : "vmware" ,
124+ "ostypeid" : "5d83ac5d-d03c-4743-9629-7d70b5928f7f" ,
125+ "url" : "https://download.cloudstack.org/testing/custom_templates/ubuntu/22.04/cks-ubuntu-2204-vmware.ova" ,
126+ "requireshvm" : "True" ,
127+ "ispublic" : "True" ,
128+ "isextractable" : "True" ,
129+ "forcks" : "True"
130+ }
131+ }
74132
75133class TestKubernetesCluster (cloudstackTestCase ):
76134
@@ -84,6 +142,7 @@ def setUpClass(cls):
84142 cls .mgtSvrDetails = cls .config .__dict__ ["mgtSvr" ][0 ].__dict__
85143
86144 cls .hypervisorNotSupported = False
145+ cls .hypervisorIsNotVmware = cls .hypervisor .lower () != "vmware"
87146 if cls .hypervisor .lower () not in ["kvm" , "vmware" , "xenserver" ]:
88147 cls .hypervisorNotSupported = True
89148 cls .setup_failed = False
@@ -129,6 +188,15 @@ def setUpClass(cls):
129188 (cls .services ["cks_kubernetes_versions" ][cls .k8s_version_to ]["semanticversion" ], cls .services ["cks_kubernetes_versions" ][cls .k8s_version_to ]["url" ], e ))
130189
131190 if cls .setup_failed == False :
191+ cls .nodes_template = None
192+ cls .mgmtSshKey = None
193+ if cls .hypervisor .lower () == "vmware" :
194+ cls .nodes_template = get_test_template (cls .apiclient ,
195+ cls .zone .id ,
196+ cls .hypervisor ,
197+ NODES_TEMPLATE )
198+ cls .nodes_template .update (cls .apiclient , forcks = True )
199+ cls .mgmtSshKey = cls .getMgmtSshKey ()
132200 cks_offering_data = cls .services ["cks_service_offering" ]
133201 cks_offering_data ["name" ] = 'CKS-Instance-' + random_gen ()
134202 cls .cks_service_offering = ServiceOffering .create (
@@ -222,6 +290,19 @@ def updateVmwareSettings(cls, tearDown):
222290 name = "vmware.create.full.clone" ,
223291 value = value )
224292
293+ @classmethod
294+ def getMgmtSshKey (cls ):
295+ """Get the management server SSH public key"""
296+ sshClient = SshClient (
297+ cls .mgtSvrDetails ["mgtSvrIp" ],
298+ 22 ,
299+ cls .mgtSvrDetails ["user" ],
300+ cls .mgtSvrDetails ["passwd" ]
301+ )
302+ command = "cat /var/cloudstack/management/.ssh/id_rsa.pub"
303+ response = sshClient .execute (command )
304+ return str (response [0 ])
305+
225306 @classmethod
226307 def restartServer (cls ):
227308 """Restart management server"""
@@ -666,8 +747,12 @@ def test_11_test_unmanaged_cluster_lifecycle(self):
666747 def test_12_test_deploy_cluster_different_offerings_per_node_type (self ):
667748 """Test creating a CKS cluster with different offerings per node type
668749
669- # Validate the following:
750+ # Validate the following on Kubernetes cluster creation:
751+ # - Use a service offering for control nodes
752+ # - Use a service offering for worker nodes
670753 """
754+ if self .setup_failed == True :
755+ self .fail ("Setup incomplete" )
671756 cluster = self .getValidKubernetesCluster (worker_offering = self .cks_worker_nodes_offering ,
672757 control_offering = self .cks_control_nodes_offering )
673758 self .assertEqual (
@@ -685,6 +770,117 @@ def test_12_test_deploy_cluster_different_offerings_per_node_type(self):
685770 0 ,
686771 "No Etcd Nodes expected but got {}" .format (cluster .etcdnodes )
687772 )
773+ self .debug ("Deleting Kubernetes cluster with ID: %s" % cluster .id )
774+ self .deleteKubernetesClusterAndVerify (cluster .id )
775+ return
776+
777+ @attr (tags = ["advanced" , "smoke" ], required_hardware = "true" )
778+ @skipTestIf ("hypervisorIsNotVmware" )
779+ def test_13_test_add_external_nodes_to_cluster (self ):
780+ """Test creating a CKS cluster with different offerings per node type
781+
782+ # Validate the following:
783+ # - Deploy Kubernetes Cluster
784+ # - Deploy VM on the same network as the Kubernetes cluster with the worker nodes offering and CKS ready template
785+ # - Add external node to the Kubernetes Cluster
786+ """
787+ if self .setup_failed == True :
788+ self .fail ("Setup incomplete" )
789+ cluster = self .getValidKubernetesCluster (worker_offering = self .cks_worker_nodes_offering ,
790+ control_offering = self .cks_control_nodes_offering )
791+ self .assertEqual (
792+ cluster .size ,
793+ 1 ,
794+ "Expected 1 worker node but got {}" .format (cluster .size )
795+ )
796+ self .services ["virtual_machine" ]["template" ] = self .nodes_template .id
797+ external_node = VirtualMachine .create (self .apiclient ,
798+ self .services ["virtual_machine" ],
799+ zoneid = self .zone .id ,
800+ accountid = self .account .name ,
801+ domainid = self .account .domainid ,
802+ serviceofferingid = self .cks_worker_nodes_offering .id ,
803+ networkids = cluster .networkid )
804+
805+ # Acquire public IP and create Port Forwarding Rule and Firewall rule for SSH access
806+ free_ip_addresses = PublicIPAddress .list (
807+ self .apiclient ,
808+ domainid = self .account .domainid ,
809+ account = self .account .name ,
810+ forvirtualnetwork = True ,
811+ state = 'Free'
812+ )
813+ random .shuffle (free_ip_addresses )
814+ external_node_ip = free_ip_addresses [0 ]
815+ external_node_ipaddress = PublicIPAddress .create (
816+ self .apiclient ,
817+ zoneid = self .zone .id ,
818+ networkid = cluster .networkid ,
819+ ipaddress = external_node_ip .ipaddress
820+ )
821+ self .debug ("Creating Firewall rule for VM ID: %s" % external_node .id )
822+ fw_rule = FireWallRule .create (
823+ self .apiclient ,
824+ ipaddressid = external_node_ip .id ,
825+ protocol = 'TCP' ,
826+ cidrlist = ['0.0.0.0/0' ],
827+ startport = 22 ,
828+ endport = 22
829+ )
830+ pf_rule = {
831+ "privateport" : 22 ,
832+ "publicport" : 22 ,
833+ "protocol" : "TCP"
834+ }
835+ nat_rule = NATRule .create (
836+ self .apiclient ,
837+ external_node ,
838+ pf_rule ,
839+ ipaddressid = external_node_ip .id
840+ )
841+
842+ # Add the management server SSH key to the authorized hosts on the external node
843+ node_ssh_client = SshClient (
844+ external_node_ip .ipaddress ,
845+ 22 ,
846+ 'cloud' ,
847+ 'cloud' ,
848+ retries = 30 ,
849+ delay = 10
850+ )
851+ node_ssh_client .execute ("echo '" + self .mgmtSshKey + "' > ~/.ssh/authorized_keys" )
852+
853+ self .addExternalNodesToKubernetesCluster (cluster .id , [external_node .id ])
854+ self .assertEqual (
855+ cluster .size ,
856+ 2 ,
857+ "Expected 2 worker nodes but got {}" .format (cluster .size )
858+ )
859+ self .removeExternalNodesFromKubernetesCluster (cluster .id , [external_node .id ])
860+ self .assertEqual (
861+ cluster .size ,
862+ 1 ,
863+ "Expected 1 worker node but got {}" .format (cluster .size )
864+ )
865+ nat_rule .delete (self .apiclient )
866+ fw_rule .delete (self .apiclient )
867+ external_node_ipaddress .delete (self .apiclient )
868+ VirtualMachine .delete (external_node , self .apiclient , expunge = True )
869+ self .debug ("Deleting Kubernetes cluster with ID: %s" % cluster .id )
870+ self .deleteKubernetesClusterAndVerify (cluster .id )
871+ return
872+
873+ def addExternalNodesToKubernetesCluster (self , cluster_id , vm_list ):
874+ cmd = addNodesToKubernetesCluster .addNodesToKubernetesClusterCmd ()
875+ cmd .id = cluster_id
876+ cmd .nodeids = vm_list
877+ return self .apiclient .addNodesToKubernetesCluster (cmd )
878+
879+ def removeExternalNodesFromKubernetesCluster (self , cluster_id , vm_list ):
880+ cmd = removeNodesFromKubernetesCluster .removeNodesFromKubernetesClusterCmd ()
881+ cmd .id = cluster_id
882+ cmd .nodeids = vm_list
883+ return self .apiclient .removeNodesFromKubernetesCluster (cmd )
688884
689885 def addVirtualMachinesToKubernetesCluster (self , cluster_id , vm_list ):
690886 cmd = addVirtualMachinesToKubernetesCluster .addVirtualMachinesToKubernetesClusterCmd ()
0 commit comments