diff options
author | Scott Moser <smoser@ubuntu.com> | 2016-07-13 22:29:00 -0400 |
---|---|---|
committer | Scott Moser <smoser@ubuntu.com> | 2016-07-13 22:29:00 -0400 |
commit | 150d064b5693c7a287790900923b9413fe6d73dd (patch) | |
tree | 9795460fa9137346f8ef0a918d32e372a97d7ec5 | |
parent | e1d68c11c44ab11acf3f7b234a1db45d85ae19de (diff) | |
parent | 7d58c949c2f5d7821ec664ed25167dfea92964f7 (diff) | |
download | vyos-cloud-init-150d064b5693c7a287790900923b9413fe6d73dd.tar.gz vyos-cloud-init-150d064b5693c7a287790900923b9413fe6d73dd.zip |
merge from trunk.lp1602373
this merges in the render_hwaddress support.
newly added tests still run, so hwaddress seems correctly getting in.
-rw-r--r-- | cloudinit/distros/__init__.py | 28 | ||||
-rw-r--r-- | cloudinit/net/eni.py | 39 | ||||
-rw-r--r-- | cloudinit/sources/DataSourceConfigDrive.py | 26 | ||||
-rw-r--r-- | tests/unittests/test_net.py | 31 |
4 files changed, 106 insertions, 18 deletions
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 14b500f8..40af8802 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -32,6 +32,8 @@ import stat from cloudinit import importer from cloudinit import log as logging from cloudinit import net +from cloudinit.net import eni +from cloudinit.net import network_state from cloudinit import ssh_util from cloudinit import type_utils from cloudinit import util @@ -138,9 +140,31 @@ class Distro(object): return self._bring_up_interfaces(dev_names) return False + def _apply_network_from_network_config(self, netconfig, bring_up=True): + distro = self.__class__ + LOG.warn("apply_network_config is not currently implemented " + "for distribution '%s'. Attempting to use apply_network", + distro) + header = '\n'.join([ + "# Converted from network_config for distro %s" % distro, + "# Implmentation of _write_network_config is needed." + ]) + ns = network_state.parse_net_config_data(netconfig) + contents = eni.network_state_to_eni( + ns, header=header, render_hwaddress=True) + return self.apply_network(contents, bring_up=bring_up) + def apply_network_config(self, netconfig, bring_up=False): - # Write it out - dev_names = self._write_network_config(netconfig) + # apply network config netconfig + # This method is preferred to apply_network which only takes + # a much less complete network config format (interfaces(5)). + try: + dev_names = self._write_network_config(netconfig) + except NotImplementedError: + # backwards compat until all distros have apply_network_config + return self._apply_network_from_network_config( + netconfig, bring_up=bring_up) + # Now try to bring them up if bring_up: return self._bring_up_interfaces(dev_names) diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index 1383dd6b..cdcc951b 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -100,7 +100,7 @@ def _iface_add_attrs(iface, index): return sorted(content) -def _iface_start_entry(iface, index): +def _iface_start_entry(iface, index, render_hwaddress=False): fullname = iface['name'] if index != 0: fullname += ":%s" % index @@ -116,8 +116,13 @@ def _iface_start_entry(iface, index): subst = iface.copy() subst.update({'fullname': fullname, 'cverb': cverb}) - return ["{cverb} {fullname}".format(**subst), - "iface {fullname} {inet} {mode}".format(**subst)] + lines = [ + "{cverb} {fullname}".format(**subst), + "iface {fullname} {inet} {mode}".format(**subst)] + if render_hwaddress and iface.get('mac_address'): + lines.append(" hwaddress {mac_address}".format(**subst)) + + return lines def _parse_deb_config_data(ifaces, contents, src_dir, src_path): @@ -357,7 +362,7 @@ class Renderer(renderer.Renderer): content.append(down + route_line + or_true) return content - def _render_iface(self, iface): + def _render_iface(self, iface, render_hwaddress=False): sections = [] subnets = iface.get('subnets', {}) if subnets: @@ -377,7 +382,8 @@ class Renderer(renderer.Renderer): iface['mode'] = 'dhcp' lines = list( - _iface_start_entry(iface, index) + + _iface_start_entry( + iface, index, render_hwaddress=render_hwaddress) + _iface_add_subnet(iface, subnet) + _iface_add_attrs(iface, index) ) @@ -400,7 +406,7 @@ class Renderer(renderer.Renderer): sections.append(lines) return sections - def _render_interfaces(self, network_state): + def _render_interfaces(self, network_state, render_hwaddress=False): '''Given state, emit etc/network/interfaces content.''' # handle 'lo' specifically as we need to insert the global dns entries @@ -437,7 +443,8 @@ class Renderer(renderer.Renderer): if iface.get('name') == "lo": continue - sections.extend(self._render_iface(iface)) + sections.extend( + self._render_iface(iface, render_hwaddress=render_hwaddress)) for route in network_state.iter_routes(): sections.append(self._render_route(route)) @@ -477,3 +484,21 @@ class Renderer(renderer.Renderer): "" ]) util.write_file(fname, content) + + +def network_state_to_eni(network_state, header=None, render_hwaddress=False): + # render the provided network state, return a string of equivalent eni + eni_path = 'etc/network/interfaces' + renderer = Renderer({ + 'eni_path': eni_path, + 'eni_header': header, + 'links_path_prefix': None, + 'netrules_path': None, + }) + if not header: + header = "" + if not header.endswith("\n"): + header += "\n" + contents = renderer._render_interfaces( + network_state, render_hwaddress=render_hwaddress) + return header + contents diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py index 3130e618..91d6ff13 100644 --- a/cloudinit/sources/DataSourceConfigDrive.py +++ b/cloudinit/sources/DataSourceConfigDrive.py @@ -107,12 +107,19 @@ class DataSourceConfigDrive(openstack.SourceMixin, sources.DataSource): if self.dsmode == sources.DSMODE_DISABLED: return False - # This is legacy and sneaky. If dsmode is 'pass' then write - # 'injected files' and apply legacy ENI network format. prev_iid = get_previous_iid(self.paths) cur_iid = md['instance-id'] - if prev_iid != cur_iid and self.dsmode == sources.DSMODE_PASS: - on_first_boot(results, distro=self.distro) + if prev_iid != cur_iid: + # better would be to handle this centrally, allowing + # the datasource to do something on new instance id + # note, networking is only rendered here if dsmode is DSMODE_PASS + # which means "DISABLED, but render files and networking" + on_first_boot(results, distro=self.distro, + network=self.dsmode == sources.DSMODE_PASS) + + # This is legacy and sneaky. If dsmode is 'pass' then do not claim + # the datasource was used, even though we did run on_first_boot above. + if self.dsmode == sources.DSMODE_PASS: LOG.debug("%s: not claiming datasource, dsmode=%s", self, self.dsmode) return False @@ -184,15 +191,16 @@ def get_previous_iid(paths): return None -def on_first_boot(data, distro=None): +def on_first_boot(data, distro=None, network=True): """Performs any first-boot actions using data read from a config-drive.""" if not isinstance(data, dict): raise TypeError("Config-drive data expected to be a dict; not %s" % (type(data))) - net_conf = data.get("network_config", '') - if net_conf and distro: - LOG.warn("Updating network interfaces from config drive") - distro.apply_network(net_conf) + if network: + net_conf = data.get("network_config", '') + if net_conf and distro: + LOG.warn("Updating network interfaces from config drive") + distro.apply_network(net_conf) write_injected_files(data.get('files')) diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index ee9061a9..41b9a6d0 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -549,6 +549,37 @@ iface eth1000 inet dhcp self.assertEqual(expected.lstrip(), contents.lstrip()) +class TestEniNetworkStateToEni(TestCase): + mycfg = { + 'config': [{"type": "physical", "name": "eth0", + "mac_address": "c0:d6:9f:2c:e8:80", + "subnets": [{"type": "dhcp"}]}], + 'version': 1} + my_mac = 'c0:d6:9f:2c:e8:80' + + def test_no_header(self): + rendered = eni.network_state_to_eni( + network_state=network_state.parse_net_config_data(self.mycfg), + render_hwaddress=True) + self.assertIn(self.my_mac, rendered) + self.assertIn("hwaddress", rendered) + + def test_with_header(self): + header = "# hello world\n" + rendered = eni.network_state_to_eni( + network_state=network_state.parse_net_config_data(self.mycfg), + header=header, render_hwaddress=True) + self.assertIn(header, rendered) + self.assertIn(self.my_mac, rendered) + + def test_no_hwaddress(self): + rendered = eni.network_state_to_eni( + network_state=network_state.parse_net_config_data(self.mycfg), + render_hwaddress=False) + self.assertNotIn(self.my_mac, rendered) + self.assertNotIn("hwaddress", rendered) + + class TestCmdlineConfigParsing(TestCase): simple_cfg = { 'config': [{"type": "physical", "name": "eth0", |