Skip to content

Commit 1c8bf1d

Browse files
fultonjclaude
andcommitted
Configure OVN SB direct IPs for DCN nodesets
Problem: DCN compute nodes' OVN Controller agents cannot connect to the OVN SB database because the default ovncontroller-config ConfigMap uses Kubernetes ClusterIP (tcp:ovsdbserver-sb.openstack.svc:6642) which is not routable from external EDPM nodes on different network segments. While DNS resolution works (via dnsmasq at 192.168.122.80), the resolved ClusterIP cannot be reached from DCN sites which are on different internalapi subnets (172.17.10.x for dcn1, 172.17.20.x for dcn2 vs central's 172.17.0.x). This causes port binding failures when launching VMs in DCN availability zones: "Binding failed for port, please check neutron logs for more information" Evidence: - Central compute OVN Controller agents: Connected and working (`:-)` status) - DCN compute OVN Controller agents: NOT registered in OVN SB database - `ovn-sbctl show` shows only central computes and gateway, no DCN chassis Root Cause: Setting edpm_ovn_dbs variable is insufficient because the edpm_ovn role loads ovncontroller-config ConfigMap data which overrides the ovn-remote setting. The default ConfigMap (created by OVNDBCluster operator) uses ClusterIP. Solution: 1. Retrieve OVN SB internalapi IPs from pod annotations 2. Create DCN-specific ConfigMap (ovncontroller-config-dcn) with direct IPs 3. Create DCN-specific DataPlaneService (ovn-dcn) referencing this ConfigMap 4. Patch dcn1/dcn2 nodesets to use ovn-dcn service instead of ovn This ensures DCN nodes connect to OVN SB via routable internalapi IPs: tcp:172.17.0.34:6642,tcp:172.17.0.36:6642,tcp:172.17.0.35:6642 Co-Authored-By: Claude <[email protected]> Signed-off-by: John Fulton <[email protected]>
1 parent 6d9167b commit 1c8bf1d

File tree

2 files changed

+195
-0
lines changed

2 files changed

+195
-0
lines changed

tests/roles/dataplane_adoption/defaults/main.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ dataplane_cr: |
397397
ovn_monitor_all: true
398398
edpm_ovn_remote_probe_interval: 60000
399399
edpm_ovn_ofctrl_wait_before_clear: 8000
400+
{% if edpm_ovn_dbs_nodeset is defined -%}
401+
edpm_ovn_dbs: {{ edpm_ovn_dbs_nodeset }}
402+
{%+ endif +%}
400403
nodes:
401404
402405
dpa_dir: "../.."

tests/roles/dataplane_adoption/tasks/main.yaml

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,69 @@
4343
EOF
4444
when: configure_ipam | bool
4545

