Merge ~raharper/cloud-init:fix/netplan-ipv6-mtu into cloud-init:master

Proposed by Ryan Harper
Status: Merged
Approved by: Ryan Harper
Approved revision: 8f46b8a1c633f8a6b112a92ccb2ba34268971dfa
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~raharper/cloud-init:fix/netplan-ipv6-mtu
Merge into: cloud-init:master
Diff against target: 190 lines (+54/-10)
3 files modified
cloudinit/cmd/devel/net_convert.py (+2/-0)
cloudinit/net/netplan.py (+27/-8)
tests/unittests/test_net.py (+25/-2)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Dan Watkins Approve
Review via email: mp+374627@code.launchpad.net

Commit message

net/netplan: use ipv6-mtu key for specifying ipv6 mtu values

netplan introduced an 'info' subcommand which emits yaml describing
implemented features that indicate new or changed fields and values
in the yaml that it accepts. Previously, cloud-init emitted the key
'mtu6' for ipv6 MTU values. This is not correct and netplan will
fail to parse these values. Netplan as of 0.98 supports both the
info subcommand and the ipv6-mtu key.

This branch modifies the netplan renderer to collect the netplan
info output into a 'features' property which is a list of available
feature flags which the renderer can use to modify its output. If
the command is not available, no feature flags are set and
cloud-init will render IPv6 MTU values just as MTU for the subnet.

To post a comment you must log in.
8f46b8a... by Ryan Harper

Drop print debug lines

Revision history for this message
Dan Watkins (oddbloke) wrote :

One inline question, but the main implementation LGTM.

review: Needs Information
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:110dd9f865b87746f633210cd8e6f15f54ee32dc
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1228/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1228//rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Ryan Harper (raharper) :
Revision history for this message
Dan Watkins (oddbloke) :
review: Approve
Revision history for this message
Dan Watkins (oddbloke) wrote :

