Skip to content

Commit 80280d3

Browse files
committed
M #-: Add Open vSwitch role (WIP)
Signed-off-by: Michal Opala <sk4zuzu@gmail.com>
1 parent 13d0343 commit 80280d3

6 files changed

Lines changed: 254 additions & 0 deletions

File tree

playbooks/site.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@
1515
- role: repository
1616
tags: [preinstall, prometheus]
1717

18+
- hosts:
19+
- "{{ frontend_group | d('frontend') }}"
20+
- "{{ node_group | d('node') }}"
21+
collections:
22+
- opennebula.deploy
23+
roles:
24+
- role: openvswitch
25+
tags: [network, openvswitch]
26+
1827
- hosts: "{{ frontend_group | d('frontend') }}"
1928
tags: [frontend, stage1]
2029
collections:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
---

roles/openvswitch/meta/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
---

roles/openvswitch/tasks/main.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
- when:
3+
- ovs is defined
4+
- ovs is mapping
5+
- ovs | count > 0
6+
block:
7+
- name: Install OVS packages
8+
ansible.builtin.package:
9+
name: "{{ _common + (_specific[ansible_distribution] | d(_specific[ansible_os_family])) }}"
10+
vars:
11+
_common:
12+
- findutils
13+
_specific:
14+
AlmaLinux:
15+
- iproute
16+
- iputils
17+
- openvswitch3.5
18+
Debian:
19+
- iproute2
20+
- iputils-arping
21+
- openvswitch-switch
22+
RedHat:
23+
- iproute
24+
- iputils
25+
- openvswitch3.6
26+
register: package
27+
until: package is success
28+
retries: 12
29+
delay: 5
30+
31+
- name: Enable / Start OVS (NOW)
32+
ansible.builtin.systemd_service:
33+
name: "{{ _specific[ansible_os_family] }}"
34+
state: started
35+
enabled: true
36+
vars:
37+
_specific:
38+
Debian: openvswitch-switch.service
39+
RedHat: openvswitch.service
40+
41+
- name: Install OVS-related scripts
42+
ansible.builtin.template:
43+
src: "{{ item.src }}"
44+
dest: "{{ item.dest }}"
45+
mode: "{{ item.mode }}"
46+
owner: 0
47+
group: 0
48+
loop:
49+
- src: opennebula-ovs.sh.jinja
50+
dest: /usr/local/sbin/opennebula-ovs.sh
51+
mode: u=rwx,go=rx
52+
- src: opennebula-ovs.service.jinja
53+
dest: /etc/systemd/system/opennebula-ovs.service
54+
mode: u=rw,go=r
55+
register: template
56+
57+
- name: Switch to OVS networking
58+
ansible.builtin.systemd_service:
59+
daemon_reload: "{{ item.daemon_reload | d(omit) }}"
60+
name: "{{ item.name | d(omit) }}"
61+
state: "{{ item.state | d(omit) }}"
62+
enabled: "{{ item.enabled | d(omit) }}"
63+
loop:
64+
- daemon_reload: "{{ _changed }}"
65+
- name: opennebula-ovs.service
66+
state: "{{ 'restarted' if _changed else 'started' }}"
67+
enabled: true
68+
vars:
69+
_changed: >-
70+
{{ template is changed }}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[Unit]
2+
Description=OVS Bridge Interface Network configuration
3+
{% if ansible_os_family == 'Debian' %}
4+
After=openvswitch-switch.service network-pre.target
5+
Wants=network-pre.target
6+
Before=network.target
7+
Requires=openvswitch-switch.service
8+
{% endif %}
9+
{% if ansible_os_family == 'RedHat' %}
10+
After=openvswitch.service network-pre.target
11+
Wants=network-pre.target
12+
Before=network.target
13+
Requires=openvswitch.service
14+
{% endif %}
15+
16+
[Service]
17+
Type=oneshot
18+
RemainAfterExit=yes
19+
EnvironmentFile=
20+
ExecStart=/usr/local/sbin/opennebula-ovs.sh
21+
TimeoutStartSec=120
22+
StandardOutput=journal
23+
StandardError=journal
24+
# Ensure the service runs in a clean environment
25+
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
26+
# Network operations require root
27+
User=root
28+
29+
[Install]
30+
WantedBy=multi-user.target
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#!/usr/bin/env bash
2+
set -o errexit -o pipefail
3+
4+
# --- Helper functions
5+
6+
log() { echo "$*" >&2; }
7+
die() { log "ERROR: $*"; exit 1; }
8+
9+
# --- Basic asserts
10+
11+
type -p arping find ip jq ovs-vsctl &>/dev/null
12+
13+
{% for iface in ovs.iface.keys() %}
14+
# {{ iface }}
15+
log 'Asserting {{ iface }} exists'
16+
ip link show dev '{{ iface }}' &>/dev/null || die 'Interface {{ iface }} does not exist'
17+
{% endfor %}
18+
19+
# --- Bridge creation
20+
21+
{% for br, v in ovs.br.items() %}
22+
# {{ br }}
23+
if ovs-vsctl br-exists '{{ br }}' &>/dev/null; then
24+
log 'WARNING: Bridge {{ br }} already exists, skipping creation'
25+
else
26+
log 'Creating OVS bridge {{ br }}'
27+
ovs-vsctl add-br '{{ br }}' || die 'Failed to create bridge'
28+
fi
29+
{% for port in v.ports %}
30+
{% if port in ovs.iface %}
31+
if ovs-vsctl port-to-br '{{ port }}' &>/dev/null; then
32+
log 'WARNING: Port {{ port }} already exists, skipping creation'
33+
else
34+
log 'Adding port {{ port }} to {{ br }}'
35+
ovs-vsctl add-port '{{ br }}' '{{ port }}' || die 'Failed to add port'
36+
fi
37+
{% endif %}
38+
{% if port in ovs.bond %}
39+
if ovs-vsctl port-to-br '{{ port }}' &>/dev/null; then
40+
log 'WARNING: Bond {{ port }} already exists, skipping creation'
41+
else
42+
log 'Adding bond {{ port }} to {{ br }}'
43+
ovs-vsctl add-bond '{{ br }}' '{{ port }}' \
44+
{{ ovs.bond[port].ifaces | map('regex_replace', '^(.*)$', "'\g<1>'") | join(' ') }} \
45+
bond_mode='{{ ovs.bond[port].mode | d('active-backup') }}' \
46+
other_config:bond-primary='{{ ovs.bond[port].ifaces | first }}' || die 'Failed to add bond'
47+
fi
48+
{% endif %}
49+
{% endfor %}
50+
{% if v.vlan is defined and (v.vlan is number or v.vlan is string) %}
51+
if [[ "$(ovs-vsctl get port '{{ br }}' tag)" == '{{ v.vlan }}' ]]; then
52+
log 'WARNING: VLAN tag {{ v.vlan }} already set on {{ br }}, skipping'
53+
else
54+
log 'Setting VLAN tag {{ v.vlan }} on {{ br }}'
55+
ovs-vsctl set port '{{ br }}' tag='{{ v.vlan }}' || die 'Failed to set VLAN tag'
56+
fi
57+
{% endif %}
58+
{% endfor %}
59+
60+
# --- Networking cleanup
61+
62+
if type -p systemctl &>/dev/null && systemctl is-active --quiet NetworkManager; then
63+
log 'Stopping and disabling NetworkManager'
64+
systemctl disable NetworkManager || log 'WARNING: Failed to disable NetworkManager'
65+
systemctl stop NetworkManager || log 'WARNING: Failed to stop NetworkManager'
66+
fi
67+
68+
if type -p netplan &>/dev/null && netplan status -f json | jq -re '."netplan-global-state"."online"' &>/dev/null; then
69+
log 'Disabling Netplan'
70+
find /etc/netplan/ -type f -name '*.yaml' -exec mv {} {}.bak \;
71+
netplan apply || log 'WARNING: Failed to disable Netplan'
72+
fi
73+
74+
{% for vlan in ovs.br.values() | selectattr('vlan', 'defined') | map(attribute='vlan') %}
75+
{% for iface in ovs.iface.keys() %}
76+
# {{ iface }}.{{ vlan }}
77+
if ip link show '{{ iface }}.{{ vlan }}' &>/dev/null; then
78+
log 'Deleting VLAN interface {{ iface }}.{{ vlan }}'
79+
ip link delete '{{ iface }}.{{ vlan }}' || log 'WARNING: Failed to delete {{ iface }}.{{ vlan }}'
80+
fi
81+
{% endfor %}
82+
{% endfor %}
83+
84+
# --- IP / MTU configuration
85+
86+
{% for dev, v in ((ovs.iface.items() | list) + (ovs.br.items() | list)) %}
87+
# {{ dev }}
88+
log 'Flushing {{ dev }}'
89+
ip addr flush dev '{{ dev }}' || die 'Failed to flush {{ dev }}'
90+
{% for a in v.addrs | d([]) %}
91+
log 'Adding IP address {{ a.cidr }} to {{ dev }}'
92+
ip addr add '{{ a.cidr }}' dev '{{ dev }}' {{ "metric '{}'".format(a.metric) if a.metric is defined else "" }} || die 'Failed to add IP address'
93+
{% endfor %}
94+
{% if v.mtu is defined and (v.mtu is number or v.mtu is string) %}
95+
if [[ "$(ovs-vsctl get Interface '{{ dev }}' mtu_request)" == '{{ v.mtu }}' ]]; then
96+
log 'WARNING: MTU {{ v.mtu }} already set on {{ dev }}, skipping'
97+
else
98+
log 'Setting MTU {{ v.mtu }} on {{ dev }}'
99+
ovs-vsctl set Interface '{{ dev }}' mtu_request='{{ v.mtu }}' || die 'Failed to set MTU'
100+
fi
101+
{% endif %}
102+
log 'Bringing up {{ dev }}'
103+
ip link set dev '{{ dev }}' up || die 'Failed to bring up {{ dev }}'
104+
{% endfor %}
105+
106+
# --- Default route
107+
108+
{% set gw = (((ovs.iface.values() | list) + (ovs.br.values() | list)) | selectattr('gw', 'defined') | first).gw | d(None) %}
109+
{% if gw is not none %}
110+
if ip --json route show default | jq -re '. != []' &>/dev/null; then
111+
log 'WARNING: Default route already exists'
112+
else
113+
log 'Adding default route via {{ gw }}'
114+
ip route add default via '{{ gw }}' || die 'Failed to add default route'
115+
fi
116+
{% endif %}
117+
118+
# --- Nameserver configuration
119+
120+
if type -p systemctl resolvectl &>/dev/null && systemctl is-active --quiet systemd-resolved; then
121+
{% for dev, v in ((ovs.iface.items() | list) + (ovs.br.items() | list)) %}
122+
# {{ dev }}
123+
{% if v.dns is defined and v.dns is sequence %}
124+
log 'Setting nameservers for {{ dev }}'
125+
resolvectl dns '{{ dev }}' {{ v.dns | map('regex_replace', '^(.*)$', "'\g<1>'") | join(' ') }} || die 'Failed to setup nameservers'
126+
{% endif %}
127+
{% endfor %}
128+
fi
129+
130+
# --- Networking refresh
131+
132+
{% for br, v in ovs.br.items() %}
133+
# {{ br }}
134+
{% for a in v.addrs | d([]) %}
135+
log 'Sending gratuitous ARP for {{ a.cidr }} on {{ br }}'
136+
arping -c 3 -A -I '{{ br }}' '{{ a.cidr.split('/') | first }}' || log 'WARNING: arping failed'
137+
{% endfor %}
138+
{% endfor %}
139+
140+
# ---
141+
142+
log 'OVS network configuration completed successfully'
143+
exit 0

0 commit comments

Comments
 (0)