This is an automated email from the ASF dual-hosted git repository.

weizhouapache pushed a commit to branch network-namespace
in repository https://gitbox.apache.org/repos/asf/cloudstack-extensions.git


The following commit(s) were added to refs/heads/network-namespace by this push:
     new c1fc905  NE: remove host bridge and VLAN sub-interface if no VMs
c1fc905 is described below

commit c1fc905577035635f0430d67a59583ad8c30ff3a
Author: Wei Zhou <[email protected]>
AuthorDate: Mon Jul 27 17:49:01 2026 +0200

    NE: remove host bridge and VLAN sub-interface if no VMs
---
 Network-Namespace/README.md                    | 17 +++++---
 Network-Namespace/network-namespace-wrapper.sh | 54 ++++++++++++++++++++++++++
 2 files changed, 66 insertions(+), 5 deletions(-)

diff --git a/Network-Namespace/README.md b/Network-Namespace/README.md
index 018ba8a..267cff2 100644
--- a/Network-Namespace/README.md
+++ b/Network-Namespace/README.md
@@ -648,8 +648,11 @@ network-namespace.sh destroy-network  --network-id 42 
--vlan 100
 6. For **VPC tier** networks: deregisters this tier from the VPC — namespace is
    only removed by a subsequent `destroy-vpc` call.
 
-> The host bridge `breth1-100` and VLAN sub-interface `eth1.100` are **not**
-> removed — they may still be used by other networks or for VM connectivity.
+> The host bridge `breth1-100` and VLAN sub-interface `eth1.100` are removed
+> once nothing else is attached to the bridge (checked via
+> `teardown_host_bridge_if_unused`). If another network/tenant is still
+> sharing the same physical VLAN, or a VM tap is still attached, the bridge
+> and VLAN sub-interface are left in place.
 
 ### 9. Unregister and delete the extension
 
@@ -886,8 +889,9 @@ Actions:
    removed by a subsequent `destroy-vpc` call.
 
 > The host bridge `br<GUEST_ETH>-<vlan>` and VLAN sub-interface 
 > `GUEST_ETH.<vlan>`
-> are NOT removed on destroy — they may still be used by other networks or for
-> VM connectivity.
+> are removed on destroy once the bridge has no remaining member interfaces
+> (`teardown_host_bridge_if_unused`). This is a no-op if another network/tenant
+> is still sharing the same physical VLAN, or a VM tap is still attached.
 
 ### VPC lifecycle commands: `implement-vpc`, `update-vpc-source-nat-ip`, 
`shutdown-vpc`, `destroy-vpc`
 
@@ -1042,7 +1046,10 @@ Actions:
 4. Remove host route `<public-ip>/32`.
 5. Remove IP address from `vpn-<pvlan>-<id>` inside namespace.
 6. If no other IPs share the same `<pvlan>/<id>` combination, delete
-   `vph-<pvlan>-<id>` (host veth).
+   `vph-<pvlan>-<id>` (host veth), then attempt to remove the public bridge
+   `br<PUB_ETH>-<pvlan>` and VLAN sub-interface `PUB_ETH.<pvlan>` — a no-op
+   (`teardown_host_bridge_if_unused`) if another network/tenant is still
+   sharing the same public VLAN.
 7. Remove state files.
 
 ### `add-static-nat`
diff --git a/Network-Namespace/network-namespace-wrapper.sh 
b/Network-Namespace/network-namespace-wrapper.sh
index cf61e12..93072d0 100755
--- a/Network-Namespace/network-namespace-wrapper.sh
+++ b/Network-Namespace/network-namespace-wrapper.sh
@@ -456,6 +456,47 @@ ensure_host_bridge() {
     echo "${br}"
 }
 
+# teardown_host_bridge_if_unused <eth> <vlan>
+# Counterpart to ensure_host_bridge(): removes the VLAN sub-interface 
(ethX.vlan)
+# and the bridge (br<eth>-<vlan>) it created, but ONLY if no VM (or other
+# consumer, e.g. another VPC tier/network/tenant riding the same public VLAN)
+# still has an interface plugged into the bridge.
+#
+# ensure_host_bridge() always enslaves the VLAN uplink (ethX.vlan) into the
+# bridge, so it is always present as a bridge member — it must be excluded
+# from the "is anything still using this bridge" check, otherwise the bridge
+# would never be considered unused.
+#
+# Safe to call unconditionally from any teardown path — it is a no-op
+# whenever the bridge is still in use (by a VM tap or otherwise), or already
+# gone.
+teardown_host_bridge_if_unused() {
+    local eth="$1" vlan_raw="$2" vlan br vif members
+    vlan=$(normalize_vlan "${vlan_raw}")
+    br=$(host_bridge_name "${eth}" "${vlan}")
+    vif="${eth}.${vlan}"
+
+    if ! ip link show "${br}" >/dev/null 2>&1; then
+        return 0
+    fi
+
+    if [ -d "/sys/class/net/${br}/brif" ]; then
+        members=$(ls -A "/sys/class/net/${br}/brif" 2>/dev/null | grep -v -x 
"${vif}" || true)
+        if [ -n "${members}" ]; then
+            log "teardown_host_bridge_if_unused: ${br} still has member 
interfaces (${members}), leaving in place"
+            return 0
+        fi
+    fi
+
+    ip link del "${br}" 2>/dev/null || true
+    log "Removed host bridge ${br}"
+
+    if ip link show "${vif}" >/dev/null 2>&1; then
+        ip link del "${vif}" 2>/dev/null || true
+        log "Removed VLAN interface ${vif}"
+    fi
+}
+
 # _guard_ns_teardown <caller-label>
 # When network_state is "shutdown", "destroy", or "allocated" and the namespace
 # is already gone, exit successfully — the network has been torn down or was
@@ -896,6 +937,13 @@ cmd_destroy_network() {
     veth_h=$(veth_host_name "${VLAN}" "${CHOSEN_ID}")
     ip link del "${veth_h}" 2>/dev/null || true
 
+    # The guest VLAN bridge/sub-interface (ensure_host_bridge) is where VM taps
+    # are plugged in directly by the hypervisor. Destroying a network implies
+    # no VMs remain on it, so it is safe to tear these down here (guarded by
+    # teardown_host_bridge_if_unused() in case anything unexpected is still
+    # attached).
+    teardown_host_bridge_if_unused "${GUEST_ETH}" "${VLAN}"
+
     local vsd; vsd=$(_vpc_state_dir)
 
     # Public veth pairs and their state files only exist for Isolated networks
@@ -920,6 +968,9 @@ cmd_destroy_network() {
                 pveth_h=$(pub_veth_host_name "${pvlan}" "${CHOSEN_ID}")
                 ip link del "${pveth_h}" 2>/dev/null || true
                 rm -f "${f}" "${f%.pvlan}" "${tier_f}" 2>/dev/null || true
+                # Public bridge may be shared by other tiers/networks/tenants 
on
+                # the same public VLAN; only removed once nothing else uses it.
+                teardown_host_bridge_if_unused "${PUB_ETH}" "${pvlan}"
             done
         fi
     fi
@@ -1127,6 +1178,9 @@ cmd_release_ip() {
     if [ "${remaining}" -eq 0 ]; then
         ip link del "${pveth_h}" 2>/dev/null || true
         log "release-ip: removed public veth ${pveth_h}"
+        # Public bridge may still be shared by other networks/tenants on the
+        # same public VLAN; only removed once nothing else uses it.
+        teardown_host_bridge_if_unused "${PUB_ETH}" "${PUBLIC_VLAN}"
     fi
 
     # Remove default route if no IPs remain

Reply via email to