Skip to content

Commit 52bb7ae

Browse files
Add IPv6 secondary interface support (prepare + CNO configuration) OSPRH-6486
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 2fccbb8 commit 52bb7ae

13 files changed

Lines changed: 478 additions & 1 deletion

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
- name: Check if IPv6 secondary resources exist (via resources file)
3+
ansible.builtin.include_vars:
4+
file: "{{ resources_file }}"
5+
name: registered_resources
6+
ignore_errors: true # noqa: ignore-errors
7+
register: resources_load
8+
9+
- name: Cleanup IPv6 secondary OpenStack resources
10+
when:
11+
- resources_load is succeeded
12+
- registered_resources.ipv6_secondary_router_name is defined
13+
block:
14+
- name: Detach IPv6 secondary subnets from router
15+
ansible.builtin.shell: >
16+
openstack router remove subnet {{ registered_resources.ipv6_secondary_router_name }} {{ item }}
17+
loop: "{{ registered_resources.ipv6_secondary_subnet_ids | default([]) }}"
18+
environment:
19+
OS_CLOUD: "{{ user_cloud }}"
20+
changed_when: true
21+
ignore_errors: true # noqa: ignore-errors
22+
23+
- name: Delete IPv6 secondary router
24+
openstack.cloud.router:
25+
cloud: "{{ user_cloud }}"
26+
state: absent
27+
name: "{{ registered_resources.ipv6_secondary_router_name }}"
28+
ignore_errors: true # noqa: ignore-errors
29+
30+
- name: Delete IPv6 secondary networks
31+
openstack.cloud.network:
32+
cloud: "{{ user_cloud }}"
33+
state: absent
34+
name: "{{ item.net_name }}"
35+
loop: "{{ ipv6_secondary_networks.networks }}"
36+
ignore_errors: true # noqa: ignore-errors

collection/stages/roles/cleanup/tasks/main.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@
6868
- name: Discover the OpenShift installation type
6969
ansible.builtin.include_tasks: detect_ocp_installation.yml
7070

