-
Notifications
You must be signed in to change notification settings - Fork 1k
Add virtual NUT testbed (vNUT) support to testbed-cli using KVM VMs #22976
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| #!/usr/bin/python3 | ||
| """ | ||
| Ansible module to manage virtual network links (veth pairs) for NUT virtual testbed. | ||
r12f marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Usage in Ansible: | ||
| - name: Connect container to management bridge | ||
| vnut_network: | ||
| action: connect_mgmt | ||
| device: "switch-t0-1" | ||
| mgmt_ip: "10.0.0.100/24" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stale docstring: the module docstring (lines 6-38) still documents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed — updated module docstring to only document connect_mgmt and create_bridge actions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed fixed — docstring updated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed — docstring now documents only There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Confirmed fixed — docstring now documents only the two remaining actions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed: the docstring now documents only the two current actions ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed fixed — docstring now documents only connect_mgmt and create_bridge. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed: docstring updated to document only the remaining actions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed — docstring now documents |
||
| mgmt_gateway: "10.0.0.1" | ||
| mgmt_bridge: "br-mgmt" | ||
| testbed_name: "nut-ci-1" | ||
| container_prefix: "net" | ||
|
|
||
| - name: Create management bridge | ||
| vnut_network: | ||
| action: create_bridge | ||
| bridge_name: "br-mgmt" | ||
| bridge_ip: "10.0.0.1/24" | ||
| """ | ||
|
|
||
| import hashlib | ||
| import subprocess | ||
|
|
||
| from ansible.module_utils.basic import AnsibleModule | ||
|
|
||
|
|
||
| def run_cmd(cmd_args, check=True): | ||
| """Run a command and return (rc, stdout, stderr). | ||
|
|
||
| Args: | ||
| cmd_args: List of command arguments (e.g. ["ip", "link", "show", "eth0"]). | ||
| check: If True, raise RuntimeError on non-zero exit code. | ||
| """ | ||
| result = subprocess.run(cmd_args, capture_output=True, text=True, timeout=60) | ||
| if check and result.returncode != 0: | ||
| raise RuntimeError( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No timeout on
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed — added timeout=60 to subprocess.run() in run_cmd. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed — There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Confirmed fixed — timeout=60 added to subprocess.run(). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed — There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed — |
||
| "Command failed: {}\nstdout: {}\nstderr: {}".format( | ||
| " ".join(cmd_args), result.stdout.strip(), result.stderr.strip() | ||
| ) | ||
| ) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open — run_cmd still silently discards stderr on success. Low priority nit.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged — will address stderr handling in a follow-up. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open (minor) — stderr is still discarded on success. Low priority. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open — stderr discarded on success. Minor nit, low priority. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open — minor nit, non-blocking. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open (nit): There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open (nit): stderr discarded on success. Non-blocking. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open (nit): stderr discarded on success in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed — |
||
| return result.returncode, result.stdout.strip(), result.stderr.strip() | ||
|
|
||
|
|
||
| def link_exists_on_host(link_name): | ||
| """Check if a network link exists on the host.""" | ||
| rc, _, _ = run_cmd(["ip", "link", "show", link_name], check=False) | ||
| return rc == 0 | ||
|
|
||
|
|
||
| def bridge_exists(bridge_name): | ||
| """Check if a bridge exists on the host.""" | ||
| rc, _, _ = run_cmd( | ||
| ["ip", "link", "show", "type", "bridge", "dev", bridge_name], check=False | ||
| ) | ||
| return rc == 0 | ||
|
|
||
|
|
||
| def get_container_pid(container_name): | ||
| """Get the PID of a running Docker container.""" | ||
| rc, stdout, stderr = run_cmd( | ||
| ["docker", "inspect", "-f", "{{.State.Pid}}", container_name], check=False | ||
| ) | ||
| if rc != 0: | ||
| raise RuntimeError( | ||
| "Container '{}' not found or not running: {}".format(container_name, stderr) | ||
| ) | ||
| pid = stdout.strip() | ||
| if pid == "0": | ||
| raise RuntimeError("Container '{}' is not running (PID=0)".format(container_name)) | ||
| return pid | ||
|
|
||
|
|
||
| def container_name(prefix, testbed, device): | ||
| """Build the Docker container name from components.""" | ||
| return "{}_{}_{}".format(prefix, testbed, device) | ||
|
|
||
|
|
||
| def interface_exists_in_ns(pid, iface_name): | ||
| """Check if an interface exists inside a container network namespace.""" | ||
| rc, _, _ = run_cmd( | ||
| ["nsenter", "-t", pid, "-n", "ip", "link", "show", iface_name], check=False | ||
| ) | ||
| return rc == 0 | ||
|
|
||
|
|
||
| def action_connect_mgmt(module): | ||
| """Connect a container to a management bridge.""" | ||
| p = module.params | ||
| device = p["device"] | ||
| mgmt_ip = p["mgmt_ip"] | ||
| mgmt_gw = p["mgmt_gateway"] | ||
| bridge = p["mgmt_bridge"] | ||
| testbed_name = p["testbed_name"] | ||
| prefix = p["container_prefix"] | ||
|
|
||
| cname = container_name(prefix, testbed_name, device) | ||
| pid = get_container_pid(cname) | ||
|
|
||
| # Idempotency: if eth0 already exists inside container, skip | ||
| if interface_exists_in_ns(pid, "eth0"): | ||
| module.exit_json( | ||
| changed=False, | ||
| msg="Management interface eth0 already exists in {}".format(cname), | ||
| ) | ||
|
|
||
| short_id = hashlib.md5(cname.encode()).hexdigest()[:8] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 8-character MD5 prefix ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open — 8-char MD5 prefix used. Acceptable for current scale, noting for future reference.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged — 8-char MD5 prefix is sufficient for current scale. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open but acceptable risk for the expected scale. The 8-char MD5 prefix is fine for typical testbed sizes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open — 8-char MD5 prefix is acceptable for expected scale. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acceptable for expected scale. Non-blocking. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open (nit): 8-char MD5 prefix gives 32 bits — acceptable for small testbeds, but worth a comment noting the limitation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acceptable for expected scale. Non-blocking. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open (nit): 8-char MD5 prefix for interface naming. Acceptable at current scale. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged — 8-char MD5 prefix is acceptable for this use case (per-testbed container naming, not security-critical). Low collision risk in practice. |
||
| veth_a = "vm{}a".format(short_id) # 12 chars, well under 15 | ||
| veth_b = "vm{}b".format(short_id) # 12 chars, well under 15 | ||
|
|
||
| # Clean up if host-side veth exists | ||
| if link_exists_on_host(veth_a): | ||
| run_cmd(["ip", "link", "delete", veth_a]) | ||
|
|
||
| # Create veth pair and move into container; clean up on failure | ||
| run_cmd(["ip", "link", "add", veth_a, "type", "veth", "peer", "name", veth_b]) | ||
| try: | ||
| # Move one end into container as eth0 | ||
| run_cmd(["ip", "link", "set", veth_a, "netns", pid]) | ||
| run_cmd(["nsenter", "-t", pid, "-n", "ip", "link", "set", veth_a, "name", "eth0"]) | ||
| run_cmd(["nsenter", "-t", pid, "-n", "ip", "addr", "add", mgmt_ip, "dev", "eth0"]) | ||
| run_cmd(["nsenter", "-t", pid, "-n", "ip", "link", "set", "eth0", "up"]) | ||
| run_cmd(["nsenter", "-t", pid, "-n", "ip", "route", "add", "default", "via", mgmt_gw]) | ||
|
|
||
| # Attach host end to bridge | ||
| run_cmd(["ip", "link", "set", veth_b, "master", bridge]) | ||
| run_cmd(["ip", "link", "set", veth_b, "up"]) | ||
| except Exception: | ||
| # Clean up the veth pair to avoid dangling interfaces | ||
| for iface in (veth_a, veth_b): | ||
| try: | ||
| run_cmd(["ip", "link", "delete", iface]) | ||
| except Exception: | ||
github-code-quality[bot] marked this conversation as resolved.
Fixed
Show fixed
Hide fixed
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show fixed
Hide fixed
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The inner
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed — added comment explaining intentional best-effort cleanup. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed fixed — the comment clarifies intentional best-effort cleanup behavior. |
||
| pass # Best-effort cleanup; re-raise original exception below | ||
| raise | ||
|
|
||
| module.exit_json( | ||
| changed=True, | ||
| msg="Connected {} to bridge {} with IP {}".format(cname, bridge, mgmt_ip), | ||
| ) | ||
|
|
||
|
|
||
| def action_create_bridge(module): | ||
| """Create a Linux bridge with an IP address.""" | ||
| p = module.params | ||
| bridge_name = p["bridge_name"] | ||
| bridge_ip = p["bridge_ip"] | ||
|
|
||
| if bridge_exists(bridge_name): | ||
| module.exit_json(changed=False, msg="Bridge {} already exists".format(bridge_name)) | ||
|
|
||
| run_cmd(["ip", "link", "add", bridge_name, "type", "bridge"]) | ||
| run_cmd(["ip", "addr", "add", bridge_ip, "dev", bridge_name]) | ||
| run_cmd(["ip", "link", "set", bridge_name, "up"]) | ||
|
|
||
| module.exit_json( | ||
| changed=True, | ||
| msg="Created bridge {} with IP {}".format(bridge_name, bridge_ip), | ||
| ) | ||
|
|
||
|
|
||
| def main(): | ||
| module = AnsibleModule( | ||
| argument_spec=dict( | ||
| action=dict( | ||
| type="str", | ||
| required=True, | ||
| choices=["connect_mgmt", "create_bridge"], | ||
| ), | ||
| # connect_mgmt params | ||
| device=dict(type="str"), | ||
| mgmt_ip=dict(type="str"), | ||
| mgmt_gateway=dict(type="str"), | ||
| mgmt_bridge=dict(type="str", default="br-mgmt"), | ||
| # create_bridge params | ||
| bridge_name=dict(type="str"), | ||
| bridge_ip=dict(type="str"), | ||
| # common params | ||
| testbed_name=dict(type="str"), | ||
| container_prefix=dict(type="str", default="net"), | ||
| ), | ||
| required_if=[ | ||
| ("action", "connect_mgmt", ["device", "mgmt_ip", "mgmt_gateway", "testbed_name"]), | ||
| ("action", "create_bridge", ["bridge_name", "bridge_ip"]), | ||
| ], | ||
| supports_check_mode=False, | ||
| ) | ||
|
|
||
| action = module.params["action"] | ||
|
|
||
| try: | ||
| if action == "connect_mgmt": | ||
| action_connect_mgmt(module) | ||
| elif action == "create_bridge": | ||
| action_create_bridge(module) | ||
| except RuntimeError as e: | ||
| module.fail_json(msg=str(e)) | ||
| except Exception as e: | ||
| module.fail_json(msg="Unexpected error: {}".format(e)) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| --- | ||
| # Default Docker images (TG container only) | ||
| vnut_ptf_image: "docker-ptf:latest" | ||
|
|
||
| # Naming prefix for TG Docker containers | ||
| vnut_container_prefix: "net" | ||
|
|
||
| # KVM VM settings | ||
| vnut_vm_memory_gb: 4 | ||
| vnut_vm_vcpus: 2 | ||
| vnut_sonic_vm_storage: "{{ ansible_env.HOME }}/sonic-vm" | ||
| vnut_sonic_vs_image: "{{ vnut_sonic_vm_storage }}/images/sonic-vs.img" | ||
| vnut_vm_disk_dir: "{{ vnut_sonic_vm_storage }}/disks" | ||
| vnut_vm_serial_port_base: 9100 | ||
|
|
||
| # Front-panel MTU | ||
| vnut_fp_mtu: 9100 | ||
|
|
||
| # Management network | ||
| mgmt_bridge: "vnut_mgmt" | ||
| mgmt_gw: "10.250.0.1" | ||
| mgmt_prefixlen: "24" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../../../vm_set/library/kickstart.py |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../../../vm_set/library/sonic_kickstart.py |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| --- | ||
| # connect_tg_links.yml — Connect TG container interfaces to front-panel link bridges | ||
| # For each link involving a TG device, create a veth pair with one end in the | ||
| # TG container namespace and the other end attached to the link bridge. | ||
|
|
||
| - name: Identify TG links | ||
| set_fact: | ||
| vnut_tg_links: >- | ||
| {{ (vnut_links | selectattr('StartDevice', 'in', testbed_tg_names) | list | ||
| + vnut_links | selectattr('EndDevice', 'in', testbed_tg_names) | list) | unique }} | ||
|
|
||
| - name: Connect TG containers to link bridges | ||
| shell: | | ||
| {% for link in vnut_links %} | ||
| {% if link.StartDevice in testbed_tg_names %} | ||
| {# TG is the start device #} | ||
| BRIDGE="vbr_{{ testbed_name[:8] }}_{{ loop.index0 }}" | ||
| CONTAINER="{{ vnut_container_prefix }}_{{ testbed_name }}_{{ link.StartDevice }}" | ||
| PORT="{{ link.StartPort }}" | ||
| VETH_A="vtg{{ testbed_name[:6] }}_{{ loop.index0 }}a" | ||
| VETH_B="vtg{{ testbed_name[:6] }}_{{ loop.index0 }}b" | ||
| PID=$(docker inspect -f '{% raw %}{{.State.Pid}}{% endraw %}' "$CONTAINER") | ||
|
|
||
| # Check if port already exists in container | ||
| if nsenter -t "$PID" -n ip link show "$PORT" >/dev/null 2>&1; then | ||
| echo "Port $PORT already exists in $CONTAINER" | ||
| else | ||
| ip link show "$VETH_A" 2>/dev/null || ip link add "$VETH_A" type veth peer name "$VETH_B" | ||
| ip link set "$VETH_A" master "$BRIDGE" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open —
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged — will improve error handling in connect_tg_links in a follow-up. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open — There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open — error swallowing pattern unchanged. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open — the idempotency check with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open: error swallowing pattern unchanged. Non-blocking but worth improving. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed — the pattern now checks existence first ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The if ! ip link show "$VETH_A" 2>/dev/null; then
ip link add "$VETH_A" type veth peer name "$VETH_B"
fiThis way, legitimate There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open: same concern as thread 88 —
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged — idempotency checks prevent silent failures for the common case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged by maintainer — idempotency pattern acceptable. |
||
| ip link set "$VETH_A" up | ||
| ip link set "$VETH_B" netns "$PID" | ||
| nsenter -t "$PID" -n ip link set "$VETH_B" name "$PORT" | ||
| nsenter -t "$PID" -n ip link set "$PORT" up | ||
| echo "Connected $CONTAINER:$PORT to $BRIDGE" | ||
| fi | ||
|
|
||
| {% elif link.EndDevice in testbed_tg_names %} | ||
| {# TG is the end device #} | ||
| BRIDGE="vbr_{{ testbed_name[:8] }}_{{ loop.index0 }}" | ||
| CONTAINER="{{ vnut_container_prefix }}_{{ testbed_name }}_{{ link.EndDevice }}" | ||
| PORT="{{ link.EndPort }}" | ||
| VETH_A="vtg{{ testbed_name[:6] }}_{{ loop.index0 }}a" | ||
| VETH_B="vtg{{ testbed_name[:6] }}_{{ loop.index0 }}b" | ||
| PID=$(docker inspect -f '{% raw %}{{.State.Pid}}{% endraw %}' "$CONTAINER") | ||
|
|
||
| if nsenter -t "$PID" -n ip link show "$PORT" >/dev/null 2>&1; then | ||
| echo "Port $PORT already exists in $CONTAINER" | ||
| else | ||
| ip link show "$VETH_A" 2>/dev/null || ip link add "$VETH_A" type veth peer name "$VETH_B" | ||
| ip link set "$VETH_A" master "$BRIDGE" | ||
| ip link set "$VETH_A" up | ||
| ip link set "$VETH_B" netns "$PID" | ||
| nsenter -t "$PID" -n ip link set "$VETH_B" name "$PORT" | ||
| nsenter -t "$PID" -n ip link set "$PORT" up | ||
| echo "Connected $CONTAINER:$PORT to $BRIDGE" | ||
| fi | ||
|
|
||
| {% endif %} | ||
| {% endfor %} | ||
| args: | ||
| executable: /bin/bash | ||
| register: connect_tg_result | ||
| changed_when: "'Connected' in connect_tg_result.stdout" | ||
| when: vnut_tg_links | length > 0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| --- | ||
| # create_links.yml — Create Linux bridges for each front-panel link. | ||
| # KVM VM interfaces and TG container interfaces will attach to these bridges. | ||
| # Bridge naming: vbr_<testbed>_<idx> (max 15 chars for Linux interface name) | ||
|
|
||
| - name: Create front-panel link bridges | ||
| shell: | | ||
| BRIDGE="vbr_{{ testbed_name[:8] }}_{{ idx }}" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still open (nit):
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged — bridge name length is safe for current scale. Will add validation in a follow-up if needed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged by maintainer — bridge name length safe for current scale. |
||
| if ip link show "$BRIDGE" >/dev/null 2>&1; then | ||
| echo "Bridge $BRIDGE already exists" | ||
| else | ||
| ip link add "$BRIDGE" type bridge | ||
| ip link set "$BRIDGE" up | ||
| echo "Created bridge $BRIDGE" | ||
| fi | ||
| loop: "{{ vnut_links }}" | ||
| loop_control: | ||
| index_var: idx | ||
| label: "{{ item.StartDevice }}:{{ item.StartPort }} <-> {{ item.EndDevice }}:{{ item.EndPort }}" | ||
| register: bridge_results | ||
| changed_when: "'Created' in bridge_results.stdout" | ||
|
|
||
| - name: Display link bridge creation summary | ||
| debug: | ||
| msg: "Created {{ vnut_links | length }} front-panel link bridges" | ||
Uh oh!
There was an error while loading. Please reload this page.