Skip to content

Commit eb960e3

Browse files
committed
Add KVM script cleanup to test and update README with VPC/ACL support
- Add remove_file() method to KvmHostDeployer to cleanup wrapper scripts from KVM hosts after tests - Add _cleanup_kvm_script() method to test class, called from _safe_teardown() - Update README.md with documentation for: - VPC support (shared namespaces, host affinity) - implement-vpc, shutdown-vpc, destroy-vpc VPC-level commands - apply-network-acl command for VPC Network ACLs - VPC-specific iptables chains for ACL filtering
1 parent 4b93c63 commit eb960e3

2 files changed

Lines changed: 144 additions & 4 deletions

File tree

extensions/network-namespace/README.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,56 @@ Actions (superset of shutdown):
743743
> are NOT removed on destroy — they may still be used by other networks or for
744744
> VM connectivity.
745745
746+
### VPC lifecycle commands: `implement-vpc`, `shutdown-vpc`, `destroy-vpc`
747+
748+
These commands manage VPC-level state. Called by `NetworkExtensionElement` when
749+
implementing, shutting down, or destroying a VPC (before or after per-tier
750+
network operations).
751+
752+
#### `implement-vpc`
753+
754+
```
755+
network-namespace-wrapper.sh implement-vpc \
756+
--vpc-id <vpc-id> \
757+
--cidr <vpc-cidr>
758+
```
759+
760+
Actions:
761+
1. Create the shared VPC namespace `cs-vpc-<vpc-id>`.
762+
2. Enable IP forwarding inside the namespace.
763+
3. Create iptables chains for NAT and filter rules.
764+
4. Save VPC metadata (CIDR, gateway) to state files under `/var/lib/cloudstack/<ext-name>/vpc-<vpc-id>/`.
765+
766+
> This command runs **before** any tier networks are implemented. Tier networks
767+
> inherit the same namespace and host assignment.
768+
769+
#### `shutdown-vpc`
770+
771+
```
772+
network-namespace-wrapper.sh shutdown-vpc \
773+
--vpc-id <vpc-id>
774+
```
775+
776+
Actions:
777+
1. Flush all iptables rules (ingress, egress, NAT chains inside the namespace).
778+
2. Stop all services (dnsmasq, haproxy, apache2, password-server) for all tiers.
779+
3. Keep the namespace and tier veths intact (tiers may restart).
780+
781+
> Called when the VPC is shut down; tier networks may be restarted later.
782+
783+
#### `destroy-vpc`
784+
785+
```
786+
network-namespace-wrapper.sh destroy-vpc \
787+
--vpc-id <vpc-id>
788+
```
789+
790+
Actions:
791+
1. Remove the entire namespace `cs-vpc-<vpc-id>` (deletes all interfaces inside).
792+
2. Remove VPC-wide state directory `/var/lib/cloudstack/<ext-name>/vpc-<vpc-id>/`.
793+
794+
> This is the final cleanup step; after this, the VPC namespace is gone.
795+
746796
### `assign-ip`
747797

748798
Called when a public IP is associated with the network (including source NAT).
@@ -929,6 +979,55 @@ iptables design (two independent parts, both inside the namespace):
929979
`default_egress_allow` policy (allow-by-default or deny-by-default) to VM
930980
outbound traffic on `-i vn-<vlan>-<id>`.
931981

982+
### `apply-network-acl`
983+
984+
Apply Network ACL (Access Control List) rules for VPC networks.
985+
986+
```
987+
network-namespace-wrapper.sh apply-network-acl \
988+
--network-id <id> \
989+
--vlan <vlan-id> \
990+
--acl-rules <base64-json> \
991+
[--vpc-id <vpc-id>]
992+
```
993+
994+
The `--acl-rules` value is a Base64-encoded JSON array of ACL rule objects:
995+
```json
996+
[
997+
{
998+
"id": 1,
999+
"number": 100,
1000+
"trafficType": "Ingress",
1001+
"action": "Allow",
1002+
"protocol": "tcp",
1003+
"portStart": 80,
1004+
"portEnd": 80,
1005+
"sourceCidrs": ["0.0.0.0/0"]
1006+
},
1007+
{
1008+
"id": 2,
1009+
"number": 200,
1010+
"trafficType": "Egress",
1011+
"action": "Allow",
1012+
"protocol": "all",
1013+
"destCidrs": ["0.0.0.0/0"]
1014+
}
1015+
]
1016+
```
1017+
1018+
iptables design:
1019+
1020+
* **Ingress rules** (filter FORWARD, chain `CS_EXTNET_ACL_IN_<networkId>`):
1021+
Matches `-i vn-<vlan>-<id>` (traffic entering the VM namespace),
1022+
ordered by rule number. Actions: ACCEPT or DROP.
1023+
1024+
* **Egress rules** (filter FORWARD, chain `CS_EXTNET_ACL_OUT_<networkId>`):
1025+
Matches `-o vn-<vlan>-<id>` (traffic leaving the VM namespace),
1026+
ordered by rule number. Actions: ACCEPT or DROP.
1027+
1028+
Both chains are inserted at position 1 of `CS_EXTNET_FWD_<networkId>` so ACL rules
1029+
take precedence over the catch-all ACCEPT rules.
1030+
9321031
### `config-dhcp-subnet` / `remove-dhcp-subnet`
9331032

