summaryrefslogtreecommitdiff
path: root/cloudinit/sources
diff options
context:
space:
mode:
authorRyan Harper <ryan.harper@canonical.com>2019-09-09 21:13:01 +0000
committerServer Team CI Bot <josh.powers+server-team-bot@canonical.com>2019-09-09 21:13:01 +0000
commitfa47d527a03a00319936323f0a857fbecafceaf7 (patch)
treede0814b9cd1ec166b030591761d7dee9e67480c9 /cloudinit/sources
parent476050b46021130654a7417bbb41647a5214dbcc (diff)
downloadvyos-cloud-init-fa47d527a03a00319936323f0a857fbecafceaf7.tar.gz
vyos-cloud-init-fa47d527a03a00319936323f0a857fbecafceaf7.zip
net,Oracle: Add support for netfailover detection
Add support for detecting netfailover[1] device 3-tuple in networking layer. In the Oracle datasource ensure that if a provided network config, either fallback or provided config includes a netfailover master to remove any MAC address value as this can break under 3-netdev as the other two devices have the same MAC. 1. https://www.kernel.org/doc/html/latest/networking/net_failover.html
Diffstat (limited to 'cloudinit/sources')
-rw-r--r--cloudinit/sources/DataSourceOracle.py62
-rw-r--r--cloudinit/sources/tests/test_oracle.py147
2 files changed, 208 insertions, 1 deletions
diff --git a/cloudinit/sources/DataSourceOracle.py b/cloudinit/sources/DataSourceOracle.py
index 1cb0636c..eec87403 100644
--- a/cloudinit/sources/DataSourceOracle.py
+++ b/cloudinit/sources/DataSourceOracle.py
@@ -16,7 +16,7 @@ Notes:
"""
from cloudinit.url_helper import combine_url, readurl, UrlError
-from cloudinit.net import dhcp, get_interfaces_by_mac
+from cloudinit.net import dhcp, get_interfaces_by_mac, is_netfail_master
from cloudinit import net
from cloudinit import sources
from cloudinit import util
@@ -108,6 +108,56 @@ def _add_network_config_from_opc_imds(network_config):
'match': {'macaddress': mac_address}}
+def _ensure_netfailover_safe(network_config):
+ """
+ Search network config physical interfaces to see if any of them are
+ a netfailover master. If found, we prevent matching by MAC as the other
+ failover devices have the same MAC but need to be ignored.
+
+ Note: we rely on cloudinit.net changes which prevent netfailover devices
+ from being present in the provided network config. For more details about
+ netfailover devices, refer to cloudinit.net module.
+
+ :param network_config
+ A v1 or v2 network config dict with the primary NIC, and possibly
+ secondary nic configured. This dict will be mutated.
+
+ """
+ # ignore anything that's not an actual network-config
+ if 'version' not in network_config:
+ return
+
+ if network_config['version'] not in [1, 2]:
+ LOG.debug('Ignoring unknown network config version: %s',
+ network_config['version'])
+ return
+
+ mac_to_name = get_interfaces_by_mac()
+ if network_config['version'] == 1:
+ for cfg in [c for c in network_config['config'] if 'type' in c]:
+ if cfg['type'] == 'physical':
+ if 'mac_address' in cfg:
+ mac = cfg['mac_address']
+ cur_name = mac_to_name.get(mac)
+ if not cur_name:
+ continue
+ elif is_netfail_master(cur_name):
+ del cfg['mac_address']
+
+ elif network_config['version'] == 2:
+ for _, cfg in network_config.get('ethernets', {}).items():
+ if 'match' in cfg:
+ macaddr = cfg.get('match', {}).get('macaddress')
+ if macaddr:
+ cur_name = mac_to_name.get(macaddr)
+ if not cur_name:
+ continue
+ elif is_netfail_master(cur_name):
+ del cfg['match']['macaddress']
+ del cfg['set-name']
+ cfg['match']['name'] = cur_name
+
+
class DataSourceOracle(sources.DataSource):
dsname = 'Oracle'
@@ -208,9 +258,13 @@ class DataSourceOracle(sources.DataSource):
We nonetheless return cmdline provided config if present
and fallback to generate fallback."""
if self._network_config == sources.UNSET:
+ # this is v1
self._network_config = cmdline.read_initramfs_config()
+
if not self._network_config:
+ # this is now v2
self._network_config = self.distro.generate_fallback_config()
+
if self.ds_cfg.get('configure_secondary_nics'):
try:
# Mutate self._network_config to include secondary VNICs
@@ -219,6 +273,12 @@ class DataSourceOracle(sources.DataSource):
util.logexc(
LOG,
"Failed to fetch secondary network configuration!")
+
+ # we need to verify that the nic selected is not a netfail over
+ # device and, if it is a netfail master, then we need to avoid
+ # emitting any match by mac
+ _ensure_netfailover_safe(self._network_config)
+
return self._network_config
diff --git a/cloudinit/sources/tests/test_oracle.py b/cloudinit/sources/tests/test_oracle.py
index 2a70bbc9..85b6db97 100644
--- a/cloudinit/sources/tests/test_oracle.py
+++ b/cloudinit/sources/tests/test_oracle.py
@@ -8,6 +8,7 @@ from cloudinit.tests import helpers as test_helpers
from textwrap import dedent
import argparse
+import copy
import httpretty
import json
import mock
@@ -586,4 +587,150 @@ class TestNetworkConfigFromOpcImds(test_helpers.CiTestCase):
self.assertEqual('10.0.0.231', secondary_nic_cfg['addresses'][0])
+class TestNetworkConfigFiltersNetFailover(test_helpers.CiTestCase):
+
+ with_logs = True
+
+ def setUp(self):
+ super(TestNetworkConfigFiltersNetFailover, self).setUp()
+ self.add_patch(DS_PATH + '.get_interfaces_by_mac',
+ 'm_get_interfaces_by_mac')
+ self.add_patch(DS_PATH + '.is_netfail_master', 'm_netfail_master')
+
+ def test_ignore_bogus_network_config(self):
+ netcfg = {'something': 'here'}
+ passed_netcfg = copy.copy(netcfg)
+ oracle._ensure_netfailover_safe(passed_netcfg)
+ self.assertEqual(netcfg, passed_netcfg)
+
+ def test_ignore_network_config_unknown_versions(self):
+ netcfg = {'something': 'here', 'version': 3}
+ passed_netcfg = copy.copy(netcfg)
+ oracle._ensure_netfailover_safe(passed_netcfg)
+ self.assertEqual(netcfg, passed_netcfg)
+
+ def test_checks_v1_type_physical_interfaces(self):
+ mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3'
+ self.m_get_interfaces_by_mac.return_value = {
+ mac_addr: nic_name,
+ }
+ netcfg = {'version': 1, 'config': [
+ {'type': 'physical', 'name': nic_name, 'mac_address': mac_addr,
+ 'subnets': [{'type': 'dhcp4'}]}]}
+ passed_netcfg = copy.copy(netcfg)
+ self.m_netfail_master.return_value = False
+ oracle._ensure_netfailover_safe(passed_netcfg)
+ self.assertEqual(netcfg, passed_netcfg)
+ self.assertEqual([mock.call(nic_name)],
+ self.m_netfail_master.call_args_list)
+
+ def test_checks_v1_skips_non_phys_interfaces(self):
+ mac_addr, nic_name = '00:00:17:02:2b:b1', 'bond0'
+ self.m_get_interfaces_by_mac.return_value = {
+ mac_addr: nic_name,
+ }
+ netcfg = {'version': 1, 'config': [
+ {'type': 'bond', 'name': nic_name, 'mac_address': mac_addr,
+ 'subnets': [{'type': 'dhcp4'}]}]}
+ passed_netcfg = copy.copy(netcfg)
+ oracle._ensure_netfailover_safe(passed_netcfg)
+ self.assertEqual(netcfg, passed_netcfg)
+ self.assertEqual(0, self.m_netfail_master.call_count)
+
+ def test_removes_master_mac_property_v1(self):
+ nic_master, mac_master = 'ens3', self.random_string()
+ nic_other, mac_other = 'ens7', self.random_string()
+ nic_extra, mac_extra = 'enp0s1f2', self.random_string()
+ self.m_get_interfaces_by_mac.return_value = {
+ mac_master: nic_master,
+ mac_other: nic_other,
+ mac_extra: nic_extra,
+ }
+ netcfg = {'version': 1, 'config': [
+ {'type': 'physical', 'name': nic_master,
+ 'mac_address': mac_master},
+ {'type': 'physical', 'name': nic_other, 'mac_address': mac_other},
+ {'type': 'physical', 'name': nic_extra, 'mac_address': mac_extra},
+ ]}
+
+ def _is_netfail_master(iface):
+ if iface == 'ens3':
+ return True
+ return False
+ self.m_netfail_master.side_effect = _is_netfail_master
+ expected_cfg = {'version': 1, 'config': [
+ {'type': 'physical', 'name': nic_master},
+ {'type': 'physical', 'name': nic_other, 'mac_address': mac_other},
+ {'type': 'physical', 'name': nic_extra, 'mac_address': mac_extra},
+ ]}
+ oracle._ensure_netfailover_safe(netcfg)
+ self.assertEqual(expected_cfg, netcfg)
+
+ def test_checks_v2_type_ethernet_interfaces(self):
+ mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3'
+ self.m_get_interfaces_by_mac.return_value = {
+ mac_addr: nic_name,
+ }
+ netcfg = {'version': 2, 'ethernets': {
+ nic_name: {'dhcp4': True, 'critical': True, 'set-name': nic_name,
+ 'match': {'macaddress': mac_addr}}}}
+ passed_netcfg = copy.copy(netcfg)
+ self.m_netfail_master.return_value = False
+ oracle._ensure_netfailover_safe(passed_netcfg)
+ self.assertEqual(netcfg, passed_netcfg)
+ self.assertEqual([mock.call(nic_name)],
+ self.m_netfail_master.call_args_list)
+
+ def test_skips_v2_non_ethernet_interfaces(self):
+ mac_addr, nic_name = '00:00:17:02:2b:b1', 'wlps0'
+ self.m_get_interfaces_by_mac.return_value = {
+ mac_addr: nic_name,
+ }
+ netcfg = {'version': 2, 'wifis': {
+ nic_name: {'dhcp4': True, 'critical': True, 'set-name': nic_name,
+ 'match': {'macaddress': mac_addr}}}}
+ passed_netcfg = copy.copy(netcfg)
+ oracle._ensure_netfailover_safe(passed_netcfg)
+ self.assertEqual(netcfg, passed_netcfg)
+ self.assertEqual(0, self.m_netfail_master.call_count)
+
+ def test_removes_master_mac_property_v2(self):
+ nic_master, mac_master = 'ens3', self.random_string()
+ nic_other, mac_other = 'ens7', self.random_string()
+ nic_extra, mac_extra = 'enp0s1f2', self.random_string()
+ self.m_get_interfaces_by_mac.return_value = {
+ mac_master: nic_master,
+ mac_other: nic_other,
+ mac_extra: nic_extra,
+ }
+ netcfg = {'version': 2, 'ethernets': {
+ nic_extra: {'dhcp4': True, 'set-name': nic_extra,
+ 'match': {'macaddress': mac_extra}},
+ nic_other: {'dhcp4': True, 'set-name': nic_other,
+ 'match': {'macaddress': mac_other}},
+ nic_master: {'dhcp4': True, 'set-name': nic_master,
+ 'match': {'macaddress': mac_master}},
+ }}
+
+ def _is_netfail_master(iface):
+ if iface == 'ens3':
+ return True
+ return False
+ self.m_netfail_master.side_effect = _is_netfail_master
+
+ expected_cfg = {'version': 2, 'ethernets': {
+ nic_master: {'dhcp4': True, 'match': {'name': nic_master}},
+ nic_extra: {'dhcp4': True, 'set-name': nic_extra,
+ 'match': {'macaddress': mac_extra}},
+ nic_other: {'dhcp4': True, 'set-name': nic_other,
+ 'match': {'macaddress': mac_other}},
+ }}
+ oracle._ensure_netfailover_safe(netcfg)
+ import pprint
+ pprint.pprint(netcfg)
+ print('---- ^^ modified ^^ ---- vv original vv ----')
+ pprint.pprint(expected_cfg)
+ self.assertEqual(expected_cfg, netcfg)
+
+
# vi: ts=4 expandtab