summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cloudinit/net/network_state.py17
-rw-r--r--cloudinit/net/sysconfig.py6
-rwxr-xr-xcloudinit/sources/DataSourceAzure.py10
-rw-r--r--tests/unittests/test_datasource/test_azure.py101
-rw-r--r--tests/unittests/test_net.py54
5 files changed, 178 insertions, 10 deletions
diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
index ba85c69e..20b7716b 100644
--- a/cloudinit/net/network_state.py
+++ b/cloudinit/net/network_state.py
@@ -22,8 +22,9 @@ NETWORK_STATE_REQUIRED_KEYS = {
1: ['version', 'config', 'network_state'],
}
NETWORK_V2_KEY_FILTER = [
- 'addresses', 'dhcp4', 'dhcp6', 'gateway4', 'gateway6', 'interfaces',
- 'match', 'mtu', 'nameservers', 'renderer', 'set-name', 'wakeonlan'
+ 'addresses', 'dhcp4', 'dhcp4-overrides', 'dhcp6', 'dhcp6-overrides',
+ 'gateway4', 'gateway6', 'interfaces', 'match', 'mtu', 'nameservers',
+ 'renderer', 'set-name', 'wakeonlan'
]
NET_CONFIG_TO_V2 = {
@@ -747,12 +748,20 @@ class NetworkStateInterpreter(object):
def _v2_to_v1_ipcfg(self, cfg):
"""Common ipconfig extraction from v2 to v1 subnets array."""
+ def _add_dhcp_overrides(overrides, subnet):
+ if 'route-metric' in overrides:
+ subnet['metric'] = overrides['route-metric']
+
subnets = []
if cfg.get('dhcp4'):
- subnets.append({'type': 'dhcp4'})
+ subnet = {'type': 'dhcp4'}
+ _add_dhcp_overrides(cfg.get('dhcp4-overrides', {}), subnet)
+ subnets.append(subnet)
if cfg.get('dhcp6'):
+ subnet = {'type': 'dhcp6'}
self.use_ipv6 = True
- subnets.append({'type': 'dhcp6'})
+ _add_dhcp_overrides(cfg.get('dhcp6-overrides', {}), subnet)
+ subnets.append(subnet)
gateway4 = None
gateway6 = None
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index 6717d924..fe0c67ca 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -395,6 +395,9 @@ class Renderer(renderer.Renderer):
ipv6_index = -1
for i, subnet in enumerate(subnets, start=len(iface_cfg.children)):
subnet_type = subnet.get('type')
+ # metric may apply to both dhcp and static config
+ if 'metric' in subnet:
+ iface_cfg['METRIC'] = subnet['metric']
if subnet_type in ['dhcp', 'dhcp4', 'dhcp6']:
if has_default_route and iface_cfg['BOOTPROTO'] != 'none':
iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = False
@@ -426,9 +429,6 @@ class Renderer(renderer.Renderer):
else:
iface_cfg['GATEWAY'] = subnet['gateway']
- if 'metric' in subnet:
- iface_cfg['METRIC'] = subnet['metric']
-
if 'dns_search' in subnet:
iface_cfg['DOMAIN'] = ' '.join(subnet['dns_search'])
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index cdf49d36..44cca210 100755
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -1322,7 +1322,8 @@ def parse_network_config(imds_metadata):
network_metadata = imds_metadata['network']
for idx, intf in enumerate(network_metadata['interface']):
nicname = 'eth{idx}'.format(idx=idx)
- dev_config = {}
+ dev_config = {'dhcp4': False, 'dhcp6': False}
+ dhcp_override = {'route-metric': (idx + 1) * 100}
for addr4 in intf['ipv4']['ipAddress']:
privateIpv4 = addr4['privateIpAddress']
if privateIpv4:
@@ -1340,12 +1341,15 @@ def parse_network_config(imds_metadata):
# non-primary interfaces should have a higher
# route-metric (cost) so default routes prefer
# primary nic due to lower route-metric value
- dev_config['dhcp4-overrides'] = {
- 'route-metric': (idx + 1) * 100}
+ dev_config['dhcp4-overrides'] = dhcp_override
for addr6 in intf['ipv6']['ipAddress']:
privateIpv6 = addr6['privateIpAddress']
if privateIpv6:
dev_config['dhcp6'] = True
+ # non-primary interfaces should have a higher
+ # route-metric (cost) so default routes prefer
+ # primary nic due to lower route-metric value
+ dev_config['dhcp6-overrides'] = dhcp_override
break
if dev_config:
mac = ':'.join(re.findall(r'..', intf['macAddress']))
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
index 80c6f019..d92d7b2f 100644
--- a/tests/unittests/test_datasource/test_azure.py
+++ b/tests/unittests/test_datasource/test_azure.py
@@ -153,6 +153,102 @@ SECONDARY_INTERFACE = {
MOCKPATH = 'cloudinit.sources.DataSourceAzure.'
+class TestParseNetworkConfig(CiTestCase):
+
+ maxDiff = None
+
+ def test_single_ipv4_nic_configuration(self):
+ """parse_network_config emits dhcp on single nic with ipv4"""
+ expected = {'ethernets': {
+ 'eth0': {'dhcp4': True,
+ 'dhcp4-overrides': {'route-metric': 100},
+ 'dhcp6': False,
+ 'match': {'macaddress': '00:0d:3a:04:75:98'},
+ 'set-name': 'eth0'}}, 'version': 2}
+ self.assertEqual(expected, dsaz.parse_network_config(NETWORK_METADATA))
+
+ def test_increases_route_metric_for_non_primary_nics(self):
+ """parse_network_config increases route-metric for each nic"""
+ expected = {'ethernets': {
+ 'eth0': {'dhcp4': True,
+ 'dhcp4-overrides': {'route-metric': 100},
+ 'dhcp6': False,
+ 'match': {'macaddress': '00:0d:3a:04:75:98'},
+ 'set-name': 'eth0'},
+ 'eth1': {'set-name': 'eth1',
+ 'match': {'macaddress': '22:0d:3a:04:75:98'},
+ 'dhcp6': False,
+ 'dhcp4': True,
+ 'dhcp4-overrides': {'route-metric': 200}},
+ 'eth2': {'set-name': 'eth2',
+ 'match': {'macaddress': '33:0d:3a:04:75:98'},
+ 'dhcp6': False,
+ 'dhcp4': True,
+ 'dhcp4-overrides': {'route-metric': 300}}}, 'version': 2}
+ imds_data = copy.deepcopy(NETWORK_METADATA)
+ imds_data['network']['interface'].append(SECONDARY_INTERFACE)
+ third_intf = copy.deepcopy(SECONDARY_INTERFACE)
+ third_intf['macAddress'] = third_intf['macAddress'].replace('22', '33')
+ third_intf['ipv4']['subnet'][0]['address'] = '10.0.2.0'
+ third_intf['ipv4']['ipAddress'][0]['privateIpAddress'] = '10.0.2.6'
+ imds_data['network']['interface'].append(third_intf)
+ self.assertEqual(expected, dsaz.parse_network_config(imds_data))
+
+ def test_ipv4_and_ipv6_route_metrics_match_for_nics(self):
+ """parse_network_config emits matching ipv4 and ipv6 route-metrics."""
+ expected = {'ethernets': {
+ 'eth0': {'dhcp4': True,
+ 'dhcp4-overrides': {'route-metric': 100},
+ 'dhcp6': False,
+ 'match': {'macaddress': '00:0d:3a:04:75:98'},
+ 'set-name': 'eth0'},
+ 'eth1': {'set-name': 'eth1',
+ 'match': {'macaddress': '22:0d:3a:04:75:98'},
+ 'dhcp4': True,
+ 'dhcp6': False,
+ 'dhcp4-overrides': {'route-metric': 200}},
+ 'eth2': {'set-name': 'eth2',
+ 'match': {'macaddress': '33:0d:3a:04:75:98'},
+ 'dhcp4': True,
+ 'dhcp4-overrides': {'route-metric': 300},
+ 'dhcp6': True,
+ 'dhcp6-overrides': {'route-metric': 300}}}, 'version': 2}
+ imds_data = copy.deepcopy(NETWORK_METADATA)
+ imds_data['network']['interface'].append(SECONDARY_INTERFACE)
+ third_intf = copy.deepcopy(SECONDARY_INTERFACE)
+ third_intf['macAddress'] = third_intf['macAddress'].replace('22', '33')
+ third_intf['ipv4']['subnet'][0]['address'] = '10.0.2.0'
+ third_intf['ipv4']['ipAddress'][0]['privateIpAddress'] = '10.0.2.6'
+ third_intf['ipv6'] = {
+ "subnet": [{"prefix": "64", "address": "2001:dead:beef::2"}],
+ "ipAddress": [{"privateIpAddress": "2001:dead:beef::1"}]
+ }
+ imds_data['network']['interface'].append(third_intf)
+ self.assertEqual(expected, dsaz.parse_network_config(imds_data))
+
+ def test_ipv4_secondary_ips_will_be_static_addrs(self):
+ """parse_network_config emits primary ipv4 as dhcp others are static"""
+ expected = {'ethernets': {
+ 'eth0': {'addresses': ['10.0.0.5/24'],
+ 'dhcp4': True,
+ 'dhcp4-overrides': {'route-metric': 100},
+ 'dhcp6': True,
+ 'dhcp6-overrides': {'route-metric': 100},
+ 'match': {'macaddress': '00:0d:3a:04:75:98'},
+ 'set-name': 'eth0'}}, 'version': 2}
+ imds_data = copy.deepcopy(NETWORK_METADATA)
+ nic1 = imds_data['network']['interface'][0]
+ nic1['ipv4']['ipAddress'].append({'privateIpAddress': '10.0.0.5'})
+
+ # Secondary ipv6 addresses currently ignored/unconfigured
+ nic1['ipv6'] = {
+ "subnet": [{"prefix": "10", "address": "2001:dead:beef::16"}],
+ "ipAddress": [{"privateIpAddress": "2001:dead:beef::1"},
+ {"privateIpAddress": "2001:dead:beef::2"}]
+ }
+ self.assertEqual(expected, dsaz.parse_network_config(imds_data))
+
+
class TestGetMetadataFromIMDS(HttprettyTestCase):
with_logs = True
@@ -641,6 +737,7 @@ scbus-1 on xpt0 bus 0
'ethernets': {
'eth0': {'set-name': 'eth0',
'match': {'macaddress': '00:0d:3a:04:75:98'},
+ 'dhcp6': False,
'dhcp4': True,
'dhcp4-overrides': {'route-metric': 100}}},
'version': 2}
@@ -658,14 +755,17 @@ scbus-1 on xpt0 bus 0
'ethernets': {
'eth0': {'set-name': 'eth0',
'match': {'macaddress': '00:0d:3a:04:75:98'},
+ 'dhcp6': False,
'dhcp4': True,
'dhcp4-overrides': {'route-metric': 100}},
'eth1': {'set-name': 'eth1',
'match': {'macaddress': '22:0d:3a:04:75:98'},
+ 'dhcp6': False,
'dhcp4': True,
'dhcp4-overrides': {'route-metric': 200}},
'eth2': {'set-name': 'eth2',
'match': {'macaddress': '33:0d:3a:04:75:98'},
+ 'dhcp6': False,
'dhcp4': True,
'dhcp4-overrides': {'route-metric': 300}}},
'version': 2}
@@ -999,6 +1099,7 @@ scbus-1 on xpt0 bus 0
'ethernets': {
'eth0': {'dhcp4': True,
'dhcp4-overrides': {'route-metric': 100},
+ 'dhcp6': False,
'match': {'macaddress': '00:0d:3a:04:75:98'},
'set-name': 'eth0'}},
'version': 2}
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 6f83ad73..35ce55d2 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -3101,6 +3101,36 @@ USERCTL=no
self._compare_files_to_expected(
expected, self._render_and_read(network_config=v2data))
+ def test_from_v2_route_metric(self):
+ """verify route-metric gets rendered on nic when source is netplan."""
+ overrides = {'route-metric': 100}
+ v2base = {
+ 'version': 2,
+ 'ethernets': {
+ 'eno1': {'dhcp4': True,
+ 'match': {'macaddress': '07-1c-c6-75-a4-be'}}}}
+ expected = {
+ 'ifcfg-eno1': textwrap.dedent("""\
+ BOOTPROTO=dhcp
+ DEVICE=eno1
+ HWADDR=07-1c-c6-75-a4-be
+ METRIC=100
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ STARTMODE=auto
+ TYPE=Ethernet
+ USERCTL=no
+ """),
+ }
+ for dhcp_ver in ('dhcp4', 'dhcp6'):
+ v2data = copy.deepcopy(v2base)
+ if dhcp_ver == 'dhcp6':
+ expected['ifcfg-eno1'] += "IPV6INIT=yes\nDHCPV6C=yes\n"
+ v2data['ethernets']['eno1'].update(
+ {dhcp_ver: True, '{0}-overrides'.format(dhcp_ver): overrides})
+ self._compare_files_to_expected(
+ expected, self._render_and_read(network_config=v2data))
+
class TestOpenSuseSysConfigRendering(CiTestCase):
@@ -3466,6 +3496,30 @@ iface eth0 inet dhcp
self.assertEqual(
expected, dir2dict(tmp_dir)['/etc/network/interfaces'])
+ def test_v2_route_metric_to_eni(self):
+ """Network v2 route-metric overrides are preserved in eni output"""
+ tmp_dir = self.tmp_dir()
+ renderer = eni.Renderer()
+ expected_tmpl = textwrap.dedent("""\
+ auto lo
+ iface lo inet loopback
+
+ auto eth0
+ iface eth0 inet{suffix} dhcp
+ metric 100
+ """)
+ for dhcp_ver in ('dhcp4', 'dhcp6'):
+ suffix = '6' if dhcp_ver == 'dhcp6' else ''
+ dhcp_cfg = {
+ dhcp_ver: True,
+ '{ver}-overrides'.format(ver=dhcp_ver): {'route-metric': 100}}
+ v2_input = {'version': 2, 'ethernets': {'eth0': dhcp_cfg}}
+ ns = network_state.parse_net_config_data(v2_input)
+ renderer.render_network_state(ns, target=tmp_dir)
+ self.assertEqual(
+ expected_tmpl.format(suffix=suffix),
+ dir2dict(tmp_dir)['/etc/network/interfaces'])
+
class TestNetplanNetRendering(CiTestCase):