Skip to content

Commit b847ed7

Browse files
Merge pull request #17 from shiftstack/support_ipv6_secondary_OSPRH-6486
Support IPv6 Secondary Interface in shiftstack-qa automation
2 parents 378fd15 + 9930f71 commit b847ed7

14 files changed

Lines changed: 580 additions & 1 deletion

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
ansible.builtin.shell: >
32+
openstack network delete {{ item }}
33+
loop: "{{ registered_resources.ipv6_secondary_network_ids | default([]) }}"
34+
environment:
35+
OS_CLOUD: "{{ user_cloud }}"
36+
changed_when: true
37+
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: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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 all workers via live state
41+
ansible.builtin.shell: |
42+
set -o pipefail && \
43+
timeout 120 oc debug node/{{ item.0 }} -- chroot /host \
44+
ip -6 addr show scope global | \
45+
grep -B1 '{{ item.1.cidr | regex_replace('::/64$', '') }}' | \
46+
awk -F': ' '/^[0-9]/{print $2}' | head -1
47+
environment:
48+
KUBECONFIG: "{{ kubeconfig }}"
49+
with_nested:
50+
- "{{ ocp_workers }}"
51+
- "{{ ipv6_secondary_networks.networks }}"
52+
register: all_worker_interfaces
53+
changed_when: false
54+
retries: 10
55+
delay: 30
56+
until: all_worker_interfaces.stdout != ""
57+
58+
- name: Validate consistent NIC naming across all workers
59+
ansible.builtin.assert:
60+
that:
61+
- per_network_interfaces | unique | length == 1
62+
fail_msg: |
63+
NIC naming inconsistency for {{ item.net_name }}: {{ per_network_interfaces }}
64+
Workers have different interface names for the same IPv6 network.
65+
vars:
66+
per_network_interfaces: "{{ all_worker_interfaces.results | selectattr('item.1.net_name', 'equalto', item.net_name) | map(attribute='stdout') | list }}"
67+
loop: "{{ ipv6_secondary_networks.networks }}"
68+
69+
- name: Store IPv6 interface names (validated consistent across workers)
70+
ansible.builtin.set_fact:
71+
ipv6_interfaces: "{{ all_worker_interfaces.results | selectattr('item.0', 'equalto', ocp_workers[0]) | map(attribute='stdout') | list }}"
72+
73+
- name: Validate discovered IPv6 interfaces are not empty
74+
ansible.builtin.assert:
75+
that:
76+
- ipv6_interfaces | length == ipv6_secondary_networks.networks | length
77+
- ipv6_interfaces | select('equalto', '') | list | length == 0
78+
fail_msg: |
79+
Failed to discover IPv6 interfaces on workers.
80+
Expected {{ ipv6_secondary_networks.networks | length }} interfaces, got: {{ ipv6_interfaces }}
81+
82+
- name: Display discovered IPv6 interfaces
83+
ansible.builtin.debug:
84+
var: ipv6_interfaces
85+
86+
- name: Template CNO macvlan patch
87+
ansible.builtin.template:
88+
src: network-macvlan.yml.j2
89+
dest: "/tmp/network-macvlan.yml"
90+
mode: u=rw,g=rw,o=r
91+
92+
- name: Create OCP projects for IPv6 network testing
93+
kubernetes.core.k8s:
94+
kubeconfig: "{{ kubeconfig }}"
95+
api_version: project.openshift.io/v1
96+
kind: Project
97+
name: "{{ item }}"
98+
state: present
99+
loop: "{{ ipv6_secondary_networks.projects }}"
100+
101+
- name: Patch CNO with macvlan additionalNetworks
102+
ansible.builtin.shell: |
103+
set -o pipefail && \
104+
oc patch network.operator cluster --patch "$(cat /tmp/network-macvlan.yml)" --type merge
105+
environment:
106+
KUBECONFIG: "{{ kubeconfig }}"
107+
changed_when: true
108+
109+
- name: Verify NetworkAttachmentDefinition CR exists in {{ item }}
110+
kubernetes.core.k8s_info:
111+
kubeconfig: "{{ kubeconfig }}"
112+
api_version: k8s.cni.cncf.io/v1
113+
kind: NetworkAttachmentDefinition
114+
namespace: "{{ item }}"
115+
register: network_attachments
116+
until:
117+
- network_attachments is defined
118+
- network_attachments is not failed
119+
- network_attachments.resources | length > 0
120+
retries: 10
121+
delay: 30
122+
loop: "{{ ipv6_secondary_networks.projects }}"
123+
124+
- name: Template IPv6 test deployments
125+
ansible.builtin.template:
126+
src: ipv6-deployment.yml.j2
127+
dest: "/tmp/ipv6-deployment-{{ item }}.yml"
128+
mode: u=rw,g=rw,o=r
129+
vars:
130+
ipv6_project: "{{ item }}"
131+
loop: "{{ ipv6_secondary_networks.projects }}"
132+
133+
- name: Deploy hello-openshift pods with IPv6 network annotation
134+
kubernetes.core.k8s:
135+
kubeconfig: "{{ kubeconfig }}"
136+
state: present
137+
src: "/tmp/ipv6-deployment-{{ item }}.yml"
138+
wait: true
139+
wait_timeout: 300
140+
namespace: "{{ item }}"
141+
loop: "{{ ipv6_secondary_networks.projects }}"
142+
143+
- name: Wait for pods to be ready in each IPv6 namespace
144+
kubernetes.core.k8s_info:
145+
kubeconfig: "{{ kubeconfig }}"
146+
kind: Pod
147+
namespace: "{{ item }}"
148+
label_selectors:
149+
- app=hello-openshift
150+
field_selectors:
151+
- status.phase=Running
152+
register: running_pods
153+
until: running_pods.resources | length == num_workers | int
154+
retries: 20
155+
delay: 15
156+
loop: "{{ ipv6_secondary_networks.projects }}"
157+
158+
- name: Get IPv6 network IDs for port security operations
159+
openstack.cloud.networks_info:
160+
cloud: "{{ user_cloud }}"
161+
register: all_openstack_networks
162+
163+
- name: Build list of IPv6 secondary network IDs
164+
ansible.builtin.set_fact:
165+
ipv6_net_ids: "{{ all_openstack_networks.networks | selectattr('name', 'in', ipv6_secondary_networks.networks | map(attribute='net_name') | list) | map(attribute='id') | list }}"
166+
167+
- name: Get worker ports on IPv6 secondary networks
168+
openstack.cloud.port_info:
169+
cloud: "{{ user_cloud }}"
170+
filters:
171+
device_owner: compute:nova
172+
register: all_compute_ports
173+
174+
- name: Filter worker ports on IPv6 networks
175+
ansible.builtin.set_fact:
176+
ipv6_worker_ports: "{{ all_compute_ports.ports | selectattr('network_id', 'in', ipv6_net_ids) | list }}"
177+
178+
- name: Disable port security on worker IPv6 ports
179+
openstack.cloud.port:
180+
cloud: "{{ user_cloud }}"
181+
state: present
182+
name: "{{ item.id }}"
183+
network: "{{ item.network_id }}"
184+
port_security_enabled: false
185+
no_security_groups: true
186+
loop: "{{ ipv6_worker_ports }}"
187+
loop_control:
188+
label: "{{ item.id }}"
189+
190+
- name: Verify pod-to-pod IPv6 connectivity on each network
191+
ansible.builtin.include_tasks: procedures/verify_ipv6_connectivity.yml
192+
loop: "{{ ipv6_secondary_networks.projects }}"
193+
loop_control:
194+
loop_var: ipv6_namespace
195+
196+
- name: Verify external IPv6 reachability from worker nodes
197+
ansible.builtin.include_tasks: procedures/verify_ipv6_external_reachability.yml
198+
loop: "{{ ipv6_secondary_networks.projects }}"
199+
loop_control:
200+
loop_var: ipv6_namespace
201+
202+
- name: Cleanup temporary manifest files
203+
ansible.builtin.file:
204+
path: "{{ item }}"
205+
state: absent
206+
loop: "{{ ['/tmp/network-macvlan.yml'] + ipv6_secondary_networks.projects | map('regex_replace', '^(.*)$', '/tmp/ipv6-deployment-\\1.yml') | list }}"
207+
loop_control:
208+
label: "{{ item }}"
209+
ignore_errors: true # noqa: ignore-errors
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
- name: Reset pod IP mapping for {{ ipv6_namespace }}
3+
ansible.builtin.set_fact:
4+
ipv6_pod_ips: {}
5+
6+
- name: Get pods in namespace {{ ipv6_namespace }}
7+
kubernetes.core.k8s_info:
8+
kubeconfig: "{{ kubeconfig }}"
9+
kind: Pod
10+
namespace: "{{ ipv6_namespace }}"
11+
label_selectors:
12+
- app=hello-openshift
13+
field_selectors:
14+
- status.phase=Running
15+
register: ipv6_pods
16+
17+
- name: Store pod names for {{ ipv6_namespace }}
18+
ansible.builtin.set_fact:
19+
ipv6_pod_names: "{{ ipv6_pods.resources | map(attribute='metadata.name') | list }}"
20+
21+
- name: Get pod IPv6 addresses on net1 interface
22+
ansible.builtin.shell: |
23+
set -o pipefail && \
24+
oc exec {{ item }} -n {{ ipv6_namespace }} -- ip -6 addr show dev net1 scope global | \
25+
awk '/inet6/{print $2}' | cut -f1 -d'/'
26+
environment:
27+
KUBECONFIG: "{{ kubeconfig }}"
28+
loop: "{{ ipv6_pod_names }}"
29+
register: pod_ipv6_results
30+
changed_when: false
31+
retries: 20
32+
delay: 30
33+
until: pod_ipv6_results.stdout != ""
34+
35+
- name: Build pod name to IPv6 address mapping
36+
ansible.builtin.set_fact:
37+
ipv6_pod_ips: "{{ ipv6_pod_ips | default({}) | combine({item.item: item.stdout}) }}"
38+
loop: "{{ pod_ipv6_results.results }}"
39+
loop_control:
40+
label: "{{ item.item }}"
41+
42+
- name: Display pod IPv6 addresses for {{ ipv6_namespace }}
43+
ansible.builtin.debug:
44+
var: ipv6_pod_ips
45+
46+
- name: Validate all pods have IPv6 addresses
47+
ansible.builtin.assert:
48+
that:
49+
- ipv6_pod_ips | length > 0
50+
- ipv6_pod_ips.values() | select('equalto', '') | list | length == 0
51+
fail_msg: |
52+
Not all pods in {{ ipv6_namespace }} received IPv6 addresses.
53+
Pod IPs: {{ ipv6_pod_ips }}
54+
55+
- name: Check pod-to-pod IPv6 connectivity in {{ ipv6_namespace }}
56+
ansible.builtin.shell: |
57+
set -o pipefail && \
58+
oc exec {{ item[0] }} -n {{ ipv6_namespace }} -- /bin/curl -s --connect-timeout 10 http://[{{ item[1] }}]:8080
59+
environment:
60+
KUBECONFIG: "{{ kubeconfig }}"
61+
with_nested:
62+
- "{{ ipv6_pod_ips.keys() | list }}"
63+
- "{{ ipv6_pod_ips.values() | list }}"
64+
when: ipv6_pod_ips[item[0]] != item[1]
65+
register: connectivity_check
66+
changed_when: false
67+
retries: 5
68+
delay: 10
69+
until: connectivity_check.stdout is search('Hello OpenShift!')
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
- name: Reset pod-node mapping for {{ ipv6_namespace }}
3+
ansible.builtin.set_fact:
4+
ipv6_ext_pod_nodes: {}
5+
6+
- name: Get pods with node info in namespace {{ ipv6_namespace }} for external reachability test
7+
kubernetes.core.k8s_info:
8+
kubeconfig: "{{ kubeconfig }}"
9+
kind: Pod
10+
namespace: "{{ ipv6_namespace }}"
11+
label_selectors:
12+
- app=hello-openshift
13+
field_selectors:
14+
- status.phase=Running
15+
register: ipv6_ext_pods
16+
17+
- name: Build pod-to-node mapping for {{ ipv6_namespace }}
18+
ansible.builtin.set_fact:
19+
ipv6_ext_pod_nodes: "{{ ipv6_ext_pod_nodes | default({}) | combine({item.metadata.name: item.spec.nodeName}) }}"
20+
loop: "{{ ipv6_ext_pods.resources }}"
21+
loop_control:
22+
label: "{{ item.metadata.name }}"
23+
24+
- name: Get pod IPv6 addresses for external test
25+
ansible.builtin.shell: |
26+
set -o pipefail && \
27+
oc exec {{ item }} -n {{ ipv6_namespace }} -- ip -6 addr show dev net1 scope global | \
28+
awk '/inet6/{print $2}' | cut -f1 -d'/'
29+
environment:
30+
KUBECONFIG: "{{ kubeconfig }}"
31+
loop: "{{ ipv6_ext_pod_nodes.keys() | list }}"
32+
register: ext_pod_ipv6_results
33+
changed_when: false
34+
35+
- name: Verify external IPv6 reachability from cross-node worker to pods in {{ ipv6_namespace }}
36+
ansible.builtin.shell: |
37+
set -o pipefail && \
38+
timeout 120 oc debug node/{{ ocp_workers | difference([ipv6_ext_pod_nodes[item.item]]) | first }} -- \
39+
chroot /host curl -s --connect-timeout 10 http://[{{ item.stdout }}]:8080
40+
environment:
41+
KUBECONFIG: "{{ kubeconfig }}"
42+
loop: "{{ ext_pod_ipv6_results.results }}"
43+
loop_control:
44+
label: "{{ item.item }}"
45+
when: item.stdout != ""
46+
register: external_check
47+
changed_when: false
48+
retries: 5
49+
delay: 10
50+
until: external_check.stdout is search('Hello OpenShift!')

0 commit comments

Comments
 (0)