71+
- name: Cleanup IPv6 secondary network resources if they exist
72+
ansible.builtin.include_tasks: cleanup_ipv6_secondary.yml
73+
when: ocp_deployment_topology.secondary_ip_protocol | default('') == 'ipv6'
74+
7175
- name: Destroy the OpenShift cluster if it exists
7276
ansible.builtin.include_tasks: destroy_openshift_cluster.yml
7377
when: existing_ocp_installation_type != ''
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
apiVersion: machineconfiguration.openshift.io/v1
3+
kind: MachineConfig
4+
metadata:
5+
labels:
6+
machineconfiguration.openshift.io/role: worker
7+
name: 05-worker-kernelarg-dhcp
8+
spec:
9+
config:
10+
ignition:
11+
version: 3.2.0
12+
kernelArguments:
13+
- ip=dhcp,dhcp6
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
---
2+
- name: Apply DHCP MachineConfig to workers
3+
kubernetes.core.k8s:
4+
kubeconfig: "{{ kubeconfig }}"
5+
state: present
6+
definition: "{{ lookup('file', '../files/worker-machineconfig-dhcp.yaml') | from_yaml }}"
7+
8+
- name: Wait for the MCP to finish the cluster updates
9+
ansible.builtin.include_role:
10+
name: tools_cluster_checks
11+
tasks_from: wait_mcp_updated.yml
12+
vars:
13+
wait_retries: 60
14+
wait_delay: 60
15+
16+
- name: Active wait until all the ClusterOperators are ready
17+
ansible.builtin.include_role:
18+
name: tools_cluster_checks
19+
tasks_from: wait_until_cluster_operators_ready.yml
20+
21+
- name: Wait until OCP cluster is healthy
22+
ansible.builtin.include_role:
23+
name: tools_cluster_checks
24+
tasks_from: wait_until_cluster_is_healthy.yml
25+
26+
- name: Get OCP worker nodes
27+
kubernetes.core.k8s_info:
28+
kubeconfig: "{{ kubeconfig }}"
29+
api_version: v1
30+
kind: Node
31+
label_selectors:
32+
- node-role.kubernetes.io/worker
33+
register: workers
34+
35+
- name: Store the OCP worker node names
36+
ansible.builtin.set_fact:
37+
ocp_workers: "{{ workers | json_query('resources[*].metadata.name') | sort }}"
38+
num_workers: "{{ workers.resources | length }}"
39+
40+
- name: Discover IPv6 interfaces on first worker
41+
ansible.builtin.shell: |
42+
set -o pipefail && \
43+
oc adm node-logs {{ ocp_workers[0] }} | \
44+
grep '{{ item.cidr | regex_replace('::/64$', '') }}' | \
45+
grep dev | \
46+
sed 's/.*{{ item.cidr | regex_replace('::/64$', '') }}::.* dev \(\S\+\).*/\1/g' | \
47+
tail -1
48+
environment:
49+
KUBECONFIG: "{{ kubeconfig }}"
50+
loop: "{{ ipv6_secondary_networks.networks }}"
51+
register: ipv6_interfaces_results
52+
changed_when: false
53+
retries: 10
54+
delay: 30
55+
until: ipv6_interfaces_results.stdout != ""
56+
57+
- name: Store IPv6 interface names
58+
ansible.builtin.set_fact:
59+
ipv6_interfaces: "{{ ipv6_interfaces_results.results | map(attribute='stdout') | list }}"
60+
61+
- name: Display discovered IPv6 interfaces
62+
ansible.builtin.debug:
63+
var: ipv6_interfaces
64+
65+
- name: Template CNO macvlan patch
66+
ansible.builtin.template:
67+
src: network-macvlan.yml.j2
68+
dest: "/tmp/network-macvlan.yml"
69+
mode: u=rw,g=rw,o=r
70+
71+
- name: Create OCP projects for IPv6 network testing
72+
kubernetes.core.k8s:
73+
kubeconfig: "{{ kubeconfig }}"
74+
api_version: project.openshift.io/v1
75+
kind: Project
76+
name: "{{ item }}"
77+
state: present
78+
loop: "{{ ipv6_secondary_networks.projects }}"
79+
80+
- name: Patch CNO with macvlan additionalNetworks
81+
ansible.builtin.shell: |
82+
set -o pipefail && \
83+
oc patch network.operator cluster --patch "$(cat /tmp/network-macvlan.yml)" --type merge
84+
environment:
85+
KUBECONFIG: "{{ kubeconfig }}"
86+
changed_when: true
87+
88+
- name: Verify NetworkAttachmentDefinition CRs exist
89+
kubernetes.core.k8s_info:
90+
kubeconfig: "{{ kubeconfig }}"
91+
api_version: k8s.cni.cncf.io/v1
92+
kind: NetworkAttachmentDefinition
93+
register: network_attachments
94+
until:
95+
- network_attachments is defined
96+
- network_attachments is not failed
97+
- network_attachments | json_query('resources[*].metadata.name') | list | sort == ipv6_secondary_networks.projects | sort
98+
retries: 10
99+
delay: 30
100+
101+
- name: Template IPv6 test deployments
102+
ansible.builtin.template:
103+
src: ipv6-deployment.yml.j2
104+
dest: "/tmp/ipv6-deployment-{{ item }}.yml"
105+
mode: u=rw,g=rw,o=r
106+
vars:
107+
ipv6_project: "{{ item }}"
108+
loop: "{{ ipv6_secondary_networks.projects }}"
109+
110+
- name: Deploy hello-openshift pods with IPv6 network annotation
111+
kubernetes.core.k8s:
112+
kubeconfig: "{{ kubeconfig }}"
113+
state: present
114+
src: "/tmp/ipv6-deployment-{{ item }}.yml"
115+
wait: true
116+
wait_timeout: 300
117+
namespace: "{{ item }}"
118+
loop: "{{ ipv6_secondary_networks.projects }}"
119+
120+
- name: Wait for pods to be ready in each IPv6 namespace
121+
kubernetes.core.k8s_info:
122+
kubeconfig: "{{ kubeconfig }}"
123+
kind: Pod
124+
namespace: "{{ item }}"
125+
label_selectors:
126+
- app=hello-openshift
127+
field_selectors:
128+
- status.phase=Running
129+
register: running_pods
130+
until: running_pods.resources | length == num_workers | int
131+
retries: 20
132+
delay: 15
133+
loop: "{{ ipv6_secondary_networks.projects }}"
134+
135+
- name: Get IPv6 network IDs for port security operations
136+
openstack.cloud.networks_info:
137+
cloud: "{{ user_cloud }}"
138+
register: all_openstack_networks
139+
140+
- name: Build list of IPv6 secondary network IDs
141+
ansible.builtin.set_fact:
142+
ipv6_net_ids: "{{ all_openstack_networks.networks | selectattr('name', 'in', ipv6_secondary_networks.networks | map(attribute='net_name') | list) | map(attribute='id') | list }}"
143+
144+
- name: Get worker ports on IPv6 secondary networks
145+
openstack.cloud.port_info:
146+
cloud: "{{ user_cloud }}"
147+
filters:
148+
device_owner: compute:nova
149+
register: all_compute_ports
150+
151+
- name: Filter worker ports on IPv6 networks
152+
ansible.builtin.set_fact:
153+
ipv6_worker_ports: "{{ all_compute_ports.ports | selectattr('network_id', 'in', ipv6_net_ids) | list }}"
154+
155+
- name: Disable port security on worker IPv6 ports
156+
openstack.cloud.port:
157+
cloud: "{{ user_cloud }}"
158+
state: present
159+
name: "{{ item.id }}"
160+
port_security_enabled: false
161+
no_security_groups: true
162+
loop: "{{ ipv6_worker_ports }}"
163+
loop_control:
164+
label: "{{ item.id }}"
165+
166+
- name: Verify pod-to-pod IPv6 connectivity on each network
167+
ansible.builtin.include_tasks: procedures/verify_ipv6_connectivity.yml
168+
loop: "{{ ipv6_secondary_networks.projects }}"
169+
loop_control:
170+
loop_var: ipv6_namespace
171+
172+
- name: Verify external IPv6 reachability from worker nodes
173+
ansible.builtin.include_tasks: procedures/verify_ipv6_external_reachability.yml
174+
loop: "{{ ipv6_secondary_networks.projects }}"
175+
loop_control:
176+
loop_var: ipv6_namespace
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
- name: Get pods in namespace {{ ipv6_namespace }}
3+
kubernetes.core.k8s_info:
4+
kubeconfig: "{{ kubeconfig }}"
5+
kind: Pod
6+
namespace: "{{ ipv6_namespace }}"
7+
label_selectors:
8+
- app=hello-openshift
9+
field_selectors:
10+
- status.phase=Running
11+
register: ipv6_pods
12+
13+
- name: Store pod names for {{ ipv6_namespace }}
14+
ansible.builtin.set_fact:
15+
ipv6_pod_names: "{{ ipv6_pods.resources | map(attribute='metadata.name') | list }}"
16+
17+
- name: Get pod IPv6 addresses on net1 interface
18+
ansible.builtin.shell: |
19+
set -o pipefail && \
20+
oc exec {{ item }} -n {{ ipv6_namespace }} -- ip -6 addr show dev net1 scope global | \
21+
awk '/inet6/{print $2}' | cut -f1 -d'/'
22+
environment:
23+
KUBECONFIG: "{{ kubeconfig }}"
24+
loop: "{{ ipv6_pod_names }}"
25+
register: pod_ipv6_results
26+
changed_when: false
27+
retries: 20
28+
delay: 30
29+
until: pod_ipv6_results.stdout != ""
30+
31+
- name: Build pod name to IPv6 address mapping
32+
ansible.builtin.set_fact:
33+
ipv6_pod_ips: "{{ ipv6_pod_ips | default({}) | combine({item.item: item.stdout}) }}"
34+
loop: "{{ pod_ipv6_results.results }}"
35+
loop_control:
36+
label: "{{ item.item }}"
37+
38+
- name: Display pod IPv6 addresses for {{ ipv6_namespace }}
39+
ansible.builtin.debug:
40+
var: ipv6_pod_ips
41+
42+
- name: Check pod-to-pod IPv6 connectivity in {{ ipv6_namespace }}
43+
ansible.builtin.shell: |
44+
oc exec {{ item[0] }} -n {{ ipv6_namespace }} -- /bin/curl -s --connect-timeout 10 http://[{{ item[1] }}]:8080
45+
environment:
46+
KUBECONFIG: "{{ kubeconfig }}"
47+
with_nested:
48+
- "{{ ipv6_pod_ips.keys() | list }}"
49+
- "{{ ipv6_pod_ips.values() | list }}"
50+
when: ipv6_pod_ips[item[0]] != item[1]
51+
register: connectivity_check
52+
changed_when: false
53+
retries: 5
54+
delay: 10
55+
until: connectivity_check.stdout is search('Hello OpenShift!')
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
- name: Get pods in namespace {{ ipv6_namespace }} for external reachability test
3+
kubernetes.core.k8s_info:
4+
kubeconfig: "{{ kubeconfig }}"
5+
kind: Pod
6+
namespace: "{{ ipv6_namespace }}"
7+
label_selectors:
8+
- app=hello-openshift
9+
field_selectors:
10+
- status.phase=Running
11+
register: ipv6_ext_pods
12+
13+
- name: Store pod names for external test
14+
ansible.builtin.set_fact:
15+
ipv6_ext_pod_names: "{{ ipv6_ext_pods.resources | map(attribute='metadata.name') | list }}"
16+
17+
- name: Get pod IPv6 addresses for external test
18+
ansible.builtin.shell: |
19+
set -o pipefail && \
20+
oc exec {{ item }} -n {{ ipv6_namespace }} -- ip -6 addr show dev net1 scope global | \
21+
awk '/inet6/{print $2}' | cut -f1 -d'/'
22+
environment:
23+
KUBECONFIG: "{{ kubeconfig }}"
24+
loop: "{{ ipv6_ext_pod_names }}"
25+
register: ext_pod_ipv6_results
26+
changed_when: false
27+
28+
- name: Verify external IPv6 reachability from worker node to pods in {{ ipv6_namespace }}
29+
ansible.builtin.shell: |
30+
oc debug node/{{ ocp_workers[0] }} -- chroot /host curl -s --connect-timeout 10 http://[{{ item.stdout }}]:8080
31+
environment:
32+
KUBECONFIG: "{{ kubeconfig }}"
33+
loop: "{{ ext_pod_ipv6_results.results }}"
34+
loop_control:
35+
label: "{{ item.item }}"
36+
when: item.stdout != ""
37+
register: external_check
38+
changed_when: false
39+
retries: 5
40+
delay: 10
41+
until: external_check.stdout is search('Hello OpenShift!')
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: hello-openshift
5+
namespace: {{ ipv6_project }}
6+
spec:
7+
replicas: {{ num_workers }}
8+
selector:
9+
matchLabels:
10+
app: hello-openshift
11+
template:
12+
metadata:
13+
labels:
14+
app: hello-openshift
15+
annotations:
16+
k8s.v1.cni.cncf.io/networks: "{{ ipv6_project }}"
17+
spec:
18+
affinity:
19+
podAntiAffinity:
20+
requiredDuringSchedulingIgnoredDuringExecution:
21+
- labelSelector:
22+
matchExpressions:
23+
- key: app
24+
operator: In
25+
values:
26+
- hello-openshift
27+
topologyKey: kubernetes.io/hostname
28+
securityContext:
29+
runAsNonRoot: true
30+
seccompProfile:
31+
type: RuntimeDefault
32+
containers:
33+
- name: hello-openshift
34+
securityContext:
35+
allowPrivilegeEscalation: false
36+
capabilities:
37+
drop:
38+
- ALL
39+
image: quay.io/openshift/origin-hello-openshift
40+
ports:
41+
- containerPort: 8080
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
spec:
2+
additionalNetworks:
3+
{% for network in ipv6_secondary_networks.networks %}
4+
- name: {{ network.net_name }}
5+
namespace: {{ network.net_name }}
6+
rawCNIConfig: |-
7+
{
8+
"cniVersion": "0.3.1",
9+
"name": "{{ network.net_name }}",
10+
"type": "macvlan",
11+
"master": "{{ ipv6_interfaces[loop.index0] }}"
12+
}
13+
type: Raw
14+
{% endfor %}

0 commit comments

Comments
 (0)