9341033
Configure or tear down dnsmasq DHCP service for the network inside the namespace.
@@ -1226,6 +1325,23 @@ argument; hook scripts should parse the JSON argument as needed.
12261325

12271326
## Developer / testing notes
12281327

1328+
### VPC Support
1329+
1330+
The extension now supports **VPC (Virtual Private Cloud)** networks in addition to
1331+
isolated networks. Key differences from isolated networks:
1332+
1333+
* **Namespace sharing**: All tiers of a VPC share a single namespace (`cs-vpc-<vpcId>`)
1334+
instead of each network getting its own (`cs-net-<networkId>`).
1335+
* **Host affinity**: All tiers of a VPC land on the same KVM host via stable hash-based
1336+
selection using the VPC ID as the routing key.
1337+
* **VPC-level operations**: `implement-vpc`, `shutdown-vpc`, `destroy-vpc` commands
1338+
manage VPC-wide state (namespace creation/teardown).
1339+
* **VPC tier operations**: `implement-network`, `shutdown-network`, `destroy-network`
1340+
commands manage per-tier bridges and routes; the namespace is preserved across
1341+
tier lifecycle operations.
1342+
1343+
### Integration tests
1344+
12291345
The integration smoke test at
12301346
`test/integration/smoke/test_network_extension_namespace.py`
12311347
exercises the full lifecycle against real KVM hosts in the zone.
@@ -1248,10 +1364,12 @@ The test covers:
12481364
* Create / list / update / delete external network device.
12491365
* Full network lifecycle: implement → assign-ip (source NAT) → static NAT →
12501366
port forwarding → firewall rules → DHCP/DNS → shutdown / destroy.
1367+
* VPC multi-tier networks with shared namespace and automatic host affinity.
12511368
* NSP state transitions: Disabled → Enabled → Disabled → Deleted.
12521369
* Tests `test_04`, `test_05`, `test_06` (DHCP, DNS, LB) require `arping`,
12531370
`dnsmasq`, and `haproxy` on the KVM hosts; the test skips them automatically
12541371
if these tools are not installed.
1372+
* Script cleanup on both management server and KVM hosts after each test.
12551373

12561374
Run the test:
12571375
```bash

test/integration/smoke/test_network_extension_namespace.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,25 @@ def host_ips_csv(self):
348348
"""Return comma-separated IP list of all configured hosts."""
349349
return ','.join(h.get('ip', '') for h in self.config_hosts if h.get('ip'))
350350

351+
def remove_file(self, remote_path):
352+
"""Remove wrapper script from all deployed KVM hosts."""
353+
if not self._deployed_hosts:
354+
self.logger.debug("No KVM hosts deployed — skipping remove_file")
355+
return
356+
for h in self.config_hosts:
357+
ip = h.get('ip', '')
358+
username = h.get('username', 'root')
359+
password = h.get('password', '')
360+
if not ip or ip not in self._deployed_hosts:
361+
continue
362+
try:
363+
SshClient(ip, 22, username, password).execute(
364+
"rm -f '%s'" % remote_path)
365+
self.logger.info("Removed %s from KVM host %s", remote_path, ip)
366+
except Exception as e:
367+
self.logger.warning("Could not remove %s from %s: %s",
368+
remote_path, ip, e)
369+
351370

352371
# ---------------------------------------------------------------------------
353372
# Test class
@@ -485,10 +504,6 @@ def setUp(self):
485504
self.kvm_deployer = None
486505
self._ssh_private_key_file = None
487506

488-
# Skip every test when no KVM hosts are available in the Marvin config.
489-
if not self.kvm_host_configs:
490-
self.skipTest("No KVM hosts configured — skipping")
491-
492507
def tearDown(self):
493508
#self._safe_teardown()
494509
try:
@@ -613,6 +628,11 @@ def _cleanup_mgmt_script(self):
613628
self.mgmt_deployer.remove_file(self._mgmt_script_path)
614629
self._mgmt_script_path = None
615630

631+
def _cleanup_kvm_script(self):
632+
"""Remove KVM wrapper script from all deployed KVM hosts."""
633+
if self.kvm_deployer and self.kvm_deployer._dest_path:
634+
self.kvm_deployer.remove_file(self.kvm_deployer._dest_path)
635+
616636
# ------------------------------------------------------------------
617637
# Teardown helper
618638
# ------------------------------------------------------------------
@@ -643,6 +663,7 @@ def _safe_teardown(self):
643663
pass
644664
self.extension = None
645665
self._cleanup_mgmt_script()
666+
self._cleanup_kvm_script()
646667

647668
# ------------------------------------------------------------------
648669
# SSH helpers (provider-agnostic — no KVM namespace checks)
@@ -1014,6 +1035,7 @@ def _teardown_extension(self):
10141035
self.extension = None
10151036
self.physical_network = None
10161037
self._cleanup_mgmt_script()
1038+
self._cleanup_kvm_script()
10171039

10181040
# ------------------------------------------------------------------
10191041
# Tests — API-only (no KVM / no SSH)

0 commit comments

Comments
 (0)