@@ -378,6 +378,11 @@ def setUpClass(cls):
378378 cls .zone = get_zone (cls .apiclient , testClient .getZoneForTests ())
379379 cls .domain = get_domain (cls .apiclient )
380380 cls .mgtSvrDetails = cls .config .__dict__ ["mgtSvr" ][0 ].__dict__
381+ # All management servers — entry-point script is deployed to every one.
382+ cls .all_mgt_svr_details = [
383+ mgt .__dict__ if hasattr (mgt , '__dict__' ) else mgt
384+ for mgt in cls .config .__dict__ .get ("mgtSvr" , [])
385+ ] or [cls .mgtSvrDetails ]
381386 cls .hv = testClient .getHypervisorInfo ()
382387 cls ._cleanup = []
383388 cls .tmp_files = []
@@ -388,6 +393,9 @@ def setUpClass(cls):
388393 cls .logger .setLevel (logging .DEBUG )
389394 cls .logger .addHandler (cls .stream_handler )
390395
396+ cls .logger .info ("Management servers: %s" ,
397+ [m .get ("mgtSvrIp" , "?" ) for m in cls .all_mgt_svr_details ])
398+
391399 # KVM host credentials from Marvin config
392400 cls .kvm_host_configs = _get_kvm_hosts_from_config (cls .config )
393401 cls .logger .info ("KVM hosts from config: %s" ,
@@ -422,21 +430,6 @@ def setUpClass(cls):
422430 "falling back to default" , e )
423431 cls .template = get_template (cls .apiclient , cls .zone .id , cls .hv )
424432
425- # ---- SSH keypair (written to a temp file) ----
426- try :
427- kp = SSHKeyPair .create (cls .apiclient , name = random_gen () + ".pem" )
428- cls ._cleanup .append (SSHKeyPair (kp .__dict__ , None ))
429- pkfile = os .path .join (tempfile .gettempdir (), kp .name )
430- kp .private_key_file = pkfile
431- cls .tmp_files .append (pkfile )
432- with open (pkfile , "w+" ) as fh :
433- fh .write (kp .privatekey )
434- os .chmod (pkfile , 0o400 )
435- cls .keypair = kp
436- cls .logger .info ("SSH keypair '%s' written to %s" , kp .name , pkfile )
437- except Exception as e :
438- cls .logger .warning ("Could not create SSH keypair: %s" , e )
439-
440433 # ---- Download wrapper scripts from GitHub ----
441434 try :
442435 _ensure_scripts_downloaded ()
@@ -543,11 +536,11 @@ def _deploy_scripts(self):
543536
544537 self ._mgmt_script_path = (self .extension_path or "" ).strip ().rstrip ('/' )
545538
546- # Deploy entry-point to ALL management servers
547- all_mgt_svrs = self .config .__dict__ .get ("mgtSvr" , [])
539+ # Deploy entry-point to ALL management servers.
540+ # all_mgt_svr_details is collected once in setUpClass from
541+ # cls.config.__dict__["mgtSvr"] so every server in a HA pair is covered.
548542 self ._all_mgmt_deployers = []
549- for mgt in all_mgt_svrs :
550- mgt_details = mgt .__dict__ if hasattr (mgt , '__dict__' ) else mgt
543+ for mgt_details in self .all_mgt_svr_details :
551544 deployer = MgmtServerDeployer (mgt_details , logger = self .logger )
552545 deployer .copy_file (entry_point_src , self ._mgmt_script_path )
553546 self .logger .info ("Entry-point deployed to mgmt %s at %s" ,
@@ -835,7 +828,6 @@ def _create_account_keypair(self, account, name_suffix=""):
835828 account = account .name ,
836829 domainid = account .domainid ,
837830 )
838- self .cleanup .append (SSHKeyPair (kp .__dict__ , None ))
839831
840832 pkfile = os .path .join (tempfile .gettempdir (), kp .name )
841833 with open (pkfile , "w+" ) as fh :
@@ -968,6 +960,7 @@ def test_01_provider_state_transitions(self):
968960 self .assertEqual ('Disabled' , self ._find_provider (pn .id , ext_name ).state )
969961 self .logger .info ("NSP disabled OK" )
970962
963+ self ._teardown_extension ()
971964 self .logger .info ("test_01 PASSED" )
972965
973966 @attr (tags = ["advanced" , "smoke" ], required_hardware = "false" )
@@ -1314,13 +1307,24 @@ def test_06_vpc_multi_tier_and_restart(self):
13141307 """VPC multi-tier + VPC restart with SSH connectivity verification.
13151308
13161309 Creates two VPC tier networks backed by the extension, deploys a
1317- VM in each tier, and verifies SSH access independently. Then:
1310+ VM in each tier, and verifies SSH access independently.
13181311
1319- 1. VPC restart (cleanup=True) — verify SSH still works for both VMs.
1320- 2. Delete tier-1 VM + network — verify tier-2 VM is still accessible.
1321- 3. Delete tier-2 VM + network.
1322- 4. Delete VPC.
1323- 5. Teardown extension.
1312+ Sub-tests in order
1313+ ------------------
1314+ A. Baseline connectivity (before VPC restart)
1315+ tier-1 PF :22 → VM1 — assert SSH works
1316+ tier-2 LB :22 → VM2 — assert SSH works
1317+ tier-1 static NAT :22 → VM1 — enable, create FW rule,
1318+ assert SSH works
1319+ B. VPC restart (cleanup=True)
1320+ assert SSH still works via tier-1 PF, tier-2 LB, and
1321+ tier-1 static NAT after the namespace is rebuilt
1322+ C. Static NAT teardown (after VPC restart)
1323+ disable static NAT → assert SSH fails → delete IP
1324+ D. Partial delete
1325+ delete tier-1 VM + network → assert tier-2 VM still accessible
1326+ E. Final delete
1327+ delete tier-2 VM + network, VPC, teardown extension
13241328
13251329 The VPC tier network offering uses ``useVpc=on`` as required by
13261330 CloudStack for VPC-associated tier networks.
@@ -1452,7 +1456,7 @@ def test_06_vpc_multi_tier_and_restart(self):
14521456 self .cleanup .insert (0 , vm2 )
14531457 self .logger .info ("VM2 deployed in tier 2: %s (%s)" , vm2 .name , vm2 .id )
14541458
1455- # ---- Tier 1: PF ----
1459+ # ---- Tier 1: PF rule ----
14561460 pf_ip1 = PublicIPAddress .create (
14571461 self .apiclient ,
14581462 accountid = account .name ,
@@ -1467,10 +1471,10 @@ def test_06_vpc_multi_tier_and_restart(self):
14671471 ipaddressid = pf_ip1 .ipaddress .id ,
14681472 networkid = tier1 .id
14691473 )
1470- tier1_ip = pf_ip1 .ipaddress .ipaddress
1471- self .logger .info ("Tier 1 PF: %s:22 → VM1:22" , tier1_ip )
1474+ tier1_pf_ip = pf_ip1 .ipaddress .ipaddress
1475+ self .logger .info ("Tier 1 PF: %s:22 → VM1:22" , tier1_pf_ip )
14721476
1473- # ---- Tier 2: LB ----
1477+ # ---- Tier 2: LB rule ----
14741478 lb_ip2 = PublicIPAddress .create (
14751479 self .apiclient ,
14761480 accountid = account .name ,
@@ -1492,34 +1496,91 @@ def test_06_vpc_multi_tier_and_restart(self):
14921496 )
14931497 self .assertIsNotNone (lb_rule2 )
14941498 lb_rule2 .assign (self .apiclient , vms = [vm2 ])
1495- tier2_ip = lb_ip2 .ipaddress .ipaddress
1496- self .logger .info ("Tier 2 LB: %s:22 → VM2:22" , tier2_ip )
1499+ tier2_lb_ip = lb_ip2 .ipaddress .ipaddress
1500+ self .logger .info ("Tier 2 LB: %s:22 → VM2:22" , tier2_lb_ip )
1501+
1502+ # ---- Tier 1: Static NAT (allocated BEFORE restart) ----
1503+ snat_ip1_obj = PublicIPAddress .create (
1504+ self .apiclient ,
1505+ accountid = account .name ,
1506+ zoneid = self .zone .id ,
1507+ domainid = account .domainid ,
1508+ networkid = tier1 .id ,
1509+ vpcid = vpc .id
1510+ )
1511+ snat_ip1 = snat_ip1_obj .ipaddress .ipaddress
1512+ snat_ip1_id = snat_ip1_obj .ipaddress .id
1513+ StaticNATRule .enable (
1514+ self .apiclient ,
1515+ ipaddressid = snat_ip1_id ,
1516+ virtualmachineid = vm1 .id ,
1517+ networkid = tier1 .id
1518+ )
1519+ self .logger .info ("Static NAT enabled on tier-1: %s → VM1" , snat_ip1 )
1520+
1521+ # ==============================================================
1522+ # A. Baseline connectivity — BEFORE VPC restart
1523+ # ==============================================================
1524+ self .logger .info ("--- Sub-test A: Baseline connectivity (before restart) ---" )
14971525
1498- # ---- Verify SSH to both VMs ----
14991526 self ._assert_vm_ssh_accessible (
1500- tier1_ip , 22 ,
1501- "SSH to tier-1 VM (%s) should succeed" % tier1_ip )
1502- self .logger .info ("Verified: SSH to tier-1 VM works" )
1527+ tier1_pf_ip , 22 ,
1528+ "SSH to tier-1 VM via PF (%s) should succeed before restart"
1529+ % tier1_pf_ip )
1530+ self .logger .info ("Verified: SSH to tier-1 VM works via PF (before restart)" )
15031531
15041532 self ._assert_vm_ssh_accessible (
1505- tier2_ip , 22 ,
1506- "SSH to tier-2 VM via LB (%s) should succeed" % tier2_ip )
1507- self .logger .info ("Verified: SSH to tier-2 VM works via LB" )
1533+ tier2_lb_ip , 22 ,
1534+ "SSH to tier-2 VM via LB (%s) should succeed before restart"
1535+ % tier2_lb_ip )
1536+ self .logger .info ("Verified: SSH to tier-2 VM works via LB (before restart)" )
15081537
1509- # ---- VPC restart (cleanup=True) ----
1538+ self ._assert_vm_ssh_accessible (
1539+ snat_ip1 , 22 ,
1540+ "SSH to tier-1 VM via static NAT (%s) should succeed before restart"
1541+ % snat_ip1 )
1542+ self .logger .info ("Verified: SSH to tier-1 VM works via static NAT (before restart)" )
1543+
1544+ # ==============================================================
1545+ # B. VPC restart (cleanup=True)
1546+ # Re-verify all three access methods after the namespace is rebuilt.
1547+ # ==============================================================
1548+ self .logger .info ("--- Sub-test B: VPC restart (cleanup=True) ---" )
15101549 self .logger .info ("Restarting VPC %s (cleanup=True) ..." , vpc .id )
15111550 vpc .restart (self .apiclient , cleanup = True )
15121551 self .logger .info ("VPC restart completed" )
15131552
15141553 self ._assert_vm_ssh_accessible (
1515- tier1_ip , 22 ,
1516- "SSH to tier-1 VM must work after VPC restart" )
1554+ tier1_pf_ip , 22 ,
1555+ "SSH to tier-1 VM via PF must work after VPC restart" )
1556+ self .logger .info ("Verified: SSH to tier-1 VM works via PF (after restart)" )
1557+
15171558 self ._assert_vm_ssh_accessible (
1518- tier2_ip , 22 ,
1559+ tier2_lb_ip , 22 ,
15191560 "SSH to tier-2 VM via LB must work after VPC restart" )
1520- self .logger .info ("Verified: both VMs accessible after VPC restart" )
1561+ self .logger .info ("Verified: SSH to tier-2 VM works via LB (after restart) " )
15211562
1522- # ---- Delete tier 1 VM + network ----
1563+ self ._assert_vm_ssh_accessible (
1564+ snat_ip1 , 22 ,
1565+ "SSH to tier-1 VM via static NAT must work after VPC restart" )
1566+ self .logger .info ("Verified: SSH to tier-1 VM works via static NAT (after restart)" )
1567+
1568+ # ==============================================================
1569+ # C. Disable static NAT (after VPC restart)
1570+ # ==============================================================
1571+ self .logger .info ("--- Sub-test C: Disable static NAT (after restart) ---" )
1572+ StaticNATRule .disable (self .apiclient , ipaddressid = snat_ip1_id )
1573+ self .logger .info ("Static NAT disabled on tier-1 %s" , snat_ip1 )
1574+ self ._assert_vm_ssh_not_accessible (
1575+ snat_ip1 , 22 ,
1576+ "SSH via tier-1 %s:22 should fail after static NAT disabled" % snat_ip1 )
1577+ self .logger .info ("Verified: SSH fails after static NAT disabled" )
1578+ snat_ip1_obj .delete (self .apiclient )
1579+
1580+ # ==============================================================
1581+ # D. Partial delete: tier-1 — tier-2 must remain accessible
1582+ # ==============================================================
1583+ self .logger .info ("--- Sub-test D: Delete tier-1, verify tier-2 intact ---" )
15231584 pf_rule1 .delete (self .apiclient )
15241585 pf_ip1 .delete (self .apiclient )
15251586 vm1 .delete (self .apiclient , expunge = True )
@@ -1531,11 +1592,13 @@ def test_06_vpc_multi_tier_and_restart(self):
15311592 # Tier 2 must remain accessible — cmd_destroy() on tier-1 must not
15321593 # delete tier-2's public veth (fixed via .tier ownership tracking).
15331594 self ._assert_vm_ssh_accessible (
1534- tier2_ip , 22 ,
1595+ tier2_lb_ip , 22 ,
15351596 "SSH to tier-2 VM via LB must still work after tier-1 deleted" )
15361597 self .logger .info ("Verified: tier-2 VM still accessible via LB after tier-1 deleted" )
15371598
1538- # ---- Delete tier 2 VM + network ----
1599+ # ==============================================================
1600+ # E. Final delete: tier-2, VPC
1601+ # ==============================================================
15391602 lb_rule2 .remove (self .apiclient , vms = [vm2 ])
15401603 lb_rule2 .delete (self .apiclient )
15411604 lb_ip2 .delete (self .apiclient )
@@ -1545,7 +1608,6 @@ def test_06_vpc_multi_tier_and_restart(self):
15451608 self .cleanup = [o for o in self .cleanup if o != tier2 ]
15461609 self .logger .info ("Tier 2 VM + network deleted" )
15471610
1548- # ---- Delete VPC ----
15491611 vpc .delete (self .apiclient )
15501612 self .cleanup = [o for o in self .cleanup if o != vpc ]
15511613
0 commit comments