cloud-init-dev team mailing list archive
-
cloud-init-dev team
-
Mailing list archive
-
Message #03057
[Merge] ~raharper/cloud-init:bug-lp-1709180-v2-params into cloud-init:master
Ryan Harper has proposed merging ~raharper/cloud-init:bug-lp-1709180-v2-params into cloud-init:master.
Requested reviews:
cloud-init commiters (cloud-init-dev)
Related bugs:
Bug #1709180 in cloud-init: "cloud-init v2 yaml doesn't preserve bond/bridge parameters when rendering"
https://bugs.launchpad.net/cloud-init/+bug/1709180
For more details, see:
https://code.launchpad.net/~raharper/cloud-init/+git/cloud-init/+merge/328800
network: fix parsing v2 config with bonds/bridge params
When consuming a version 2 network config yaml, the parameters for
bonds and bridges have different names than in v1; invert the mapping
to retain the parameters configured in v2 in v1 format.
- Fix issue with v2 macaddress values getting dropped
- Add unittests for consuming/validating v2 configurations
LP: #1709180
--
Your team cloud-init commiters is requested to review the proposed merge of ~raharper/cloud-init:bug-lp-1709180-v2-params into cloud-init:master.
diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py
index 9f35b72..2092f86 100644
--- a/cloudinit/net/netplan.py
+++ b/cloudinit/net/netplan.py
@@ -4,7 +4,7 @@ import copy
import os
from . import renderer
-from .network_state import subnet_is_ipv6
+from .network_state import subnet_is_ipv6, NET_CONFIG_TO_V2
from cloudinit import log as logging
from cloudinit import util
@@ -27,31 +27,6 @@ network:
"""
LOG = logging.getLogger(__name__)
-NET_CONFIG_TO_V2 = {
- 'bond': {'bond-ad-select': 'ad-select',
- 'bond-arp-interval': 'arp-interval',
- 'bond-arp-ip-target': 'arp-ip-target',
- 'bond-arp-validate': 'arp-validate',
- 'bond-downdelay': 'down-delay',
- 'bond-fail-over-mac': 'fail-over-mac-policy',
- 'bond-lacp-rate': 'lacp-rate',
- 'bond-miimon': 'mii-monitor-interval',
- 'bond-min-links': 'min-links',
- 'bond-mode': 'mode',
- 'bond-num-grat-arp': 'gratuitious-arp',
- 'bond-primary-reselect': 'primary-reselect-policy',
- 'bond-updelay': 'up-delay',
- 'bond-xmit-hash-policy': 'transmit-hash-policy'},
- 'bridge': {'bridge_ageing': 'ageing-time',
- 'bridge_bridgeprio': 'priority',
- 'bridge_fd': 'forward-delay',
- 'bridge_gcint': None,
- 'bridge_hello': 'hello-time',
- 'bridge_maxage': 'max-age',
- 'bridge_maxwait': None,
- 'bridge_pathcost': 'path-cost',
- 'bridge_portprio': None,
- 'bridge_waitport': None}}
def _get_params_dict_by_match(config, match):
diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
index 87a7222..71ecada 100644
--- a/cloudinit/net/network_state.py
+++ b/cloudinit/net/network_state.py
@@ -23,6 +23,32 @@ NETWORK_V2_KEY_FILTER = [
'match', 'mtu', 'nameservers', 'renderer', 'set-name', 'wakeonlan'
]
+NET_CONFIG_TO_V2 = {
+ 'bond': {'bond-ad-select': 'ad-select',
+ 'bond-arp-interval': 'arp-interval',
+ 'bond-arp-ip-target': 'arp-ip-target',
+ 'bond-arp-validate': 'arp-validate',
+ 'bond-downdelay': 'down-delay',
+ 'bond-fail-over-mac': 'fail-over-mac-policy',
+ 'bond-lacp-rate': 'lacp-rate',
+ 'bond-miimon': 'mii-monitor-interval',
+ 'bond-min-links': 'min-links',
+ 'bond-mode': 'mode',
+ 'bond-num-grat-arp': 'gratuitious-arp',
+ 'bond-primary-reselect': 'primary-reselect-policy',
+ 'bond-updelay': 'up-delay',
+ 'bond-xmit-hash-policy': 'transmit-hash-policy'},
+ 'bridge': {'bridge_ageing': 'ageing-time',
+ 'bridge_bridgeprio': 'priority',
+ 'bridge_fd': 'forward-delay',
+ 'bridge_gcint': None,
+ 'bridge_hello': 'hello-time',
+ 'bridge_maxage': 'max-age',
+ 'bridge_maxwait': None,
+ 'bridge_pathcost': 'path-cost',
+ 'bridge_portprio': None,
+ 'bridge_waitport': None}}
+
def parse_net_config_data(net_config, skip_broken=True):
"""Parses the config, returns NetworkState object
@@ -460,12 +486,15 @@ class NetworkStateInterpreter(object):
v2_command = {
bond0: {
'interfaces': ['interface0', 'interface1'],
- 'miimon': 100,
- 'mode': '802.3ad',
- 'xmit_hash_policy': 'layer3+4'},
+ 'parameters': {
+ 'mii-monitor-interval': 100,
+ 'mode': '802.3ad',
+ 'xmit_hash_policy': 'layer3+4'}},
bond1: {
'bond-slaves': ['interface2', 'interface7'],
- 'mode': 1
+ 'parameters': {
+ 'mode': 1,
+ }
}
}
@@ -554,6 +583,7 @@ class NetworkStateInterpreter(object):
if not mac_address:
LOG.debug('NetworkState Version2: missing "macaddress" info '
'in config entry: %s: %s', eth, str(cfg))
+ phy_cmd.update({'mac_address': mac_address})
for key in ['mtu', 'match', 'wakeonlan']:
if key in cfg:
@@ -616,22 +646,34 @@ class NetworkStateInterpreter(object):
def _handle_bond_bridge(self, command, cmd_type=None):
"""Common handler for bond and bridge types"""
+
+ # inverse mapping for v2 keynames to v1 keynames
+ key_map = dict((v, k) for k, v in
+ NET_CONFIG_TO_V2.get(cmd_type).items())
+
for item_name, item_cfg in command.items():
item_params = dict((key, value) for (key, value) in
item_cfg.items() if key not in
NETWORK_V2_KEY_FILTER)
+
v1_cmd = {
'type': cmd_type,
'name': item_name,
cmd_type + '_interfaces': item_cfg.get('interfaces'),
- 'params': item_params,
+ 'params': dict((key_map[k], v) for k, v in
+ item_params.get('parameters', {}).items())
}
subnets = self._v2_to_v1_ipcfg(item_cfg)
if len(subnets) > 0:
v1_cmd.update({'subnets': subnets})
LOG.debug('v2(%ss) -> v1(%s):\n%s', cmd_type, cmd_type, v1_cmd)
- self.handle_bridge(v1_cmd)
+ if cmd_type == "bridge":
+ self.handle_bridge(v1_cmd)
+ elif cmd_type == "bond":
+ self.handle_bond(v1_cmd)
+ else:
+ raise ValueError('Unknown command type: %s', cmd_type)
def _v2_to_v1_ipcfg(self, cfg):
"""Common ipconfig extraction from v2 to v1 subnets array."""
@@ -651,12 +693,6 @@ class NetworkStateInterpreter(object):
'address': address,
}
- routes = []
- for route in cfg.get('routes', []):
- routes.append(_normalize_route(
- {'address': route.get('to'), 'gateway': route.get('via')}))
- subnet['routes'] = routes
-
if ":" in address:
if 'gateway6' in cfg and gateway6 is None:
gateway6 = cfg.get('gateway6')
@@ -667,6 +703,15 @@ class NetworkStateInterpreter(object):
subnet.update({'gateway': gateway4})
subnets.append(subnet)
+
+ routes = []
+ for route in cfg.get('routes', []):
+ routes.append(_normalize_route(
+ {'address': route.get('to'), 'gateway': route.get('via')}))
+
+ if len(subnets) and len(routes):
+ subnets[0]['routes'] = routes
+
return subnets
@@ -721,7 +766,7 @@ def _normalize_net_keys(network, address_keys=()):
elif netmask:
prefix = mask_to_net_prefix(netmask)
elif 'prefix' in net:
- prefix = int(prefix)
+ prefix = int(net['prefix'])
else:
prefix = 64 if ipv6 else 24
@@ -753,13 +798,15 @@ def _normalize_route(route):
# Prune None-value keys. Specifically allow 0 (a valid metric).
normal_route = dict((k, v) for k, v in route.items()
if v not in ("", None))
- if 'destination' in normal_route:
- normal_route['network'] = normal_route['destination']
- del normal_route['destination']
+ for target_key in ['destination', 'address']:
+ if target_key in normal_route:
+ normal_route['network'] = normal_route[target_key]
+ del normal_route[target_key]
+ break
normal_route.update(
_normalize_net_keys(
- normal_route, address_keys=('network', 'destination')))
+ normal_route, address_keys=('network', target_key)))
metric = normal_route.get('metric')
if metric:
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 4653be1..d8ee7a5 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -1059,6 +1059,65 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
- type: static
address: 2001:1::1/92
"""),
+ 'yaml-v2': textwrap.dedent("""
+ version: 2
+ ethernets:
+ bond0s0:
+ set-name: bond0s0
+ match:
+ macaddress: "aa:bb:cc:dd:e8:00"
+ bond0s1:
+ set-name: bond0s1
+ match:
+ macaddress: "aa:bb:cc:dd:e8:01"
+ bonds:
+ bond0:
+ addresses:
+ - 192.168.0.2/24
+ - 192.168.1.2/24
+ - 2001:1::1/92
+ gateway4: 192.168.0.1
+ interfaces:
+ - bond0s0
+ - bond0s1
+ parameters:
+ mii-monitor-interval: 100
+ mode: active-backup
+ transmit-hash-policy: "layer3+4"
+ routes:
+ - to: 10.1.3.0/24
+ via: 192.168.0.3
+ """),
+ 'expected_netplan': textwrap.dedent("""
+ network:
+ version: 2
+ ethernets:
+ bond0s0:
+ match:
+ macaddress: aa:bb:cc:dd:e8:00
+ set-name: bond0s0
+ bond0s1:
+ match:
+ macaddress: aa:bb:cc:dd:e8:01
+ set-name: bond0s1
+ bonds:
+ bond0:
+ addresses:
+ - 192.168.0.2/24
+ - 192.168.1.2/24
+ - 2001:1::1/92
+ gateway4: 192.168.0.1
+ interfaces:
+ - bond0s0
+ - bond0s1
+ parameters:
+ mii-monitor-interval: 100
+ mode: active-backup
+ transmit-hash-policy: layer3+4
+ routes:
+ - to: 10.1.3.0/24
+ via: 192.168.0.3
+ """),
'expected_sysconfig': {
'ifcfg-bond0': textwrap.dedent("""\
BONDING_MASTER=yes
@@ -2159,6 +2218,27 @@ class TestNetplanRoundTrip(CiTestCase):
renderer.render_network_state(ns, target)
return dir2dict(target)
+ def testsimple_render_bond_netplan(self):
+ entry = NETWORK_CONFIGS['bond']
+ files = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ print(entry['expected_netplan'])
+ print('-- expected ^ | v rendered --')
+ print(files['/etc/netplan/50-cloud-init.yaml'])
+ self.assertEqual(
+ entry['expected_netplan'].splitlines(),
+ files['/etc/netplan/50-cloud-init.yaml'].splitlines())
+
+ def testsimple_render_bond_v2_input_netplan(self):
+ entry = NETWORK_CONFIGS['bond']
+ files = self._render_and_read(
+ network_config=yaml.load(entry['yaml-v2']))
+ print(entry['expected_netplan'])
+ print('-- expected ^ | v rendered --')
+ print(files['/etc/netplan/50-cloud-init.yaml'])
+ self.assertEqual(
+ entry['expected_netplan'].splitlines(),
+ files['/etc/netplan/50-cloud-init.yaml'].splitlines())
+
def testsimple_render_small_netplan(self):
entry = NETWORK_CONFIGS['small']
files = self._render_and_read(network_config=yaml.load(entry['yaml']))
Follow ups