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
commit 2f73b4164b10fd81cd6459a4de6115487a7bece7 Author: Wei Zhou <[email protected]> AuthorDate: Sat Jun 13 11:41:11 2026 +0200 Network Namespace: ignore errors when shutdown a network --- Network-Namespace/README.md | 20 ++++++++++--- Network-Namespace/network-namespace-wrapper.sh | 39 +++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/Network-Namespace/README.md b/Network-Namespace/README.md index c15e147..e0dcd0f 100644 --- a/Network-Namespace/README.md +++ b/Network-Namespace/README.md @@ -386,9 +386,20 @@ cmk updateRegisteredExtension \ "details[1].key=username" "details[1].value=root" \ "details[2].key=sshkey" "details[2].value=<pem-key-contents>" \ "details[3].key=guest.network.device" "details[3].value=eth1" \ - "details[4].key=public.network.device" "details[4].value=eth1" + "details[4].key=public.network.device" "details[4].value=eth1" \ + "details[5].key=isolation_method" "details[5].value=NetworkExtension" ``` +> **`isolation_method=NetworkExtension`** causes CloudStack to use +> `NetworkExtensionGuestNetworkGuru` when designing guest networks backed by +> this extension. The network-namespace extension uses VLAN-based isolation +> and does not rely on the script output from `implement-network` to override +> the broadcast domain type, so this detail is not strictly required for basic +> operation. It is included here as best practice and for forward +> compatibility — extensions that return `network.broadcast_domain_type` or +> `network.broadcast_uri` from `implement-network` **must** set it or those +> updates will be silently ignored by CloudStack. + The `hosts` value is a comma-separated list of KVM host IPs; `ensure-network-device` picks one per network and stores it in `--network-extension-details`. Use `sshkey` (PEM private key) for passwordless authentication, or `password` + `sshpass`. @@ -1486,6 +1497,7 @@ name bridges as `br<eth>-<vlan>` and veth pairs as `vh-<vlan>-<id>` / | `vlan` | Guest VLAN tag | | `zone_id` | CloudStack zone ID | | `guest_type` | Guest network type: `"isolated"`, `"shared"`, or `"l2"`. The wrapper uses this to skip iptables / NAT / public-veth operations for `shared` networks. | +| `network_state` | Guest network state: `"allocated"`, `"setup"`, `"implementing"`, `"implemented"`, `"shutdown"` or `"destroy"`. | | `gateway` | Guest network gateway | | `cidr` | Guest network CIDR | | `vpc_id` | Present when the network belongs to a VPC; namespace becomes `cs-vpc-<vpcId>` | @@ -1508,9 +1520,9 @@ name bridges as `br<eth>-<vlan>` and veth pairs as `vh-<vlan>-<id>` / | `netmask` | VM NIC IPv4 netmask | | `default_nic` | `"false"` for secondary NICs (gateway DHCP option suppressed) | | `device_id` | NIC device slot index | -| `nic_ip6_address` | VM NIC IPv6 address, when configured | -| `nic_ip6_gateway` | VM NIC IPv6 gateway, when available | -| `nic_ip6_cidr` | VM NIC IPv6 CIDR, when available | +| `ip6_address` | VM NIC IPv6 address, when configured | +| `ip6_gateway` | VM NIC IPv6 gateway, when available | +| `ip6_cidr` | VM NIC IPv6 CIDR, when available | #### Public-IP fields diff --git a/Network-Namespace/network-namespace-wrapper.sh b/Network-Namespace/network-namespace-wrapper.sh index 0780600..d437754 100755 --- a/Network-Namespace/network-namespace-wrapper.sh +++ b/Network-Namespace/network-namespace-wrapper.sh @@ -423,15 +423,27 @@ ensure_host_bridge() { # VLAN sub-interface if ! ip link show "${vif}" >/dev/null 2>&1; then - ip link add link "${eth}" name "${vif}" type vlan id "${vlan}" + if ! ip link add link "${eth}" name "${vif}" type vlan id "${vlan}" 2>/dev/null; then + if [ "${NETWORK_STATE:-}" = "shutdown" ]; then + log "ensure_host_bridge: failed to create ${vif} (network_state=shutdown, ignoring)" + echo "${br}"; return 0 + fi + ip link add link "${eth}" name "${vif}" type vlan id "${vlan}" + fi log "Created VLAN interface ${vif}" fi ip link set "${vif}" up 2>/dev/null || true # Bridge if ! ip link show "${br}" >/dev/null 2>&1; then - ip link add name "${br}" type bridge - ip link set "${br}" up + if ! ip link add name "${br}" type bridge 2>/dev/null; then + if [ "${NETWORK_STATE:-}" = "shutdown" ]; then + log "ensure_host_bridge: failed to create ${br} (network_state=shutdown, ignoring)" + echo "${br}"; return 0 + fi + ip link add name "${br}" type bridge + fi + ip link set "${br}" up 2>/dev/null || true log "Created host bridge ${br}" fi @@ -444,6 +456,18 @@ ensure_host_bridge() { echo "${br}" } +# _guard_ns_shutdown <caller-label> +# When network_state is "shutdown" and the namespace is already gone, exit +# successfully — the network has already been torn down on this host. +# Call this after acquire_lock in any command that does namespace operations. +_guard_ns_shutdown() { + [ "${NETWORK_STATE:-}" = "shutdown" ] || return 0 + ip netns list 2>/dev/null | grep -q "^${NAMESPACE}\b" && return 0 + log "${1:-command}: namespace ${NAMESPACE} not found (network_state=shutdown) — treating as success" + release_lock + exit 0 +} + ensure_chain() { local table="$1" chain="$2" ip netns exec "${NAMESPACE}" iptables -t "${table}" -n -L "${chain}" >/dev/null 2>&1 || \ @@ -501,6 +525,7 @@ parse_args() { RESTORE_DATA=$(_payload_json_get "${payload_file}" "payload.restore_data") FW_RULES_JSON=$(_payload_json_get "${payload_file}" "payload.fw_rules") ACL_RULES_JSON=$(_payload_json_get "${payload_file}" "payload.acl_rules") + NETWORK_STATE=$(_payload_json_get "${payload_file}" "payload.network_state") [ -z "${SOURCE_NAT}" ] && SOURCE_NAT="false" [ -z "${LB_RULES_JSON}" ] && LB_RULES_JSON="[]" @@ -899,6 +924,7 @@ cmd_assign_ip() { _load_state acquire_lock "${NETWORK_ID}" + _guard_ns_shutdown "assign-ip" log "assign-ip: network=${NETWORK_ID} ns=${NAMESPACE} ip=${PUBLIC_IP} source_nat=${SOURCE_NAT}" [ -z "${PUBLIC_IP}" ] && die "Missing --public-ip" [ -z "${PUBLIC_VLAN}" ] && die "Missing --public-vlan" @@ -1091,6 +1117,7 @@ cmd_add_static_nat() { _load_state acquire_lock "${NETWORK_ID}" + _guard_ns_shutdown "add-static-nat" log "add-static-nat: network=${NETWORK_ID} ns=${NAMESPACE} ${PUBLIC_IP} <-> ${PRIVATE_IP}" [ -z "${PUBLIC_IP}" ] && die "Missing --public-ip" [ -z "${PRIVATE_IP}" ] && die "Missing --private-ip" @@ -1198,6 +1225,7 @@ cmd_add_port_forward() { _load_state acquire_lock "${NETWORK_ID}" + _guard_ns_shutdown "add-port-forward" log "add-port-forward: network=${NETWORK_ID} ns=${NAMESPACE} ${PUBLIC_IP}:${PUBLIC_PORT} -> ${PRIVATE_IP}:${PRIVATE_PORT} (${PROTOCOL})" [ -z "${PUBLIC_IP}" ] && die "Missing --public-ip" [ -z "${PUBLIC_PORT}" ] && die "Missing --public-port" @@ -1687,7 +1715,8 @@ _svc_start_or_reload_apache2() { fi # Allow metadata traffic inbound to the namespace (INPUT) from guest subnet only. - if [ -n "${CIDR}" ]; then + # Skip if namespace is gone (e.g. network already shut down). + if [ -n "${CIDR}" ] && ip netns list 2>/dev/null | grep -q "^${NAMESPACE}\b"; then ip netns exec "${NAMESPACE}" iptables -t filter \ -C INPUT -p tcp -s "${CIDR}" --dport 80 -j ACCEPT 2>/dev/null || \ ip netns exec "${NAMESPACE}" iptables -t filter \ @@ -2490,6 +2519,7 @@ cmd_apply_fw_rules() { parse_args "$@" _load_state acquire_lock "${NETWORK_ID}" + _guard_ns_shutdown "apply-fw-rules" log "apply-fw-rules: network=${NETWORK_ID} ns=${NAMESPACE}" local fw_rules_file="" @@ -3702,6 +3732,7 @@ cmd_apply_network_acl() { parse_args "$@" _load_state acquire_lock "${NETWORK_ID}" + _guard_ns_shutdown "apply-network-acl" log "apply-network-acl: network=${NETWORK_ID} ns=${NAMESPACE} cidr=${CIDR}" local acl_rules_file=""
