summaryrefslogtreecommitdiff
path: root/tests/integration_tests/modules/test_hotplug.py
diff options
context:
space:
mode:
authorJames Falcon <therealfalcon@gmail.com>2021-07-19 14:13:21 -0500
committerGitHub <noreply@github.com>2021-07-19 14:13:21 -0500
commit184c836a16e9954a2cba11ae21f07923077ec904 (patch)
tree6289d70e4f833d300a25136dde6a56fcd1b0a0dc /tests/integration_tests/modules/test_hotplug.py
parenteacb0353803263934aa2ac827c37e461c87cb107 (diff)
downloadvyos-cloud-init-184c836a16e9954a2cba11ae21f07923077ec904.tar.gz
vyos-cloud-init-184c836a16e9954a2cba11ae21f07923077ec904.zip
Initial hotplug support (#936)
Adds a udev script which will invoke a hotplug hook script on all net add events. The script will write some udev arguments to a systemd FIFO socket (to ensure we have only instance of cloud-init running at a time), which is then read by a new service that calls a new 'cloud-init devel hotplug-hook' command to handle the new event. This hotplug-hook command will: - Fetch the pickled datsource - Verify that the hotplug event is supported/enabled - Update the metadata for the datasource - Ensure the hotplugged device exists within the datasource - Apply the config change on the datasource metadata - Bring up the new interface (or apply global network configuration) - Save the updated metadata back to the pickle cache Also scattered in some unrelated typing where helpful
Diffstat (limited to 'tests/integration_tests/modules/test_hotplug.py')
-rw-r--r--tests/integration_tests/modules/test_hotplug.py94
1 files changed, 94 insertions, 0 deletions
diff --git a/tests/integration_tests/modules/test_hotplug.py b/tests/integration_tests/modules/test_hotplug.py
new file mode 100644
index 00000000..b683566f
--- /dev/null
+++ b/tests/integration_tests/modules/test_hotplug.py
@@ -0,0 +1,94 @@
+import pytest
+import time
+import yaml
+from collections import namedtuple
+
+from tests.integration_tests.instances import IntegrationInstance
+
+USER_DATA = """\
+#cloud-config
+updates:
+ network:
+ when: ['hotplug']
+"""
+
+ip_addr = namedtuple('ip_addr', 'interface state ip4 ip6')
+
+
+def _wait_till_hotplug_complete(client, expected_runs=1):
+ for _ in range(60):
+ log = client.read_from_file('/var/log/cloud-init.log')
+ if log.count('Exiting hotplug handler') == expected_runs:
+ return log
+ time.sleep(1)
+ raise Exception('Waiting for hotplug handler failed')
+
+
+def _get_ip_addr(client):
+ ips = []
+ lines = client.execute('ip --brief addr').split('\n')
+ for line in lines:
+ attributes = line.split()
+ interface, state = attributes[0], attributes[1]
+ ip4_cidr = attributes[2] if len(attributes) > 2 else None
+ ip6_cidr = attributes[3] if len(attributes) > 3 else None
+ ip4 = ip4_cidr.split('/')[0] if ip4_cidr else None
+ ip6 = ip6_cidr.split('/')[0] if ip6_cidr else None
+ ip = ip_addr(interface, state, ip4, ip6)
+ ips.append(ip)
+ return ips
+
+
+@pytest.mark.openstack
+@pytest.mark.user_data(USER_DATA)
+def test_hotplug_add_remove(client: IntegrationInstance):
+ ips_before = _get_ip_addr(client)
+ log = client.read_from_file('/var/log/cloud-init.log')
+ assert 'Exiting hotplug handler' not in log
+
+ # Add new NIC
+ added_ip = client.instance.add_network_interface()
+ _wait_till_hotplug_complete(client)
+ ips_after_add = _get_ip_addr(client)
+ new_addition = [ip for ip in ips_after_add if ip.ip4 == added_ip][0]
+
+ assert len(ips_after_add) == len(ips_before) + 1
+ assert added_ip not in [ip.ip4 for ip in ips_before]
+ assert added_ip in [ip.ip4 for ip in ips_after_add]
+ assert new_addition.state == 'UP'
+
+ netplan_cfg = client.read_from_file('/etc/netplan/50-cloud-init.yaml')
+ config = yaml.safe_load(netplan_cfg)
+ assert new_addition.interface in config['network']['ethernets']
+
+ # Remove new NIC
+ client.instance.remove_network_interface(added_ip)
+ _wait_till_hotplug_complete(client, expected_runs=2)
+ ips_after_remove = _get_ip_addr(client)
+ assert len(ips_after_remove) == len(ips_before)
+ assert added_ip not in [ip.ip4 for ip in ips_after_remove]
+
+ netplan_cfg = client.read_from_file('/etc/netplan/50-cloud-init.yaml')
+ config = yaml.safe_load(netplan_cfg)
+ assert new_addition.interface not in config['network']['ethernets']
+
+
+@pytest.mark.openstack
+def test_no_hotplug_in_userdata(client: IntegrationInstance):
+ ips_before = _get_ip_addr(client)
+ log = client.read_from_file('/var/log/cloud-init.log')
+ assert 'Exiting hotplug handler' not in log
+
+ # Add new NIC
+ client.instance.add_network_interface()
+ _wait_till_hotplug_complete(client)
+ log = client.read_from_file('/var/log/cloud-init.log')
+ assert 'hotplug not enabled for event of type network' in log
+
+ ips_after_add = _get_ip_addr(client)
+ if len(ips_after_add) == len(ips_before) + 1:
+ # We can see the device, but it should not have been brought up
+ new_ip = [ip for ip in ips_after_add if ip not in ips_before][0]
+ assert new_ip.state == 'DOWN'
+ else:
+ assert len(ips_after_add) == len(ips_before)