46+
# DCN requires routes in the internalapi NAD so OVN SB pods can reach DCN compute nodes.
47+
# Macvlan pods don't inherit routes from the node interface, so we add them to the NAD.
48+
- name: Patch internalapi NAD with routes to DCN subnets
49+
when: edpm_nodes_dcn1 is defined or edpm_nodes_dcn2 is defined
50+
no_log: "{{ use_no_log }}"
51+
ansible.builtin.shell: |
52+
{{ shell_header }}
53+
{{ oc_header }}
54+
55+
# Get current NAD config
56+
CURRENT_CONFIG=$(oc get net-attach-def internalapi -n openstack -o jsonpath='{.spec.config}')
57+
58+
# Check if routes already exist
59+
if echo "$CURRENT_CONFIG" | grep -q '"routes"'; then
60+
echo "Routes already exist in internalapi NAD, skipping"
61+
exit 0
62+
fi
63+
64+
# Build routes array for DCN subnets
65+
# DCN1: 172.17.10.0/24, DCN2: 172.17.20.0/24, gateway: 172.17.0.1
66+
ROUTES='[{"dst":"172.17.10.0/24","gw":"172.17.0.1"},{"dst":"172.17.20.0/24","gw":"172.17.0.1"}]'
67+
68+
# Add routes to IPAM config
69+
NEW_CONFIG=$(echo "$CURRENT_CONFIG" | python3 -c "
70+
import json, sys
71+
config = json.load(sys.stdin)
72+
config['ipam']['routes'] = json.loads('$ROUTES')
73+
print(json.dumps(config))
74+
")
75+
76+
# Apply updated NAD
77+
oc apply -f - <<EOF
78+
apiVersion: k8s.cni.cncf.io/v1
79+
kind: NetworkAttachmentDefinition
80+
metadata:
81+
name: internalapi
82+
namespace: openstack
83+
labels:
84+
osp/net: internalapi
85+
osp/net-attach-def-type: standard
86+
spec:
87+
config: |
88+
$NEW_CONFIG
89+
EOF
90+
register: nad_patch_result
91+
92+
- name: Restart OVN SB pods to pick up new NAD routes
93+
when:
94+
- edpm_nodes_dcn1 is defined or edpm_nodes_dcn2 is defined
95+
- nad_patch_result.changed
96+
- "'skipping' not in nad_patch_result.stdout"
97+
no_log: "{{ use_no_log }}"
98+
ansible.builtin.shell: |
99+
{{ shell_header }}
100+
{{ oc_header }}
101+
102+
# Delete OVN SB pods so they restart with new routes
103+
oc delete pod -n openstack -l service=ovsdbserver-sb --wait=false
104+
105+
# Wait for pods to be ready again
106+
sleep 10
107+
oc wait --for=condition=Ready pod -n openstack -l service=ovsdbserver-sb --timeout=120s
108+
46109
- name: Slurp the private key
47110
no_log: "{{ use_no_log }}"
48111
ansible.builtin.slurp:
@@ -258,11 +321,100 @@
258321
{% endif %}
259322
{% endfor %}
260323
324+
- name: Get OVN SB internalapi IPs for DCN nodesets
325+
when: edpm_nodes_dcn1 is defined or edpm_nodes_dcn2 is defined
326+
no_log: "{{ use_no_log }}"
327+
ansible.builtin.shell: |
328+
{{ shell_header }}
329+
{{ oc_header }}
330+
331+
# Get internalapi IPs from OVN SB pods
332+
OVN_SB_IPS=""
333+
for pod in ovsdbserver-sb-0 ovsdbserver-sb-1 ovsdbserver-sb-2; do
334+
IP=$(oc get pod -n openstack $pod -o jsonpath='{.metadata.annotations.k8s\.v1\.cni\.cncf\.io/network-status}' | \
335+
python3 -c "import sys, json; data=json.load(sys.stdin); print([n for n in data if 'internalapi' in n.get('name','')][0]['ips'][0])")
336+
if [ -z "$OVN_SB_IPS" ]; then
337+
OVN_SB_IPS="\"$IP\""
338+
else
339+
OVN_SB_IPS="$OVN_SB_IPS, \"$IP\""
340+
fi
341+
done
342+
343+
echo "[$OVN_SB_IPS]"
344+
register: ovn_sb_ips_result
345+
346+
- name: Set OVN SB IPs fact for DCN nodesets
347+
when: edpm_nodes_dcn1 is defined or edpm_nodes_dcn2 is defined
348+
ansible.builtin.set_fact:
349+
edpm_ovn_dbs_dcn: "{{ ovn_sb_ips_result.stdout | trim | from_json }}"
350+
351+
- name: Create DCN OVN controller ConfigMap with direct IPs
352+
when: edpm_nodes_dcn1 is defined or edpm_nodes_dcn2 is defined
353+
no_log: "{{ use_no_log }}"
354+
ansible.builtin.shell: |
355+
{{ shell_header }}
356+
{{ oc_header }}
357+
358+
# Build ovn-remote connection string from internalapi IPs
359+
OVN_REMOTE="{% for ip in edpm_ovn_dbs_dcn %}tcp:{{ ip }}:6642{% if not loop.last %},{% endif %}{% endfor %}"
360+
361+
# Create ConfigMap for DCN nodes with direct IPs
362+
oc apply -f - <<EOF
363+
apiVersion: v1
364+
kind: ConfigMap
365+
metadata:
366+
name: ovncontroller-config-dcn
367+
namespace: openstack
368+
data:
369+
ovsdb-config: |
370+
ovn-remote: $OVN_REMOTE
371+
EOF
372+
373+
- name: Create DCN-specific OVN DataPlaneService
374+
when: edpm_nodes_dcn1 is defined or edpm_nodes_dcn2 is defined
375+
no_log: "{{ use_no_log }}"
376+
ansible.builtin.shell: |
377+
{{ shell_header }}
378+
{{ oc_header }}
379+
380+
# Create OpenStackDataPlaneService for DCN that uses the DCN ConfigMap
381+
oc apply -f - <<EOF
382+
apiVersion: dataplane.openstack.org/v1beta1
383+
kind: OpenStackDataPlaneService
384+
metadata:
385+
name: ovn-dcn
386+
namespace: openstack
387+
spec:
388+
addCertMounts: false
389+
caCerts: combined-ca-bundle
390+
containerImageFields:
391+
- OvnControllerImage
392+
dataSources:
393+
- configMapRef:
394+
name: ovncontroller-config-dcn
395+
edpmServiceType: ovn
396+
playbook: osp.edpm.ovn
397+
tlsCerts:
398+
default:
399+
contents:
400+
- dnsnames
401+
- ips
402+
issuer: osp-rootca-issuer-ovn
403+
keyUsages:
404+
- digital signature
405+
- key encipherment
406+
- server auth
407+
- client auth
408+
networks:
409+
- ctlplane
410+
EOF
411+
261412
- name: Create OpenStackDataPlaneNodeSet_dcn1
262413
when: edpm_nodes_dcn1 is defined
263414
no_log: "{{ use_no_log }}"
264415
vars:
265416
edpm_ovn_bridge_mappings_nodeset: "{{ edpm_ovn_bridge_mappings_dcn1|default(omit) }}"
417+
edpm_ovn_dbs_nodeset: "{{ edpm_ovn_dbs_dcn }}"
266418
ansible.builtin.shell: |
267419
{{ shell_header }}
268420
CELL=cell1
@@ -279,6 +431,7 @@
279431
no_log: "{{ use_no_log }}"
280432
vars:
281433
edpm_ovn_bridge_mappings_nodeset: "{{ edpm_ovn_bridge_mappings_dcn2|default(omit) }}"
434+
edpm_ovn_dbs_nodeset: "{{ edpm_ovn_dbs_dcn }}"
282435
ansible.builtin.shell: |
283436
{{ shell_header }}
284437
CELL=cell1
@@ -336,6 +489,45 @@
336489
cat nodeset-cell1-dcn2.yaml | oc apply -f -
337490
{%+ endif +%}
338491
492+
- name: Patch DCN nodesets to use ovn-dcn service instead of ovn
493+
when: edpm_nodes_dcn1 is defined or edpm_nodes_dcn2 is defined
494+
no_log: "{{ use_no_log }}"
495+
ansible.builtin.shell: |
496+
{{ shell_header }}
497+
{{ oc_header }}
498+
499+
# Patch dcn1 nodeset if it exists
500+
{% if edpm_nodes_dcn1 is defined %}
501+
if oc get openstackdataplanenodeset dcn1 -n openstack &>/dev/null; then
502+
# Get current services list and replace 'ovn' with 'ovn-dcn'
503+
SERVICES=$(oc get openstackdataplanenodeset dcn1 -n openstack -o jsonpath='{.spec.services}' | \
504+
sed 's/"ovn"/"ovn-dcn"/g')
505+
506+
# Apply the patch
507+
oc patch openstackdataplanenodeset dcn1 -n openstack --type=merge --patch "
508+
spec:
509+
services: $SERVICES
510+
"
511+
echo "Patched dcn1 nodeset to use ovn-dcn service"
512+
fi
513+
{% endif %}
514+
515+
# Patch dcn2 nodeset if it exists
516+
{% if edpm_nodes_dcn2 is defined %}
517+
if oc get openstackdataplanenodeset dcn2 -n openstack &>/dev/null; then
518+
# Get current services list and replace 'ovn' with 'ovn-dcn'
519+
SERVICES=$(oc get openstackdataplanenodeset dcn2 -n openstack -o jsonpath='{.spec.services}' | \
520+
sed 's/"ovn"/"ovn-dcn"/g')
521+
522+
# Apply the patch
523+
oc patch openstackdataplanenodeset dcn2 -n openstack --type=merge --patch "
524+
spec:
525+
services: $SERVICES
526+
"
527+
echo "Patched dcn2 nodeset to use ovn-dcn service"
528+
fi
529+
{% endif %}
530+
339531
# TODO(bogdando): Apply the ceph backend config for Cinder in the original openstack CR, via kustomize perhaps?
340532
- name: prepare the adopted data plane workloads to use Ceph backend for Cinder, if configured so
341533
no_log: "{{ use_no_log }}"

0 commit comments

Comments
 (0)