Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions scenarios/networking-lab/devstack-sonic-vxlan/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Devstack with SONiC VXLAN Spine-and-Leaf

Spine-and-leaf topology with 4 SONiC switches, 1 Devstack node, 2 Ironic nodes, and 1 controller.

## Topology

![Topology Diagram](topology-diagram.svg)

## Networks

### Management (`192.168.32.0/24`)
- Controller: `192.168.32.254`
- Spine01 (host): `192.168.32.11`, (switch): `192.168.32.111`
- Spine02 (host): `192.168.32.12`, (switch): `192.168.32.112`
- Leaf01 (host): `192.168.32.13`, (switch): `192.168.32.113`
- Leaf02 (host): `192.168.32.14`, (switch): `192.168.32.114`
- Devstack: `192.168.32.20`

### Inter-Switch Links (`10.1.1.0/24`)
- Spine01 ↔ Spine02: `10.1.1.0/30`
- Leaf01 ↔ Spine01: `10.1.1.4/30`
- Leaf01 ↔ Spine02: `10.1.1.8/30`
- Leaf02 ↔ Spine01: `10.1.1.12/30`
- Leaf02 ↔ Spine02: `10.1.1.16/30`

### Loopback/VTEP Addresses
- Spine01: `10.255.255.1/32`
- Spine02: `10.255.255.2/32`
- Leaf01: `10.255.255.3/32` (VTEP)
- Leaf02: `10.255.255.4/32` (VTEP)

### BGP EVPN
- AS 65001 iBGP
- Spines: Route reflectors
- Leafs: Route reflector clients, vtep VTEP (source Loopback0)
- ML2 dynamically manages VLANs/VNIs

## Deployment

```bash
ansible-playbook -e @scenarios/networking-lab/devstack-sonic-vxlan/bootstrap_vars.yml -e os_cloud=<cloud-name> bootstrap_devstack.yml
```

## Troubleshooting