This looks like it needs rebasing to fix CI.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:8f46b8a1c633f8a6b112a92ccb2ba34268971dfa
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1230/
Executed test runs:
    FAILED: Checkout

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1230//rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:8f46b8a1c633f8a6b112a92ccb2ba34268971dfa
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1231/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1231//rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/cmd/devel/net_convert.py b/cloudinit/cmd/devel/net_convert.py
2index 1ad7e0b..6f513d9 100755
3--- a/cloudinit/cmd/devel/net_convert.py
4+++ b/cloudinit/cmd/devel/net_convert.py
5@@ -116,6 +116,8 @@ def handle_args(name, args):
6 config['postcmds'] = False
7 # trim leading slash
8 config['netplan_path'] = config['netplan_path'][1:]
9+ # enable some netplan features
10+ config['features'] = ['dhcp-use-domains', 'ipv6-mtu']
11 else:
12 r_cls = sysconfig.Renderer
13 config = distro.renderer_configs.get('sysconfig')
14diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py
15index e54a34e..1b039ab 100644
16--- a/cloudinit/net/netplan.py
17+++ b/cloudinit/net/netplan.py
18@@ -34,7 +34,7 @@ def _get_params_dict_by_match(config, match):
19 if key.startswith(match))
20
21
22-def _extract_addresses(config, entry, ifname):
23+def _extract_addresses(config, entry, ifname, features=None):
24 """This method parse a cloudinit.net.network_state dictionary (config) and
25 maps netstate keys/values into a dictionary (entry) to represent
26 netplan yaml.
27@@ -66,7 +66,7 @@ def _extract_addresses(config, entry, ifname):
28 'match': {'macaddress': '52:54:00:12:34:00'},
29 'mtu': 1501,
30 'address': ['192.168.1.2/24', '2001:4800:78ff:1b:be76:4eff:fe06:1000"],
31- 'mtu6': 1480}
32+ 'ipv6-mtu': 1480}
33
34 """
35
36@@ -79,6 +79,8 @@ def _extract_addresses(config, entry, ifname):
37 else:
38 return [obj, ]
39
40+ if features is None:
41+ features = []
42 addresses = []
43 routes = []
44 nameservers = []
45@@ -108,8 +110,8 @@ def _extract_addresses(config, entry, ifname):
46 searchdomains += _listify(subnet.get('dns_search', []))
47 if 'mtu' in subnet:
48 mtukey = 'mtu'
49- if subnet_is_ipv6(subnet):
50- mtukey += '6'
51+ if subnet_is_ipv6(subnet) and 'ipv6-mtu' in features:
52+ mtukey = 'ipv6-mtu'
53 entry.update({mtukey: subnet.get('mtu')})
54 for route in subnet.get('routes', []):
55 to_net = "%s/%s" % (route.get('network'),
56@@ -179,6 +181,7 @@ class Renderer(renderer.Renderer):
57 """Renders network information in a /etc/netplan/network.yaml format."""
58
59 NETPLAN_GENERATE = ['netplan', 'generate']
60+ NETPLAN_INFO = ['netplan', 'info']
61
62 def __init__(self, config=None):
63 if not config:
64@@ -188,6 +191,22 @@ class Renderer(renderer.Renderer):
65 self.netplan_header = config.get('netplan_header', None)
66 self._postcmds = config.get('postcmds', False)
67 self.clean_default = config.get('clean_default', True)
68+ self._features = config.get('features', None)
69+
70+ @property
71+ def features(self):
72+ if self._features is None:
73+ try:
74+ info_blob, _err = util.subp(self.NETPLAN_INFO, capture=True)
75+ info = util.load_yaml(info_blob)
76+ self._features = info['netplan.io']['features']
77+ except util.ProcessExecutionError:
78+ # if the info subcommand is not present then we don't have any
79+ # new features
80+ pass
81+ except (TypeError, KeyError) as e:
82+ LOG.debug('Failed to list features from netplan info: %s', e)
83+ return self._features
84
85 def render_network_state(self, network_state, templates=None, target=None):
86 # check network state for version
87@@ -271,7 +290,7 @@ class Renderer(renderer.Renderer):
88 else:
89 del eth['match']
90 del eth['set-name']
91- _extract_addresses(ifcfg, eth, ifname)
92+ _extract_addresses(ifcfg, eth, ifname, self.features)
93 ethernets.update({ifname: eth})
94
95 elif if_type == 'bond':
96@@ -296,7 +315,7 @@ class Renderer(renderer.Renderer):
97 slave_interfaces = ifcfg.get('bond-slaves')
98 if slave_interfaces == 'none':
99 _extract_bond_slaves_by_name(interfaces, bond, ifname)
100- _extract_addresses(ifcfg, bond, ifname)
101+ _extract_addresses(ifcfg, bond, ifname, self.features)
102 bonds.update({ifname: bond})
103
104 elif if_type == 'bridge':
105@@ -331,7 +350,7 @@ class Renderer(renderer.Renderer):
106 bridge.update({'parameters': br_config})
107 if ifcfg.get('mac_address'):
108 bridge['macaddress'] = ifcfg.get('mac_address').lower()
109- _extract_addresses(ifcfg, bridge, ifname)
110+ _extract_addresses(ifcfg, bridge, ifname, self.features)
111 bridges.update({ifname: bridge})
112
113 elif if_type == 'vlan':
114@@ -343,7 +362,7 @@ class Renderer(renderer.Renderer):
115 macaddr = ifcfg.get('mac_address', None)
116 if macaddr is not None:
117 vlan['macaddress'] = macaddr.lower()
118- _extract_addresses(ifcfg, vlan, ifname)
119+ _extract_addresses(ifcfg, vlan, ifname, self.features)
120 vlans.update({ifname: vlan})
121
122 # inject global nameserver values under each all interface which
123diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
124index d220199..21604b1 100644
125--- a/tests/unittests/test_net.py
126+++ b/tests/unittests/test_net.py
127@@ -996,8 +996,8 @@ NETWORK_CONFIGS = {
128 addresses:
129 - 192.168.14.2/24
130 - 2001:1::1/64
131+ ipv6-mtu: 1500
132 mtu: 9000
133- mtu6: 1500
134 """).rstrip(' '),
135 'yaml': textwrap.dedent("""\
136 version: 1
137@@ -3585,7 +3585,9 @@ class TestNetplanPostcommands(CiTestCase):
138
139 @mock.patch.object(netplan.Renderer, '_netplan_generate')
140 @mock.patch.object(netplan.Renderer, '_net_setup_link')
141- def test_netplan_render_calls_postcmds(self, mock_netplan_generate,
142+ @mock.patch('cloudinit.util.subp')
143+ def test_netplan_render_calls_postcmds(self, mock_subp,
144+ mock_netplan_generate,
145 mock_net_setup_link):
146 tmp_dir = self.tmp_dir()
147 ns = network_state.parse_net_config_data(self.mycfg,
148@@ -3597,6 +3599,7 @@ class TestNetplanPostcommands(CiTestCase):
149 render_target = 'netplan.yaml'
150 renderer = netplan.Renderer(
151 {'netplan_path': render_target, 'postcmds': True})
152+ mock_subp.side_effect = iter([util.ProcessExecutionError])
153 renderer.render_network_state(ns, target=render_dir)
154
155 mock_netplan_generate.assert_called_with(run=True)
156@@ -3619,7 +3622,13 @@ class TestNetplanPostcommands(CiTestCase):
157 render_target = 'netplan.yaml'
158 renderer = netplan.Renderer(
159 {'netplan_path': render_target, 'postcmds': True})
160+ mock_subp.side_effect = iter([
161+ util.ProcessExecutionError,
162+ ('', ''),
163+ ('', ''),
164+ ])
165 expected = [
166+ mock.call(['netplan', 'info'], capture=True),
167 mock.call(['netplan', 'generate'], capture=True),
168 mock.call(['udevadm', 'test-builtin', 'net_setup_link',
169 '/sys/class/net/lo'], capture=True),
170@@ -3875,6 +3884,20 @@ class TestReadInitramfsConfig(CiTestCase):
171
172
173 class TestNetplanRoundTrip(CiTestCase):
174+
175+ NETPLAN_INFO_OUT = textwrap.dedent("""
176+ netplan.io:
177+ features:
178+ - dhcp-use-domains
179+ - ipv6-mtu
180+ website: https://netplan.io/
181+ """)
182+
183+ def setUp(self):
184+ super(TestNetplanRoundTrip, self).setUp()
185+ self.add_patch('cloudinit.net.netplan.util.subp', 'm_subp')
186+ self.m_subp.return_value = (self.NETPLAN_INFO_OUT, '')
187+
188 def _render_and_read(self, network_config=None, state=None,
189 netplan_path=None, target=None):
190 if target is None:

Subscribers

People subscribed via source and target branches