See [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
47 changes: 47 additions & 0 deletions scenarios/networking-lab/devstack-sonic-vxlan/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Troubleshooting

## Switches Not Booting
- Check OpenStack console logs for the switch instances
- Verify the `hotstack-sonic` image is properly configured
- Check cloud-init logs: `sudo journalctl -u cloud-init`
- Verify the SONiC container is running: `sudo systemctl status sonic.service`

## Switches Not Reachable
- Verify management interface configuration on switches: `show interface Management0` or `ip addr show eth0`
- Check DNS resolution from controller: `dig spine01.stack.lab @192.168.32.254`
- Ensure routes are configured in management VRF: `show ip route vrf mgmt`
- Check that eth0 has the correct IP: `ip addr show eth0`

## OSPF Not Working
- Access FRR shell: `podman exec sonic vtysh`
- Verify OSPF is running: `show ip ospf`
- Check OSPF neighbors: `show ip ospf neighbor`
- Verify interface MTU matches (1442): `show interface Ethernet0`
- Check OSPF interface configuration: `show ip ospf interface`

## BGP EVPN Not Working
- Access FRR shell: `podman exec sonic vtysh`
- Verify BGP is running: `show bgp summary`
- Check BGP EVPN neighbors: `show bgp l2vpn evpn summary`
- Verify loopback reachability: `ping 10.255.255.1 -I 10.255.255.3`
- Check BGP configuration: `show running-config`

## Devstack Deployment Issues
- Check network connectivity on trunk0: `ip link show trunk0`
- Verify trunk0 is added to br-ex: `sudo ovs-vsctl show`
- Review devstack logs: `/opt/stack/logs/stack.sh.log`
- Check neutron-server logs: `sudo journalctl -u devstack@q-svc`

## ML2 Not Configuring Switches
- Verify networking-generic-switch credentials in `/etc/neutron/plugins/ml2/ml2_conf_genericswitch.ini`
- Check neutron-server can reach switches: `ping 192.168.32.13` from devstack
- Review neutron-server logs for genericswitch errors: `sudo journalctl -u devstack@q-svc | grep genericswitch`
- Test SSH connectivity manually: `ssh admin@192.168.32.13` from devstack
- Test SSH connectivity manually: `ssh admin@192.168.32.13` from devstack

## Container-Specific Issues
- Check SONiC container status: `sudo podman ps`
- View container logs: `sudo podman logs sonic`
- Restart SONiC service: `sudo systemctl restart sonic.service`
- Verify SONiC image is loaded: `sudo podman images | grep sonic`
- Access SONiC CLI: `sudo podman exec -it sonic bash`
117 changes: 117 additions & 0 deletions scenarios/networking-lab/devstack-sonic-vxlan/automation-vars.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
# Networking lab automation stages

stages:
- name: Configure provisioning network route
documentation: >-
Configures a static route on the devstack node to enable communication
between the ironic-conductor and the provisioning network (where
ironic-python-agent runs during node deployment/cleaning). This retrieves
the router gateway IP from Neutron and adds a route via that gateway.
The provisioning network subnet is configured in local.conf.j2 via
IRONIC_PROVISION_SUBNET_PREFIX (default: 10.0.5.0/24).
shell: |
set -xe -o pipefail

IRONIC_PROVISION_SUBNET_PREFIX="10.0.5.0/24"

EXTERNAL_GW_INFO=$(openstack --os-cloud devstack-admin router show router1 -c external_gateway_info -f json)

ROUTER_GW_IP=$(echo "$EXTERNAL_GW_INFO" | python3 -c '
import sys, json, ipaddress
data = json.load(sys.stdin)
for ip_info in data["external_gateway_info"]["external_fixed_ips"]:
addr = ipaddress.ip_address(ip_info["ip_address"])
if addr.version == 4:
print(ip_info["ip_address"])
break
')

if [ -z "$ROUTER_GW_IP" ]; then
echo "ERROR: Could not determine router gateway IP"
exit 1
fi

ssh -o StrictHostKeyChecking=no stack@devstack.stack.lab "
ROUTES=\$(ip -j r)
ROUTE_EXISTS=\$(echo \"\$ROUTES\" | python3 -c '
import sys, json
routes = json.load(sys.stdin)
target_dst = \"$IRONIC_PROVISION_SUBNET_PREFIX\"
target_gw = \"$ROUTER_GW_IP\"
for route in routes:
if route.get(\"dst\") == target_dst and route.get(\"gateway\") == target_gw:
print(\"exists\")
break
')
if [ -z \"\$ROUTE_EXISTS\" ]; then
echo \"Adding route: $IRONIC_PROVISION_SUBNET_PREFIX via $ROUTER_GW_IP\"
sudo ip route add $IRONIC_PROVISION_SUBNET_PREFIX via $ROUTER_GW_IP
else
echo \"Route already exists: $IRONIC_PROVISION_SUBNET_PREFIX via $ROUTER_GW_IP\"
fi
"

- name: Enroll nodes in devstack ironic
documentation: >-
Registers physical baremetal nodes with the Ironic service in the DevStack
deployment using the node definitions from ironic_nodes.yaml. This creates
Ironic node records with BMC access credentials, hardware profiles, and port
configurations for networking-generic-switch integration.
shell: |
set -xe -o pipefail

NODES_FILE=/home/zuul/data/ironic_nodes.yaml

# Enroll the nodes
openstack --os-cloud devstack-admin baremetal create "$NODES_FILE"

echo "Nodes enrolled successfully"
openstack --os-cloud devstack-admin baremetal node list

- name: Wait for ironic nodes to reach enroll state
documentation: >-
Monitors node state transition to 'enroll' status, indicating that Ironic
has successfully registered the nodes and validated basic BMC connectivity.
This is the first state in the baremetal provisioning lifecycle.
shell: |
set -xe -o pipefail

counter=0
max_retries=60
sleep_interval=5

echo "Waiting for all nodes to reach 'enroll' state..."

until ! openstack --os-cloud devstack-admin baremetal node list -f value -c "Provisioning State" | grep -v "enroll"; do
((counter++))
if (( counter > max_retries )); then
echo "ERROR: Timeout waiting for nodes to reach enroll state"
openstack --os-cloud devstack-admin baremetal node list
exit 1
fi
echo "Attempt $counter/$max_retries - waiting ${sleep_interval}s..."
sleep ${sleep_interval}
done

echo "All nodes successfully reached enroll state"
openstack --os-cloud devstack-admin baremetal node list

- name: Manage nodes
documentation: >-
Transitions nodes from 'enroll' to 'manageable' state. This validates
basic hardware connectivity and prepares nodes for further operations.
shell: |
set -x -o pipefail

# Get list of node UUIDs
node_uuids=$(openstack --os-cloud devstack-admin baremetal node list -f value -c UUID)

# Manage each node with --wait (300 second timeout)
for uuid in $node_uuids; do
echo "Managing node: $uuid"
openstack --os-cloud devstack-admin baremetal node manage --wait 300 $uuid
done

echo "All nodes successfully reached manageable state"
openstack --os-cloud devstack-admin baremetal node list
50 changes: 50 additions & 0 deletions scenarios/networking-lab/devstack-sonic-vxlan/bootstrap_vars.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
# Bootstrap configuration for networking lab scenario with SONiC

# OpenStack cloud configuration
os_cloud: default
os_floating_network: public
os_router_external_network: public

# Scenario configuration
scenario: devstack-sonic-vxlan
scenario_dir: scenarios/networking-lab
stack_template_path: "{{ scenario_dir }}/{{ scenario }}/heat_template.yaml"
automation_vars_file: "{{ scenario_dir }}/{{ scenario }}/automation-vars.yml"

# DNS and NTP
ntp_servers: []
dns_servers:
- 8.8.8.8
- 8.8.4.4

# Pull secret for container images (if needed)
# pull_secret_file: ~/pull-secret.txt

# Stack naming
stack_name: "hs-{{ scenario | replace('/', '-') }}-{{ zuul.build[:8] | default('no-zuul') }}"

# Stack parameters
stack_parameters:
dns_servers: "{{ dns_servers }}"
ntp_servers: "{{ ntp_servers }}"
controller_ssh_pub_key: "{{ controller_ssh_pub_key | default('') }}"
dataplane_ssh_pub_key: "{{ dataplane_ssh_pub_key | default('') }}"
router_external_network: "{{ os_router_external_network | default('public') }}"
floating_ip_network: "{{ os_floating_network | default('public') }}"
controller_params:
image: hotstack-controller
flavor: hotstack.small
devstack_params:
image: ubuntu-noble-server
flavor: hotstack.xxlarge
switch_params:
image: hotstack-sonic
flavor: hotstack.small
ironic_params:
image: CentOS-Stream-GenericCloud-9
cd_image: sushy-tools-blank-image
flavor: hotstack.medium

# Controller role configuration
controller_install_openstack_client: true
Loading