From 281551d4125b40836686793b6a0f8d2c34c3357f Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 10 May 2016 14:10:54 -0700 Subject: Move net/renderers -> net --- cloudinit/net/eni.py | 399 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 399 insertions(+) create mode 100644 cloudinit/net/eni.py (limited to 'cloudinit/net/eni.py') diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py new file mode 100644 index 00000000..b427012e --- /dev/null +++ b/cloudinit/net/eni.py @@ -0,0 +1,399 @@ +# vi: ts=4 expandtab +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import glob +import os +import re + +from cloudinit.net import LINKS_FNAME_PREFIX +from cloudinit.net import ParserError +from cloudinit.net.udev import generate_udev_rule +from cloudinit import util + + +NET_CONFIG_COMMANDS = [ + "pre-up", "up", "post-up", "down", "pre-down", "post-down", +] + +NET_CONFIG_BRIDGE_OPTIONS = [ + "bridge_ageing", "bridge_bridgeprio", "bridge_fd", "bridge_gcinit", + "bridge_hello", "bridge_maxage", "bridge_maxwait", "bridge_stp", +] + +NET_CONFIG_OPTIONS = [ + "address", "netmask", "broadcast", "network", "metric", "gateway", + "pointtopoint", "media", "mtu", "hostname", "leasehours", "leasetime", + "vendor", "client", "bootfile", "server", "hwaddr", "provider", "frame", + "netnum", "endpoint", "local", "ttl", +] + + +# TODO: switch valid_map based on mode inet/inet6 +def _iface_add_subnet(iface, subnet): + content = "" + valid_map = [ + 'address', + 'netmask', + 'broadcast', + 'metric', + 'gateway', + 'pointopoint', + 'mtu', + 'scope', + 'dns_search', + 'dns_nameservers', + ] + for key, value in subnet.items(): + if value and key in valid_map: + if type(value) == list: + value = " ".join(value) + if '_' in key: + key = key.replace('_', '-') + content += " {} {}\n".format(key, value) + + return content + + +# TODO: switch to valid_map for attrs +def _iface_add_attrs(iface): + content = "" + ignore_map = [ + 'control', + 'index', + 'inet', + 'mode', + 'name', + 'subnets', + 'type', + ] + if iface['type'] not in ['bond', 'bridge', 'vlan']: + ignore_map.append('mac_address') + + for key, value in iface.items(): + if value and key not in ignore_map: + if type(value) == list: + value = " ".join(value) + content += " {} {}\n".format(key, value) + + return content + + +def _iface_start_entry(iface, index): + fullname = iface['name'] + if index != 0: + fullname += ":%s" % index + + control = iface['control'] + if control == "auto": + cverb = "auto" + elif control in ("hotplug",): + cverb = "allow-" + control + else: + cverb = "# control-" + control + + subst = iface.copy() + subst.update({'fullname': fullname, 'cverb': cverb}) + + return ("{cverb} {fullname}\n" + "iface {fullname} {inet} {mode}\n").format(**subst) + + +def _parse_deb_config_data(ifaces, contents, src_dir, src_path): + """Parses the file contents, placing result into ifaces. + + '_source_path' is added to every dictionary entry to define which file + the configration information came from. + + :param ifaces: interface dictionary + :param contents: contents of interfaces file + :param src_dir: directory interfaces file was located + :param src_path: file path the `contents` was read + """ + currif = None + for line in contents.splitlines(): + line = line.strip() + if line.startswith('#'): + continue + split = line.split(' ') + option = split[0] + if option == "source-directory": + parsed_src_dir = split[1] + if not parsed_src_dir.startswith("/"): + parsed_src_dir = os.path.join(src_dir, parsed_src_dir) + for expanded_path in glob.glob(parsed_src_dir): + dir_contents = os.listdir(expanded_path) + dir_contents = [ + os.path.join(expanded_path, path) + for path in dir_contents + if (os.path.isfile(os.path.join(expanded_path, path)) and + re.match("^[a-zA-Z0-9_-]+$", path) is not None) + ] + for entry in dir_contents: + with open(entry, "r") as fp: + src_data = fp.read().strip() + abs_entry = os.path.abspath(entry) + _parse_deb_config_data( + ifaces, src_data, + os.path.dirname(abs_entry), abs_entry) + elif option == "source": + new_src_path = split[1] + if not new_src_path.startswith("/"): + new_src_path = os.path.join(src_dir, new_src_path) + for expanded_path in glob.glob(new_src_path): + with open(expanded_path, "r") as fp: + src_data = fp.read().strip() + abs_path = os.path.abspath(expanded_path) + _parse_deb_config_data( + ifaces, src_data, + os.path.dirname(abs_path), abs_path) + elif option == "auto": + for iface in split[1:]: + if iface not in ifaces: + ifaces[iface] = { + # Include the source path this interface was found in. + "_source_path": src_path + } + ifaces[iface]['auto'] = True + elif option == "iface": + iface, family, method = split[1:4] + if iface not in ifaces: + ifaces[iface] = { + # Include the source path this interface was found in. + "_source_path": src_path + } + elif 'family' in ifaces[iface]: + raise ParserError( + "Interface %s can only be defined once. " + "Re-defined in '%s'." % (iface, src_path)) + ifaces[iface]['family'] = family + ifaces[iface]['method'] = method + currif = iface + elif option == "hwaddress": + ifaces[currif]['hwaddress'] = split[1] + elif option in NET_CONFIG_OPTIONS: + ifaces[currif][option] = split[1] + elif option in NET_CONFIG_COMMANDS: + if option not in ifaces[currif]: + ifaces[currif][option] = [] + ifaces[currif][option].append(' '.join(split[1:])) + elif option.startswith('dns-'): + if 'dns' not in ifaces[currif]: + ifaces[currif]['dns'] = {} + if option == 'dns-search': + ifaces[currif]['dns']['search'] = [] + for domain in split[1:]: + ifaces[currif]['dns']['search'].append(domain) + elif option == 'dns-nameservers': + ifaces[currif]['dns']['nameservers'] = [] + for server in split[1:]: + ifaces[currif]['dns']['nameservers'].append(server) + elif option.startswith('bridge_'): + if 'bridge' not in ifaces[currif]: + ifaces[currif]['bridge'] = {} + if option in NET_CONFIG_BRIDGE_OPTIONS: + bridge_option = option.replace('bridge_', '', 1) + ifaces[currif]['bridge'][bridge_option] = split[1] + elif option == "bridge_ports": + ifaces[currif]['bridge']['ports'] = [] + for iface in split[1:]: + ifaces[currif]['bridge']['ports'].append(iface) + elif option == "bridge_hw" and split[1].lower() == "mac": + ifaces[currif]['bridge']['mac'] = split[2] + elif option == "bridge_pathcost": + if 'pathcost' not in ifaces[currif]['bridge']: + ifaces[currif]['bridge']['pathcost'] = {} + ifaces[currif]['bridge']['pathcost'][split[1]] = split[2] + elif option == "bridge_portprio": + if 'portprio' not in ifaces[currif]['bridge']: + ifaces[currif]['bridge']['portprio'] = {} + ifaces[currif]['bridge']['portprio'][split[1]] = split[2] + elif option.startswith('bond-'): + if 'bond' not in ifaces[currif]: + ifaces[currif]['bond'] = {} + bond_option = option.replace('bond-', '', 1) + ifaces[currif]['bond'][bond_option] = split[1] + for iface in ifaces.keys(): + if 'auto' not in ifaces[iface]: + ifaces[iface]['auto'] = False + + +def _parse_deb_config(path): + """Parses a debian network configuration file.""" + ifaces = {} + with open(path, "r") as fp: + contents = fp.read().strip() + abs_path = os.path.abspath(path) + _parse_deb_config_data( + ifaces, contents, + os.path.dirname(abs_path), abs_path) + return ifaces + + +class Renderer(object): + """Renders network information in a /etc/network/interfaces format.""" + + def render_persistent_net(self, network_state): + """Given state, emit udev rules to map mac to ifname.""" + content = "" + interfaces = network_state.get('interfaces') + for iface in interfaces.values(): + # for physical interfaces write out a persist net udev rule + if iface['type'] == 'physical' and \ + 'name' in iface and iface.get('mac_address'): + content += generate_udev_rule(iface['name'], + iface['mac_address']) + + return content + + def render_route(self, route, indent=""): + """ When rendering routes for an iface, in some cases applying a route + may result in the route command returning non-zero which produces + some confusing output for users manually using ifup/ifdown[1]. To + that end, we will optionally include an '|| true' postfix to each + route line allowing users to work with ifup/ifdown without using + --force option. + + We may at somepoint not want to emit this additional postfix, and + add a 'strict' flag to this function. When called with strict=True, + then we will not append the postfix. + + 1. http://askubuntu.com/questions/168033/ + how-to-set-static-routes-in-ubuntu-server + """ + content = "" + up = indent + "post-up route add" + down = indent + "pre-down route del" + eol = " || true\n" + mapping = { + 'network': '-net', + 'netmask': 'netmask', + 'gateway': 'gw', + 'metric': 'metric', + } + if route['network'] == '0.0.0.0' and route['netmask'] == '0.0.0.0': + default_gw = " default gw %s" % route['gateway'] + content += up + default_gw + eol + content += down + default_gw + eol + elif route['network'] == '::' and route['netmask'] == 0: + # ipv6! + default_gw = " -A inet6 default gw %s" % route['gateway'] + content += up + default_gw + eol + content += down + default_gw + eol + else: + route_line = "" + for k in ['network', 'netmask', 'gateway', 'metric']: + if k in route: + route_line += " %s %s" % (mapping[k], route[k]) + content += up + route_line + eol + content += down + route_line + eol + + return content + + def render_interfaces(self, network_state): + ''' Given state, emit etc/network/interfaces content ''' + + content = "" + interfaces = network_state.get('interfaces') + ''' Apply a sort order to ensure that we write out + the physical interfaces first; this is critical for + bonding + ''' + order = { + 'physical': 0, + 'bond': 1, + 'bridge': 2, + 'vlan': 3, + } + content += "auto lo\niface lo inet loopback\n" + for dnskey, value in network_state.get('dns', {}).items(): + if len(value): + content += " dns-{} {}\n".format(dnskey, " ".join(value)) + + for iface in sorted(interfaces.values(), + key=lambda k: (order[k['type']], k['name'])): + + if content[-2:] != "\n\n": + content += "\n" + subnets = iface.get('subnets', {}) + if subnets: + for index, subnet in zip(range(0, len(subnets)), subnets): + if content[-2:] != "\n\n": + content += "\n" + iface['index'] = index + iface['mode'] = subnet['type'] + iface['control'] = subnet.get('control', 'auto') + if iface['mode'].endswith('6'): + iface['inet'] += '6' + elif iface['mode'] == 'static' \ + and ":" in subnet['address']: + iface['inet'] += '6' + if iface['mode'].startswith('dhcp'): + iface['mode'] = 'dhcp' + + content += _iface_start_entry(iface, index) + content += _iface_add_subnet(iface, subnet) + content += _iface_add_attrs(iface) + else: + # ifenslave docs say to auto the slave devices + if 'bond-master' in iface: + content += "auto {name}\n".format(**iface) + content += "iface {name} {inet} {mode}\n".format(**iface) + content += _iface_add_attrs(iface) + + for route in network_state.get('routes'): + content += self.render_route(route) + + # global replacements until v2 format + content = content.replace('mac_address', 'hwaddress') + return content + + def render_network_state(self, + target, network_state, eni="etc/network/interfaces", + links_prefix=LINKS_FNAME_PREFIX, + netrules='etc/udev/rules.d/70-persistent-net.rules'): + + fpeni = os.path.sep.join((target, eni,)) + util.ensure_dir(os.path.dirname(fpeni)) + with open(fpeni, 'w+') as f: + f.write(self.render_interfaces(network_state)) + + if netrules: + netrules = os.path.sep.join((target, netrules,)) + util.ensure_dir(os.path.dirname(netrules)) + with open(netrules, 'w+') as f: + f.write(self.render_persistent_net(network_state)) + + if links_prefix: + self.render_systemd_links(target, network_state, links_prefix) + + def render_systemd_links(self, target, network_state, + links_prefix=LINKS_FNAME_PREFIX): + fp_prefix = os.path.sep.join((target, links_prefix)) + for f in glob.glob(fp_prefix + "*"): + os.unlink(f) + + interfaces = network_state.get('interfaces') + for iface in interfaces.values(): + if (iface['type'] == 'physical' and 'name' in iface and + iface.get('mac_address')): + fname = fp_prefix + iface['name'] + ".link" + with open(fname, "w") as fp: + fp.write("\n".join([ + "[Match]", + "MACAddress=" + iface['mac_address'], + "", + "[Link]", + "Name=" + iface['name'], + "" + ])) -- cgit v1.2.3 From abb3c00fadefea8056c300faf141260e124a5064 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 17 May 2016 17:29:59 -0700 Subject: Don't expose anything but 'render_network_state' This should be the visible api of a network renderer as anything more granular varies between the different render types and will not apply to those renderers. --- cloudinit/net/eni.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'cloudinit/net/eni.py') diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index b427012e..b1bdac24 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -243,7 +243,7 @@ def _parse_deb_config(path): class Renderer(object): """Renders network information in a /etc/network/interfaces format.""" - def render_persistent_net(self, network_state): + def _render_persistent_net(self, network_state): """Given state, emit udev rules to map mac to ifname.""" content = "" interfaces = network_state.get('interfaces') @@ -256,7 +256,7 @@ class Renderer(object): return content - def render_route(self, route, indent=""): + def _render_route(self, route, indent=""): """ When rendering routes for an iface, in some cases applying a route may result in the route command returning non-zero which produces some confusing output for users manually using ifup/ifdown[1]. To @@ -300,7 +300,7 @@ class Renderer(object): return content - def render_interfaces(self, network_state): + def _render_interfaces(self, network_state): ''' Given state, emit etc/network/interfaces content ''' content = "" @@ -352,7 +352,7 @@ class Renderer(object): content += _iface_add_attrs(iface) for route in network_state.get('routes'): - content += self.render_route(route) + content += self._render_route(route) # global replacements until v2 format content = content.replace('mac_address', 'hwaddress') @@ -366,19 +366,19 @@ class Renderer(object): fpeni = os.path.sep.join((target, eni,)) util.ensure_dir(os.path.dirname(fpeni)) with open(fpeni, 'w+') as f: - f.write(self.render_interfaces(network_state)) + f.write(self._render_interfaces(network_state)) if netrules: netrules = os.path.sep.join((target, netrules,)) util.ensure_dir(os.path.dirname(netrules)) with open(netrules, 'w+') as f: - f.write(self.render_persistent_net(network_state)) + f.write(self._render_persistent_net(network_state)) if links_prefix: - self.render_systemd_links(target, network_state, links_prefix) + self._render_systemd_links(target, network_state, links_prefix) - def render_systemd_links(self, target, network_state, - links_prefix=LINKS_FNAME_PREFIX): + def _render_systemd_links(self, target, network_state, + links_prefix=LINKS_FNAME_PREFIX): fp_prefix = os.path.sep.join((target, links_prefix)) for f in glob.glob(fp_prefix + "*"): os.unlink(f) -- cgit v1.2.3 From 880d9fc2f9c62abf19b1506595aa81e5417dea45 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Thu, 19 May 2016 14:13:07 -0700 Subject: Adjust net module to be more isolated This allows it to be used outside of cloudinit more easily in the future. --- cloudinit/net/__init__.py | 108 ++++++++++++++++++--------------- cloudinit/net/cmdline.py | 15 ++++- cloudinit/net/eni.py | 35 +++++------ cloudinit/net/network_state.py | 38 +++++++++--- cloudinit/sources/helpers/openstack.py | 4 ++ cloudinit/util.py | 9 --- tests/unittests/test_net.py | 7 ++- 7 files changed, 129 insertions(+), 87 deletions(-) (limited to 'cloudinit/net/eni.py') diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index 0202cbd8..07e7307e 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -17,44 +17,81 @@ # along with Curtin. If not, see . import errno +import logging import os - -from cloudinit import log as logging -from cloudinit.net import network_state -from cloudinit import util - +import six +import yaml LOG = logging.getLogger(__name__) SYS_CLASS_NET = "/sys/class/net/" LINKS_FNAME_PREFIX = "etc/systemd/network/50-cloud-init-" DEFAULT_PRIMARY_INTERFACE = 'eth0' +# NOTE(harlowja): some of these are similar to what is in cloudinit main +# source or utils tree/module but the reason that is done is so that this +# whole module can be easily extracted and placed into other +# code-bases (curtin for example). -def sys_dev_path(devname, path=""): - return SYS_CLASS_NET + devname + "/" + path +def write_file(path, content): + """Simple writing a file helper.""" + base_path = os.path.dirname(path) + if not os.path.isdir(base_path): + os.makedirs(base_path) + with open(path, "wb+") as fh: + if isinstance(content, six.text_type): + content = content.encode("utf8") + fh.write(content) -def read_sys_net(devname, path, translate=None, enoent=None, keyerror=None): +def read_file(path, decode='utf8', enoent=None): try: - contents = "" - with open(sys_dev_path(devname, path), "r") as fp: - contents = fp.read().strip() - if translate is None: - return contents - - try: - return translate.get(contents) - except KeyError: - LOG.debug("found unexpected value '%s' in '%s/%s'", contents, - devname, path) - if keyerror is not None: - return keyerror - raise + with open(path, "rb") as fh: + contents = fh.load() except OSError as e: if e.errno == errno.ENOENT and enoent is not None: return enoent raise + if decode: + return contents.decode(decode) + return contents + + +def dump_yaml(obj): + return yaml.safe_dump(obj, + line_break="\n", + indent=4, + explicit_start=True, + explicit_end=True, + default_flow_style=False) + + +def read_yaml_file(path): + val = yaml.safe_load(read_file(path)) + if not isinstance(val, dict): + gotten_type_name = type(val).__name__ + raise TypeError("Expected dict to be loaded from %s, got" + " '%s' instead" % (path, gotten_type_name)) + return val + + +def sys_dev_path(devname, path=""): + return SYS_CLASS_NET + devname + "/" + path + + +def read_sys_net(devname, path, translate=None, enoent=None, keyerror=None): + contents = read_file(sys_dev_path(devname, path), enoent=enoent) + contents = contents.strip() + if translate is None: + return contents + try: + return translate.get(contents) + except KeyError: + LOG.debug("found unexpected value '%s' in '%s/%s'", contents, + devname, path) + if keyerror is not None: + return keyerror + raise def is_up(devname): @@ -107,31 +144,6 @@ class ParserError(Exception): """Raised when parser has issue parsing the interfaces file.""" -def parse_net_config_data(net_config, skip_broken=True): - """Parses the config, returns NetworkState object - - :param net_config: curtin network config dict - """ - state = None - if 'version' in net_config and 'config' in net_config: - ns = network_state.NetworkState(version=net_config.get('version'), - config=net_config.get('config')) - ns.parse_config(skip_broken=skip_broken) - state = ns.network_state - return state - - -def parse_net_config(path, skip_broken=True): - """Parses a curtin network configuration file and - return network state""" - ns = None - net_config = util.read_conf(path) - if 'network' in net_config: - ns = parse_net_config_data(net_config.get('network'), - skip_broken=skip_broken) - return ns - - def is_disabled_cfg(cfg): if not cfg or not isinstance(cfg, dict): return False @@ -146,7 +158,7 @@ def sys_netdev_info(name, field): fname = os.path.join(SYS_CLASS_NET, name, field) if not os.path.exists(fname): raise OSError("%s: could not find sysfs entry: %s" % (name, fname)) - data = util.load_file(fname) + data = read_file(fname) if data[-1] == '\n': data = data[:-1] return data diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py index f5712533..b85d4b0a 100644 --- a/cloudinit/net/cmdline.py +++ b/cloudinit/net/cmdline.py @@ -20,12 +20,25 @@ import base64 import glob import gzip import io +import shlex +import sys + +import six from cloudinit.net import get_devicelist from cloudinit.net import sys_netdev_info from cloudinit import util +PY26 = sys.version_info[0:2] == (2, 6) + + +def _shlex_split(blob): + if PY26 and isinstance(blob, six.text_type): + # Older versions don't support unicode input + blob = blob.encode("utf8") + return shlex.split(blob) + def _load_shell_content(content, add_empty=False, empty_val=None): """Given shell like syntax (key=value\nkey2=value2\n) in content @@ -33,7 +46,7 @@ def _load_shell_content(content, add_empty=False, empty_val=None): then add entries in to the returned dictionary for 'VAR=' variables. Set their value to empty_val.""" data = {} - for line in util.shlex_split(content): + for line in _shlex_split(content): try: key, value = line.split("=", 1) except ValueError: diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index b1bdac24..adb31c22 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -16,10 +16,11 @@ import glob import os import re +from cloudinit import net + from cloudinit.net import LINKS_FNAME_PREFIX from cloudinit.net import ParserError from cloudinit.net.udev import generate_udev_rule -from cloudinit import util NET_CONFIG_COMMANDS = [ @@ -363,16 +364,13 @@ class Renderer(object): links_prefix=LINKS_FNAME_PREFIX, netrules='etc/udev/rules.d/70-persistent-net.rules'): - fpeni = os.path.sep.join((target, eni,)) - util.ensure_dir(os.path.dirname(fpeni)) - with open(fpeni, 'w+') as f: - f.write(self._render_interfaces(network_state)) + fpeni = os.path.join(target, eni) + net.write_file(fpeni, self._render_interfaces(network_state)) if netrules: - netrules = os.path.sep.join((target, netrules,)) - util.ensure_dir(os.path.dirname(netrules)) - with open(netrules, 'w+') as f: - f.write(self._render_persistent_net(network_state)) + netrules = os.path.join(target, netrules) + net.write_file(netrules, + self._render_persistent_net(network_state)) if links_prefix: self._render_systemd_links(target, network_state, links_prefix) @@ -382,18 +380,17 @@ class Renderer(object): fp_prefix = os.path.sep.join((target, links_prefix)) for f in glob.glob(fp_prefix + "*"): os.unlink(f) - interfaces = network_state.get('interfaces') for iface in interfaces.values(): if (iface['type'] == 'physical' and 'name' in iface and iface.get('mac_address')): fname = fp_prefix + iface['name'] + ".link" - with open(fname, "w") as fp: - fp.write("\n".join([ - "[Match]", - "MACAddress=" + iface['mac_address'], - "", - "[Link]", - "Name=" + iface['name'], - "" - ])) + content = "\n".join([ + "[Match]", + "MACAddress=" + iface['mac_address'], + "", + "[Link]", + "Name=" + iface['name'], + "" + ]) + net.write_file(fname, content) diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py index 2feffa71..c5aeadb5 100644 --- a/cloudinit/net/network_state.py +++ b/cloudinit/net/network_state.py @@ -16,12 +16,11 @@ # along with Curtin. If not, see . import copy +import logging import six -from cloudinit import log as logging -from cloudinit import util -from cloudinit.util import yaml_dumps as dump_config +from cloudinit import net LOG = logging.getLogger(__name__) @@ -31,9 +30,34 @@ NETWORK_STATE_REQUIRED_KEYS = { } +def parse_net_config_data(net_config, skip_broken=True): + """Parses the config, returns NetworkState object + + :param net_config: curtin network config dict + """ + state = None + if 'version' in net_config and 'config' in net_config: + ns = NetworkState(version=net_config.get('version'), + config=net_config.get('config')) + ns.parse_config(skip_broken=skip_broken) + state = ns.network_state + return state + + +def parse_net_config(path, skip_broken=True): + """Parses a curtin network configuration file and + return network state""" + ns = None + net_config = net.read_yaml_file(path) + if 'network' in net_config: + ns = parse_net_config_data(net_config.get('network'), + skip_broken=skip_broken) + return ns + + def from_state_file(state_file): network_state = None - state = util.read_conf(state_file) + state = net.read_yaml_file(state_file) network_state = NetworkState() network_state.load(state) return network_state @@ -111,7 +135,7 @@ class NetworkState(object): 'config': self.config, 'network_state': self.network_state, } - return dump_config(state) + return net.dump_yaml(state) def load(self, state): if 'version' not in state: @@ -121,7 +145,7 @@ class NetworkState(object): required_keys = NETWORK_STATE_REQUIRED_KEYS[state['version']] missing_keys = diff_keys(required_keys, state) if missing_keys: - msg = 'Invalid state, missing keys: %s'.format(missing_keys) + msg = 'Invalid state, missing keys: %s' % (missing_keys) LOG.error(msg) raise ValueError(msg) @@ -130,7 +154,7 @@ class NetworkState(object): setattr(self, key, state[key]) def dump_network_state(self): - return dump_config(self.network_state) + return net.dump_yaml(self.network_state) def parse_config(self, skip_broken=True): # rebuild network state diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py index b2acc648..f85fd864 100644 --- a/cloudinit/sources/helpers/openstack.py +++ b/cloudinit/sources/helpers/openstack.py @@ -551,6 +551,10 @@ def convert_net_json(network_json): 'type': 'static', 'address': network.get('ip_address'), }) + if network['type'] == 'ipv6': + subnet['ipv6'] = True + else: + subnet['ipv4'] = True subnets.append(subnet) cfg.update({'subnets': subnets}) if link['type'] in ['ethernet', 'vif', 'ovs', 'phy']: diff --git a/cloudinit/util.py b/cloudinit/util.py index 7562b97a..2bec476e 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -37,7 +37,6 @@ import pwd import random import re import shutil -import shlex import socket import stat import string @@ -82,7 +81,6 @@ CONTAINER_TESTS = (['systemd-detect-virt', '--quiet', '--container'], ['lxc-is-container']) PROC_CMDLINE = None -PY26 = sys.version_info[0:2] == (2, 6) def decode_binary(blob, encoding='utf-8'): @@ -1202,13 +1200,6 @@ def load_file(fname, read_cb=None, quiet=False, decode=True): return contents -def shlex_split(blob): - if PY26 and isinstance(blob, six.text_type): - # Older versions don't support unicode input - blob = blob.encode("utf8") - return shlex.split(blob) - - def get_cmdline(): if 'DEBUG_PROC_CMDLINE' in os.environ: return os.environ["DEBUG_PROC_CMDLINE"] diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index ed2c6d0f..75c433f6 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -2,6 +2,7 @@ from cloudinit import util from cloudinit import net from cloudinit.net import cmdline from cloudinit.net import eni +from cloudinit.net import network_state from .helpers import TestCase from .helpers import mock @@ -112,14 +113,14 @@ class TestEniNetRendering(TestCase): mock_sys_dev_path.side_effect = sys_dev_path network_cfg = net.generate_fallback_config() - network_state = net.parse_net_config_data(network_cfg, - skip_broken=False) + ns = network_state.parse_net_config_data(network_cfg, + skip_broken=False) render_dir = os.path.join(tmp_dir, "render") os.makedirs(render_dir) renderer = eni.Renderer() - renderer.render_network_state(render_dir, network_state, + renderer.render_network_state(render_dir, ns, eni="interfaces", links_prefix=None, netrules=None) -- cgit v1.2.3 From 2fb5af62229b8975910bf0ef63731047bd8d7e63 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Thu, 19 May 2016 15:33:15 -0700 Subject: Fix up tests and flake8 warnings --- cloudinit/net/__init__.py | 2 +- cloudinit/net/eni.py | 16 ++++----- cloudinit/sources/DataSourceConfigDrive.py | 1 - cloudinit/stages.py | 2 +- tests/unittests/helpers.py | 1 + tests/unittests/test__init__.py | 1 - tests/unittests/test_datasource/test_cloudsigma.py | 1 - .../unittests/test_datasource/test_configdrive.py | 41 ++++++++++++---------- tests/unittests/test_datasource/test_nocloud.py | 2 ++ tests/unittests/test_datasource/test_smartos.py | 2 +- tests/unittests/test_net.py | 6 ++-- tests/unittests/test_rh_subscription.py | 5 +-- tox.ini | 4 +-- 13 files changed, 41 insertions(+), 43 deletions(-) (limited to 'cloudinit/net/eni.py') diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index ad44911b..ba0e39ae 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -49,8 +49,8 @@ DEFAULT_PRIMARY_INTERFACE = 'eth0' # whole module can be easily extracted and placed into other # code-bases (curtin for example). + def write_file(path, content): - """Simple writing a file helper.""" base_path = os.path.dirname(path) if not os.path.isdir(base_path): os.makedirs(base_path) diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index adb31c22..18bae97a 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -258,7 +258,7 @@ class Renderer(object): return content def _render_route(self, route, indent=""): - """ When rendering routes for an iface, in some cases applying a route + """When rendering routes for an iface, in some cases applying a route may result in the route command returning non-zero which produces some confusing output for users manually using ifup/ifdown[1]. To that end, we will optionally include an '|| true' postfix to each @@ -302,7 +302,7 @@ class Renderer(object): return content def _render_interfaces(self, network_state): - ''' Given state, emit etc/network/interfaces content ''' + '''Given state, emit etc/network/interfaces content''' content = "" interfaces = network_state.get('interfaces') @@ -336,8 +336,8 @@ class Renderer(object): iface['control'] = subnet.get('control', 'auto') if iface['mode'].endswith('6'): iface['inet'] += '6' - elif iface['mode'] == 'static' \ - and ":" in subnet['address']: + elif (iface['mode'] == 'static' + and ":" in subnet['address']): iface['inet'] += '6' if iface['mode'].startswith('dhcp'): iface['mode'] = 'dhcp' @@ -359,10 +359,10 @@ class Renderer(object): content = content.replace('mac_address', 'hwaddress') return content - def render_network_state(self, - target, network_state, eni="etc/network/interfaces", - links_prefix=LINKS_FNAME_PREFIX, - netrules='etc/udev/rules.d/70-persistent-net.rules'): + def render_network_state( + self, target, network_state, + eni="etc/network/interfaces", links_prefix=LINKS_FNAME_PREFIX, + netrules='etc/udev/rules.d/70-persistent-net.rules'): fpeni = os.path.join(target, eni) net.write_file(fpeni, self._render_interfaces(network_state)) diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py index 70373b43..4478c4e2 100644 --- a/cloudinit/sources/DataSourceConfigDrive.py +++ b/cloudinit/sources/DataSourceConfigDrive.py @@ -18,7 +18,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import copy import os from cloudinit import log as logging diff --git a/cloudinit/stages.py b/cloudinit/stages.py index 5dd31539..b837009a 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -44,8 +44,8 @@ from cloudinit import helpers from cloudinit import importer from cloudinit import log as logging from cloudinit import net -from cloudinit.reporting import events from cloudinit.net import cmdline +from cloudinit.reporting import events from cloudinit import sources from cloudinit import type_utils from cloudinit import util diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index 7b4d44e8..8d46a8bf 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -45,6 +45,7 @@ else: if _PY_MINOR == 4 and _PY_MICRO < 3: FIX_HTTPRETTY = True + # Makes the old path start # with new base instead of whatever # it previously had diff --git a/tests/unittests/test__init__.py b/tests/unittests/test__init__.py index a9b35afe..0154784a 100644 --- a/tests/unittests/test__init__.py +++ b/tests/unittests/test__init__.py @@ -1,7 +1,6 @@ import os import shutil import tempfile -import unittest2 from cloudinit import handlers from cloudinit import helpers diff --git a/tests/unittests/test_datasource/test_cloudsigma.py b/tests/unittests/test_datasource/test_cloudsigma.py index 7950fc52..2a42ce0c 100644 --- a/tests/unittests/test_datasource/test_cloudsigma.py +++ b/tests/unittests/test_datasource/test_cloudsigma.py @@ -6,7 +6,6 @@ from cloudinit.cs_utils import Cepko from cloudinit.sources import DataSourceCloudSigma from .. import helpers as test_helpers -from ..helpers import SkipTest SERVER_CONTEXT = { "cpu": 1000, diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py index 1db50798..5395e544 100644 --- a/tests/unittests/test_datasource/test_configdrive.py +++ b/tests/unittests/test_datasource/test_configdrive.py @@ -368,30 +368,32 @@ class TestNetJson(TestCase): self.assertEqual(myds.network_config, network_config) def test_network_config_conversions(self): - """Tests a bunch of input network json and checks the expected conversions.""" + """Tests a bunch of input network json and checks the + expected conversions.""" in_datas = [ NETWORK_DATA, { 'services': [{'type': 'dns', 'address': '172.19.0.12'}], - 'networks': [ - {'network_id': 'dacd568d-5be6-4786-91fe-750c374b78b4', - 'type': 'ipv4', 'netmask': '255.255.252.0', - 'link': 'tap1a81968a-79', - 'routes': [ - { - 'netmask': '0.0.0.0', - 'network': '0.0.0.0', - 'gateway': '172.19.3.254' - }, - ], - 'ip_address': '172.19.1.34', - 'id': 'network0', + 'networks': [{ + 'network_id': 'dacd568d-5be6-4786-91fe-750c374b78b4', + 'type': 'ipv4', + 'netmask': '255.255.252.0', + 'link': 'tap1a81968a-79', + 'routes': [{ + 'netmask': '0.0.0.0', + 'network': '0.0.0.0', + 'gateway': '172.19.3.254', + }], + 'ip_address': '172.19.1.34', + 'id': 'network0', + }], + 'links': [{ + 'type': 'bridge', + 'vif_id': '1a81968a-797a-400f-8a80-567f997eb93f', + 'ethernet_mac_address': 'fa:16:3e:ed:9a:59', + 'id': 'tap1a81968a-79', + 'mtu': None, }], - 'links': [ - {'type': 'bridge', - 'vif_id': '1a81968a-797a-400f-8a80-567f997eb93f', - 'ethernet_mac_address': 'fa:16:3e:ed:9a:59', - 'id': 'tap1a81968a-79', 'mtu': None}] }, ] out_datas = [ @@ -440,6 +442,7 @@ class TestNetJson(TestCase): 'address': '172.19.1.34', 'netmask': '255.255.252.0', 'type': 'static', + 'ipv4': True, 'routes': [{ 'gateway': '172.19.3.254', 'netmask': '0.0.0.0', diff --git a/tests/unittests/test_datasource/test_nocloud.py b/tests/unittests/test_datasource/test_nocloud.py index 077603b4..b0fa1130 100644 --- a/tests/unittests/test_datasource/test_nocloud.py +++ b/tests/unittests/test_datasource/test_nocloud.py @@ -7,6 +7,8 @@ import os import shutil import tempfile +import yaml + class TestNoCloudDataSource(TestCase): diff --git a/tests/unittests/test_datasource/test_smartos.py b/tests/unittests/test_datasource/test_smartos.py index 2f159ac4..28f56039 100644 --- a/tests/unittests/test_datasource/test_smartos.py +++ b/tests/unittests/test_datasource/test_smartos.py @@ -42,7 +42,7 @@ from cloudinit import helpers as c_helpers from cloudinit.util import b64e from .. import helpers -from ..helpers import mock, SkipTest +from ..helpers import mock MOCK_RETURNS = { 'hostname': 'test-host', diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index faf0f0fb..7998111a 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -1,13 +1,11 @@ -from cloudinit import net -from cloudinit import util - from cloudinit import net from cloudinit.net import cmdline from cloudinit.net import eni from cloudinit.net import network_state +from cloudinit import util -from .helpers import TestCase from .helpers import mock +from .helpers import TestCase import base64 import copy diff --git a/tests/unittests/test_rh_subscription.py b/tests/unittests/test_rh_subscription.py index e4dcc58b..891dbe77 100644 --- a/tests/unittests/test_rh_subscription.py +++ b/tests/unittests/test_rh_subscription.py @@ -10,13 +10,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - import logging -import mock -import unittest - from cloudinit.config import cc_rh_subscription +from cloudinit import util from .helpers import TestCase, mock diff --git a/tox.ini b/tox.ini index b92ebac1..7802a291 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,11 @@ [tox] -envlist = py27,py3,flake8 +envlist = py26,py27,py3,flake8 recreate = True usedevelop = True [testenv] -commands = python -m nose {posargs:tests} +commands = nosetests {posargs:tests} deps = -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt setenv = -- cgit v1.2.3 From 85a53d66ad0241b2d6453d902487bb2edc1512b8 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 24 May 2016 13:48:50 -0700 Subject: Fix up some of the net usage and restore imports and add a mini compat module --- cloudinit/net/__init__.py | 5 +++-- cloudinit/net/cmdline.py | 15 +++++-------- cloudinit/net/compat.py | 51 ++++++++++++++++++++++++++++++++++++++++++ cloudinit/net/eni.py | 15 ++++++------- cloudinit/net/network_state.py | 23 ++++++++++--------- 5 files changed, 79 insertions(+), 30 deletions(-) create mode 100644 cloudinit/net/compat.py (limited to 'cloudinit/net/eni.py') diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index 776af0d3..f8df58f0 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -20,7 +20,8 @@ import errno import logging import os -import six +from .import compat + import yaml LOG = logging.getLogger(__name__) @@ -55,7 +56,7 @@ def write_file(path, content): if not os.path.isdir(base_path): os.makedirs(base_path) with open(path, "wb+") as fh: - if isinstance(content, six.text_type): + if isinstance(content, compat.text_type): content = content.encode("utf8") fh.write(content) diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py index b85d4b0a..41cba893 100644 --- a/cloudinit/net/cmdline.py +++ b/cloudinit/net/cmdline.py @@ -21,20 +21,17 @@ import glob import gzip import io import shlex -import sys -import six - -from cloudinit.net import get_devicelist -from cloudinit.net import sys_netdev_info +from . import compat +from . import get_devicelist +from . import read_file +from . import sys_netdev_info from cloudinit import util -PY26 = sys.version_info[0:2] == (2, 6) - def _shlex_split(blob): - if PY26 and isinstance(blob, six.text_type): + if compat.PY26 and isinstance(blob, compat.text_type): # Older versions don't support unicode input blob = blob.encode("utf8") return shlex.split(blob) @@ -143,7 +140,7 @@ def config_from_klibc_net_cfg(files=None, mac_addrs=None): entries = [] names = {} for cfg_file in files: - name, entry = _klibc_to_config_entry(util.load_file(cfg_file), + name, entry = _klibc_to_config_entry(read_file(cfg_file), mac_addrs=mac_addrs) if name in names: raise ValueError( diff --git a/cloudinit/net/compat.py b/cloudinit/net/compat.py new file mode 100644 index 00000000..8bf92ef5 --- /dev/null +++ b/cloudinit/net/compat.py @@ -0,0 +1,51 @@ +# Curtin is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# Curtin is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +# more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Curtin. If not, see . + +# Mini six like module (so that this code can be more easily extracted). + +import sys + +PY26 = sys.version_info[0:2] == (2, 6) +PY27 = sys.version_info[0:2] == (2, 7) +PY2 = PY26 or PY27 +PY3 = sys.version_info[0:2] >= (3, 0) + +if PY3: + text_type = str + binary_type = bytes + string_types = (text_type, text_type) + import io + StringIO = io.StringIO +else: + text_type = unicode + binary_type = bytes + string_types = (binary_type, text_type) + from StringIO import StringIO # noqa + + +# Taken from six (remove when we can actually directly use six) + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index 18bae97a..f82c7f54 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -16,11 +16,11 @@ import glob import os import re -from cloudinit import net +from . import LINKS_FNAME_PREFIX +from . import ParserError +from . import write_file -from cloudinit.net import LINKS_FNAME_PREFIX -from cloudinit.net import ParserError -from cloudinit.net.udev import generate_udev_rule +from .udev import generate_udev_rule NET_CONFIG_COMMANDS = [ @@ -365,12 +365,11 @@ class Renderer(object): netrules='etc/udev/rules.d/70-persistent-net.rules'): fpeni = os.path.join(target, eni) - net.write_file(fpeni, self._render_interfaces(network_state)) + write_file(fpeni, self._render_interfaces(network_state)) if netrules: netrules = os.path.join(target, netrules) - net.write_file(netrules, - self._render_persistent_net(network_state)) + write_file(netrules, self._render_persistent_net(network_state)) if links_prefix: self._render_systemd_links(target, network_state, links_prefix) @@ -393,4 +392,4 @@ class Renderer(object): "Name=" + iface['name'], "" ]) - net.write_file(fname, content) + write_file(fname, content) diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py index c5aeadb5..32c48229 100644 --- a/cloudinit/net/network_state.py +++ b/cloudinit/net/network_state.py @@ -16,11 +16,12 @@ # along with Curtin. If not, see . import copy +import functools import logging -import six - -from cloudinit import net +from . import compat +from . import dump_yaml +from . import read_yaml_file LOG = logging.getLogger(__name__) @@ -48,7 +49,7 @@ def parse_net_config(path, skip_broken=True): """Parses a curtin network configuration file and return network state""" ns = None - net_config = net.read_yaml_file(path) + net_config = read_yaml_file(path) if 'network' in net_config: ns = parse_net_config_data(net_config.get('network'), skip_broken=skip_broken) @@ -57,7 +58,7 @@ def parse_net_config(path, skip_broken=True): def from_state_file(state_file): network_state = None - state = net.read_yaml_file(state_file) + state = read_yaml_file(state_file) network_state = NetworkState() network_state.load(state) return network_state @@ -78,7 +79,7 @@ def ensure_command_keys(required_keys): def wrapper(func): - @six.wraps(func) + @functools.wraps(func) def decorator(self, command, *args, **kwargs): if required_keys: missing_keys = diff_keys(required_keys, command) @@ -102,8 +103,8 @@ class CommandHandlerMeta(type): """ def __new__(cls, name, parents, dct): command_handlers = {} - for attr_name, attr in six.iteritems(dct): - if six.callable(attr) and attr_name.startswith('handle_'): + for attr_name, attr in dct.items(): + if callable(attr) and attr_name.startswith('handle_'): handles_what = attr_name[len('handle_'):] if handles_what: command_handlers[handles_what] = attr @@ -112,7 +113,7 @@ class CommandHandlerMeta(type): parents, dct) -@six.add_metaclass(CommandHandlerMeta) +@compat.add_metaclass(CommandHandlerMeta) class NetworkState(object): initial_network_state = { @@ -135,7 +136,7 @@ class NetworkState(object): 'config': self.config, 'network_state': self.network_state, } - return net.dump_yaml(state) + return dump_yaml(state) def load(self, state): if 'version' not in state: @@ -154,7 +155,7 @@ class NetworkState(object): setattr(self, key, state[key]) def dump_network_state(self): - return net.dump_yaml(self.network_state) + return dump_yaml(self.network_state) def parse_config(self, skip_broken=True): # rebuild network state -- cgit v1.2.3 From 6e19efb17609cd5c49941ea4f1608900b8459ca8 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Mon, 13 Jun 2016 13:10:07 -0700 Subject: Refactor some of sysconfig changes -> network_state module --- cloudinit/net/eni.py | 22 +++++---- cloudinit/net/network_state.py | 103 +++++++++++++++++++++++++++++------------ cloudinit/net/sysconfig.py | 26 +---------- 3 files changed, 87 insertions(+), 64 deletions(-) (limited to 'cloudinit/net/eni.py') diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index a695f5ed..df0df9b2 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -360,7 +360,15 @@ class Renderer(object): '''Given state, emit etc/network/interfaces content.''' content = "" - interfaces = network_state.get('interfaces') + content += "auto lo\niface lo inet loopback\n" + + nameservers = network_state.dns_nameservers + if nameservers: + content += " dns-nameservers %s\n" % (" ".join(nameservers)) + searchdomains = network_state.dns_searchdomains + if searchdomains: + content += " dns-search %s\n" % (" ".join(searchdomains)) + ''' Apply a sort order to ensure that we write out the physical interfaces first; this is critical for bonding @@ -371,12 +379,7 @@ class Renderer(object): 'bridge': 2, 'vlan': 3, } - content += "auto lo\niface lo inet loopback\n" - for dnskey, value in network_state.get('dns', {}).items(): - if len(value): - content += " dns-{} {}\n".format(dnskey, " ".join(value)) - - for iface in sorted(interfaces.values(), + for iface in sorted(network_state.iter_interfaces(), key=lambda k: (order[k['type']], k['name'])): if content[-2:] != "\n\n": @@ -409,7 +412,7 @@ class Renderer(object): content += "iface {name} {inet} {mode}\n".format(**iface) content += _iface_add_attrs(iface) - for route in network_state.get('routes'): + for route in network_state.iter_routes(): content += self._render_route(route) # global replacements until v2 format @@ -441,8 +444,7 @@ class Renderer(object): fp_prefix = os.path.sep.join((target, links_prefix)) for f in glob.glob(fp_prefix + "*"): os.unlink(f) - interfaces = network_state.get('interfaces') - for iface in interfaces.values(): + for iface in network_state.iter_interfaces(): if (iface['type'] == 'physical' and 'name' in iface and iface.get('mac_address')): fname = fp_prefix + iface['name'] + ".link" diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py index a8be5e26..d75fc852 100644 --- a/cloudinit/net/network_state.py +++ b/cloudinit/net/network_state.py @@ -38,10 +38,10 @@ def parse_net_config_data(net_config, skip_broken=True): """ state = None if 'version' in net_config and 'config' in net_config: - ns = NetworkState(version=net_config.get('version'), - config=net_config.get('config')) - ns.parse_config(skip_broken=skip_broken) - state = ns.network_state + nsi = NetworkStateInterpreter(version=net_config.get('version'), + config=net_config.get('config')) + nsi.parse_config(skip_broken=skip_broken) + state = nsi.network_state return state @@ -57,11 +57,10 @@ def parse_net_config(path, skip_broken=True): def from_state_file(state_file): - network_state = None state = util.read_conf(state_file) - network_state = NetworkState() - network_state.load(state) - return network_state + nsi = NetworkStateInterpreter() + nsi.load(state) + return nsi def diff_keys(expected, actual): @@ -113,9 +112,51 @@ class CommandHandlerMeta(type): parents, dct) -@six.add_metaclass(CommandHandlerMeta) class NetworkState(object): + def __init__(self, network_state, version=NETWORK_STATE_VERSION): + self._network_state = copy.deepcopy(network_state) + self._version = version + + @property + def version(self): + return self._version + + def iter_routes(self, filter_func=None): + for route in self._network_state.get('routes', []): + if filter_func is not None: + if filter_func(route): + yield route + else: + yield route + + @property + def dns_nameservers(self): + try: + return self._network_state['dns']['nameservers'] + except KeyError: + return [] + + @property + def dns_searchdomains(self): + try: + return self._network_state['dns']['search'] + except KeyError: + return [] + + def iter_interfaces(self, filter_func=None): + ifaces = self._network_state.get('interfaces', {}) + for iface in six.itervalues(ifaces): + if filter_func is None: + yield iface + else: + if filter_func(iface): + yield iface + + +@six.add_metaclass(CommandHandlerMeta) +class NetworkStateInterpreter(object): + initial_network_state = { 'interfaces': {}, 'routes': [], @@ -126,22 +167,26 @@ class NetworkState(object): } def __init__(self, version=NETWORK_STATE_VERSION, config=None): - self.version = version - self.config = config - self.network_state = copy.deepcopy(self.initial_network_state) + self._version = version + self._config = config + self._network_state = copy.deepcopy(self.initial_network_state) + + @property + def network_state(self): + return NetworkState(self._network_state, version=self._version) def dump(self): state = { - 'version': self.version, - 'config': self.config, - 'network_state': self.network_state, + 'version': self._version, + 'config': self._config, + 'network_state': self._network_state, } return util.yaml_dumps(state) def load(self, state): if 'version' not in state: LOG.error('Invalid state, missing version field') - raise Exception('Invalid state, missing version field') + raise ValueError('Invalid state, missing version field') required_keys = NETWORK_STATE_REQUIRED_KEYS[state['version']] missing_keys = diff_keys(required_keys, state) @@ -155,11 +200,11 @@ class NetworkState(object): setattr(self, key, state[key]) def dump_network_state(self): - return util.yaml_dumps(self.network_state) + return util.yaml_dumps(self._network_state) def parse_config(self, skip_broken=True): # rebuild network state - for command in self.config: + for command in self._config: command_type = command['type'] try: handler = self.command_handlers[command_type] @@ -189,7 +234,7 @@ class NetworkState(object): } ''' - interfaces = self.network_state.get('interfaces') + interfaces = self._network_state.get('interfaces', {}) iface = interfaces.get(command['name'], {}) for param, val in command.get('params', {}).items(): iface.update({param: val}) @@ -215,7 +260,7 @@ class NetworkState(object): 'gateway': None, 'subnets': subnets, }) - self.network_state['interfaces'].update({command.get('name'): iface}) + self._network_state['interfaces'].update({command.get('name'): iface}) self.dump_network_state() @ensure_command_keys(['name', 'vlan_id', 'vlan_link']) @@ -228,7 +273,7 @@ class NetworkState(object): hwaddress ether BC:76:4E:06:96:B3 vlan-raw-device eth0 ''' - interfaces = self.network_state.get('interfaces') + interfaces = self._network_state.get('interfaces', {}) self.handle_physical(command) iface = interfaces.get(command.get('name'), {}) iface['vlan-raw-device'] = command.get('vlan_link') @@ -263,12 +308,12 @@ class NetworkState(object): ''' self.handle_physical(command) - interfaces = self.network_state.get('interfaces') + interfaces = self._network_state.get('interfaces') iface = interfaces.get(command.get('name'), {}) for param, val in command.get('params').items(): iface.update({param: val}) iface.update({'bond-slaves': 'none'}) - self.network_state['interfaces'].update({iface['name']: iface}) + self._network_state['interfaces'].update({iface['name']: iface}) # handle bond slaves for ifname in command.get('bond_interfaces'): @@ -280,13 +325,13 @@ class NetworkState(object): # inject placeholder self.handle_physical(cmd) - interfaces = self.network_state.get('interfaces') + interfaces = self._network_state.get('interfaces', {}) bond_if = interfaces.get(ifname) bond_if['bond-master'] = command.get('name') # copy in bond config into slave for param, val in command.get('params').items(): bond_if.update({param: val}) - self.network_state['interfaces'].update({ifname: bond_if}) + self._network_state['interfaces'].update({ifname: bond_if}) @ensure_command_keys(['name', 'bridge_interfaces', 'params']) def handle_bridge(self, command): @@ -319,7 +364,7 @@ class NetworkState(object): # find one of the bridge port ifaces to get mac_addr # handle bridge_slaves - interfaces = self.network_state.get('interfaces') + interfaces = self._network_state.get('interfaces', {}) for ifname in command.get('bridge_interfaces'): if ifname in interfaces: continue @@ -330,7 +375,7 @@ class NetworkState(object): # inject placeholder self.handle_physical(cmd) - interfaces = self.network_state.get('interfaces') + interfaces = self._network_state.get('interfaces', {}) self.handle_physical(command) iface = interfaces.get(command.get('name'), {}) iface['bridge_ports'] = command['bridge_interfaces'] @@ -341,7 +386,7 @@ class NetworkState(object): @ensure_command_keys(['address']) def handle_nameserver(self, command): - dns = self.network_state.get('dns') + dns = self._network_state.get('dns') if 'address' in command: addrs = command['address'] if not type(addrs) == list: @@ -357,7 +402,7 @@ class NetworkState(object): @ensure_command_keys(['destination']) def handle_route(self, command): - routes = self.network_state.get('routes') + routes = self._network_state.get('routes', []) network, cidr = command['destination'].split("/") netmask = cidr2mask(int(cidr)) route = { diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index a4bcaaad..647ca3ed 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -67,29 +67,6 @@ def _quote_value(value): return value -class NetworkStateHelper(object): - def __init__(self, network_state): - self._network_state = network_state.copy() - - @property - def dns_nameservers(self): - return self._network_state['dns']['nameservers'] - - @property - def dns_searchdomains(self): - return self._network_state['dns']['search'] - - def iter_interfaces(self, filter_func=None): - ifaces = self._network_state.get('interfaces') - if ifaces: - for iface in ifaces.values(): - if filter_func is None: - yield iface - else: - if filter_func(iface): - yield iface - - class ConfigMap(object): """Sysconfig like dictionary object.""" @@ -424,7 +401,6 @@ class Renderer(object): self, target, network_state, sysconf_dir="etc/sysconfig/", netrules='etc/udev/rules.d/70-persistent-net.rules', dns='etc/resolv.conf'): - network_state = NetworkStateHelper(network_state) if target: base_sysconf_dir = os.path.join(target, sysconf_dir) else: @@ -480,7 +456,7 @@ def main(): net_state = network_state.parse_net_config_data( openstack.convert_net_json(network_json), skip_broken=False) r = Renderer() - r.render_network_state(args.dir, NetworkStateHelper(net_state)) + r.render_network_state(args.dir, net_state) if __name__ == '__main__': -- cgit v1.2.3 From 632d56a23a947a23e18fa234d675e34a1a119593 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Tue, 14 Jun 2016 15:18:53 -0400 Subject: fix pep8 failure introduced in recent commit. The commit 1232 (Refactor a large part of the networking code) broke pep8. --- cloudinit/net/eni.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'cloudinit/net/eni.py') diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index a695f5ed..5a90eb32 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -391,8 +391,8 @@ class Renderer(object): iface['control'] = subnet.get('control', 'auto') if iface['mode'].endswith('6'): iface['inet'] += '6' - elif (iface['mode'] == 'static' - and ":" in subnet['address']): + elif (iface['mode'] == 'static' and + ":" in subnet['address']): iface['inet'] += '6' if iface['mode'].startswith('dhcp'): iface['mode'] = 'dhcp' -- cgit v1.2.3 From 459de83024b41c32823b75cf483da994fb1388b7 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 15 Jun 2016 16:11:24 -0700 Subject: Fixup code review comments --- cloudinit/distros/debian.py | 12 ++--- cloudinit/distros/rhel.py | 4 +- cloudinit/net/__init__.py | 1 - cloudinit/net/eni.py | 36 +++++++------ cloudinit/net/renderer.py | 48 +++++++++++++++++ cloudinit/net/sysconfig.py | 123 +++++++++++--------------------------------- tests/unittests/test_net.py | 10 ++-- 7 files changed, 109 insertions(+), 125 deletions(-) create mode 100644 cloudinit/net/renderer.py (limited to 'cloudinit/net/eni.py') diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py index e71aaa97..511ed4fe 100644 --- a/cloudinit/distros/debian.py +++ b/cloudinit/distros/debian.py @@ -57,7 +57,11 @@ class Distro(distros.Distro): # should only happen say once per instance...) self._runner = helpers.Runners(paths) self.osfamily = 'debian' - self._net_renderer = eni.Renderer() + self._net_renderer = eni.Renderer({ + 'eni_path': self.network_conf_fn, + 'links_prefix_path': self.links_prefix, + 'netrules_path': None, + }) def apply_locale(self, locale, out_fn=None): if not out_fn: @@ -82,12 +86,8 @@ class Distro(distros.Distro): def _write_network_config(self, netconfig): ns = net.parse_net_config_data(netconfig) - self._net_renderer.render_network_state( - target="/", network_state=ns, - eni=self.network_conf_fn, links_prefix=self.links_prefix, - netrules=None) + self._net_renderer.render_network_state("/", ns) _maybe_remove_legacy_eth0() - return [] def _bring_up_interfaces(self, device_names): diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index 20525e47..1aa42d75 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -67,8 +67,8 @@ class Distro(distros.Distro): self.package_command('install', pkgs=pkglist) def _write_network_config(self, netconfig): - self._net_renderer.render_network_state( - target="/", parse_net_config_data(netconfig)) + ns = parse_net_config_data(netconfig) + self._net_renderer.render_network_state("/", ns) return [] def _write_network(self, settings): diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index f5668fff..6959ad34 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -26,7 +26,6 @@ from cloudinit import util LOG = logging.getLogger(__name__) SYS_CLASS_NET = "/sys/class/net/" DEFAULT_PRIMARY_INTERFACE = 'eth0' -LINKS_FNAME_PREFIX = "etc/systemd/network/50-cloud-init-" def sys_dev_path(devname, path=""): diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index df0df9b2..352f7dd5 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -16,10 +16,9 @@ import glob import os import re -from . import LINKS_FNAME_PREFIX from . import ParserError -from .udev import generate_udev_rule +from . import renderer from cloudinit import util @@ -297,9 +296,18 @@ def _ifaces_to_net_config_data(ifaces): 'config': [devs[d] for d in sorted(devs)]} -class Renderer(object): +class Renderer(renderer.Renderer): """Renders network information in a /etc/network/interfaces format.""" + def __init__(self, config=None): + if not config: + config = {} + self.eni_path = config.get('eni_path', 'etc/network/interfaces') + self.links_path_prefix = config.get( + 'links_path_prefix', 'etc/systemd/network/50-cloud-init-') + self.netrules_path = config.get( + 'netrules_path', 'etc/udev/rules.d/70-persistent-net.rules') + def _render_persistent_net(self, network_state): """Given state, emit udev rules to map mac to ifname.""" content = "" @@ -419,29 +427,23 @@ class Renderer(object): content = content.replace('mac_address', 'hwaddress') return content - def render_network_state( - self, target, network_state, eni="etc/network/interfaces", - links_prefix=LINKS_FNAME_PREFIX, - netrules='etc/udev/rules.d/70-persistent-net.rules', - writer=None): - - fpeni = os.path.sep.join((target, eni,)) + def render_network_state(self, target, network_state): + fpeni = os.path.join(target, self.eni_path) util.ensure_dir(os.path.dirname(fpeni)) util.write_file(fpeni, self._render_interfaces(network_state)) - if netrules: - netrules = os.path.sep.join((target, netrules,)) + if self.netrules_path: + netrules = os.path.join(target, self.netrules_path) util.ensure_dir(os.path.dirname(netrules)) util.write_file(netrules, self._render_persistent_net(network_state)) - if links_prefix: + if self.links_path_prefix: self._render_systemd_links(target, network_state, - links_prefix=links_prefix) + links_prefix=self.links_path_prefix) - def _render_systemd_links(self, target, network_state, - links_prefix=LINKS_FNAME_PREFIX): - fp_prefix = os.path.sep.join((target, links_prefix)) + def _render_systemd_links(self, target, network_state, links_prefix): + fp_prefix = os.path.join(target, links_prefix) for f in glob.glob(fp_prefix + "*"): os.unlink(f) for iface in network_state.iter_interfaces(): diff --git a/cloudinit/net/renderer.py b/cloudinit/net/renderer.py new file mode 100644 index 00000000..310cbe0d --- /dev/null +++ b/cloudinit/net/renderer.py @@ -0,0 +1,48 @@ +# Copyright (C) 2013-2014 Canonical Ltd. +# +# Author: Scott Moser +# Author: Blake Rouse +# +# Curtin is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# Curtin is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +# more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Curtin. If not, see . + +import six + +from .udev import generate_udev_rule + + +def filter_by_type(match_type): + return lambda iface: match_type == iface['type'] + + +def filter_by_name(match_name): + return lambda iface: match_name == iface['name'] + + +filter_by_physical = filter_by_type('physical') + + +class Renderer(object): + + @staticmethod + def _render_persistent_net(network_state): + """Given state, emit udev rules to map mac to ifname.""" + # TODO(harlowja): this seems shared between eni renderer and + # this, so move it to a shared location. + content = six.StringIO() + for iface in network_state.iter_interfaces(filter_by_physical): + # for physical interfaces write out a persist net udev rule + if 'name' in iface and iface.get('mac_address'): + content.write(generate_udev_rule(iface['name'], + iface['mac_address'])) + return content.getvalue() diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index 647ca3ed..dd005796 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -20,9 +20,10 @@ import six from cloudinit.distros.parsers import resolv_conf from cloudinit import util -from . import network_state from .udev import generate_udev_rule +from . import renderer + def _make_header(sep='#'): lines = [ @@ -37,17 +38,6 @@ def _make_header(sep='#'): return "\n".join(lines) -def _filter_by_type(match_type): - return lambda iface: match_type == iface['type'] - - -def _filter_by_name(match_name): - return lambda iface: match_name == iface['name'] - - -_filter_by_physical = _filter_by_type('physical') - - def _is_default_route(route): if route['network'] == '::' and route['netmask'] == 0: return True @@ -182,7 +172,7 @@ class NetInterface(ConfigMap): return c -class Renderer(object): +class Renderer(renderer.Renderer): """Renders network information in a /etc/sysconfig format.""" # See: https://access.redhat.com/documentation/en-US/\ @@ -211,18 +201,13 @@ class Renderer(object): ('bridge_bridgeprio', 'PRIO'), ]) - @staticmethod - def _render_persistent_net(network_state): - """Given state, emit udev rules to map mac to ifname.""" - # TODO(harlowja): this seems shared between eni renderer and - # this, so move it to a shared location. - content = six.StringIO() - for iface in network_state.iter_interfaces(_filter_by_physical): - # for physical interfaces write out a persist net udev rule - if 'name' in iface and iface.get('mac_address'): - content.write(generate_udev_rule(iface['name'], - iface['mac_address'])) - return content.getvalue() + def __init__(self, config=None): + if not config: + config = {} + self.sysconf_dir = config.get('sysconf_dir', 'etc/sysconfig/') + self.netrules_path = config.get( + 'netrules_path', 'etc/udev/rules.d/70-persistent-net.rules') + self.dns_path = config.get('dns_path', 'etc/resolv.conf') @classmethod def _render_iface_shared(cls, iface, iface_cfg): @@ -302,7 +287,7 @@ class Renderer(object): @classmethod def _render_physical_interfaces(cls, network_state, iface_contents): - for iface in network_state.iter_interfaces(_filter_by_physical): + for iface in network_state.iter_interfaces(renderer.filter_by_physical): iface_name = iface['name'] iface_subnets = iface.get("subnets", []) iface_cfg = iface_contents[iface_name] @@ -319,7 +304,7 @@ class Renderer(object): @classmethod def _render_bond_interfaces(cls, network_state, iface_contents): - for iface in network_state.iter_interfaces(_filter_by_type('bond')): + for iface in network_state.iter_interfaces(renderer.filter_by_type('bond')): iface_name = iface['name'] iface_cfg = iface_contents[iface_name] cls._render_bonding_opts(iface_cfg, iface) @@ -337,7 +322,7 @@ class Renderer(object): @staticmethod def _render_vlan_interfaces(network_state, iface_contents): - for iface in network_state.iter_interfaces(_filter_by_type('vlan')): + for iface in network_state.iter_interfaces(renderer.filter_by_type('vlan')): iface_name = iface['name'] iface_cfg = iface_contents[iface_name] iface_cfg['VLAN'] = True @@ -348,15 +333,15 @@ class Renderer(object): content = resolv_conf.ResolvConf("") if existing_dns_path and os.path.isfile(existing_dns_path): content = resolv_conf.ResolvConf(util.load_file(existing_dns_path)) - for ns in network_state.dns_nameservers: - content.add_nameserver(ns) - for d in network_state.dns_searchdomains: - content.add_search_domain(d) + for nameserver in network_state.dns_nameservers: + content.add_nameserver(nameserver) + for searchdomain in network_state.dns_searchdomains: + content.add_search_domain(searchdomain) return "\n".join([_make_header(';'), str(content)]) @classmethod def _render_bridge_interfaces(cls, network_state, iface_contents): - for iface in network_state.iter_interfaces(_filter_by_type('bridge')): + for iface in network_state.iter_interfaces(renderer.filter_by_type('bridge')): iface_name = iface['name'] iface_cfg = iface_contents[iface_name] iface_cfg.kind = 'bridge' @@ -397,67 +382,17 @@ class Renderer(object): contents[iface_cfg.routes.path] = iface_cfg.routes.to_string() return contents - def render_network_state( - self, target, network_state, sysconf_dir="etc/sysconfig/", - netrules='etc/udev/rules.d/70-persistent-net.rules', - dns='etc/resolv.conf'): - if target: - base_sysconf_dir = os.path.join(target, sysconf_dir) - else: - base_sysconf_dir = sysconf_dir + def render_network_state(self, target, network_state): + base_sysconf_dir = os.path.join(target, self.sysconf_dir) for path, data in self._render_sysconfig(base_sysconf_dir, network_state).items(): - if target: - util.write_file(path, data) - else: - print("File to be at: %s" % path) - print(data) - if dns: - if target: - dns_path = os.path.join(target, dns) - resolv_content = self._render_dns(network_state, - existing_dns_path=dns_path) - util.write_file(dns_path, resolv_content) - else: - resolv_content = self._render_dns(network_state) - dns_path = dns - print("File to be at: %s" % dns_path) - print(resolv_content) - if netrules: + util.write_file(path, data) + if self.dns_path: + dns_path = os.path.join(target, self.dns_path) + resolv_content = self._render_dns(network_state, + existing_dns_path=dns_path) + util.write_file(dns_path, resolv_content) + if self.netrules_path: netrules_content = self._render_persistent_net(network_state) - if target: - netrules_path = os.path.join(target, netrules) - util.write_file(netrules_path, netrules_content) - else: - netrules_path = netrules - print("File to be at: %s" % netrules_path) - print(netrules_content) - - -def main(): - """Reads a os network state json file and outputs what would be written.""" - from cloudinit.sources.helpers import openstack - - import argparse - import json - - parser = argparse.ArgumentParser() - parser.add_argument("-f", "--file", metavar="FILE", - help=("openstack network json file" - " to read (required)"), - required=True) - parser.add_argument("-d", "--dir", metavar="DIR", - help=("directory to write output into (if" - " not provided then written to stdout)"), - default=None) - args = parser.parse_args() - - network_json = json.loads(util.load_file(args.file)) - net_state = network_state.parse_net_config_data( - openstack.convert_net_json(network_json), skip_broken=False) - r = Renderer() - r.render_network_state(args.dir, net_state) - - -if __name__ == '__main__': - main() + netrules_path = os.path.join(target, self.netrules_path) + util.write_file(netrules_path, netrules_content) diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 43602030..3ae00fc6 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -248,11 +248,11 @@ class TestEniNetRendering(TestCase): render_dir = os.path.join(tmp_dir, "render") os.makedirs(render_dir) - renderer = eni.Renderer() - renderer.render_network_state(render_dir, ns, - eni="interfaces", - links_prefix=None, - netrules=None) + renderer = eni.Renderer( + {'links_path_prefix': None, + 'eni_path': 'interfaces', 'netrules_path': None, + }) + renderer.render_network_state(render_dir, ns) self.assertTrue(os.path.exists(os.path.join(render_dir, 'interfaces'))) -- cgit v1.2.3 From a454a6408b3c7ecc816073049d060632191099bb Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 15 Jun 2016 16:15:28 -0700 Subject: Fix line length issues --- cloudinit/net/eni.py | 13 ------------- cloudinit/net/sysconfig.py | 14 ++++++++------ 2 files changed, 8 insertions(+), 19 deletions(-) (limited to 'cloudinit/net/eni.py') diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index 352f7dd5..c8adb1ae 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -308,19 +308,6 @@ class Renderer(renderer.Renderer): self.netrules_path = config.get( 'netrules_path', 'etc/udev/rules.d/70-persistent-net.rules') - def _render_persistent_net(self, network_state): - """Given state, emit udev rules to map mac to ifname.""" - content = "" - interfaces = network_state.get('interfaces') - for iface in interfaces.values(): - # for physical interfaces write out a persist net udev rule - if iface['type'] == 'physical' and \ - 'name' in iface and iface.get('mac_address'): - content += generate_udev_rule(iface['name'], - iface['mac_address']) - - return content - def _render_route(self, route, indent=""): """When rendering routes for an iface, in some cases applying a route may result in the route command returning non-zero which produces diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index dd005796..c53acf71 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -20,8 +20,6 @@ import six from cloudinit.distros.parsers import resolv_conf from cloudinit import util -from .udev import generate_udev_rule - from . import renderer @@ -287,7 +285,8 @@ class Renderer(renderer.Renderer): @classmethod def _render_physical_interfaces(cls, network_state, iface_contents): - for iface in network_state.iter_interfaces(renderer.filter_by_physical): + physical_filter = renderer.filter_by_physical + for iface in network_state.iter_interfaces(physical_filter): iface_name = iface['name'] iface_subnets = iface.get("subnets", []) iface_cfg = iface_contents[iface_name] @@ -304,7 +303,8 @@ class Renderer(renderer.Renderer): @classmethod def _render_bond_interfaces(cls, network_state, iface_contents): - for iface in network_state.iter_interfaces(renderer.filter_by_type('bond')): + bond_filter = renderer.filter_by_type('bond') + for iface in network_state.iter_interfaces(bond_filter): iface_name = iface['name'] iface_cfg = iface_contents[iface_name] cls._render_bonding_opts(iface_cfg, iface) @@ -322,7 +322,8 @@ class Renderer(renderer.Renderer): @staticmethod def _render_vlan_interfaces(network_state, iface_contents): - for iface in network_state.iter_interfaces(renderer.filter_by_type('vlan')): + vlan_filter = renderer.filter_by_type('vlan') + for iface in network_state.iter_interfaces(vlan_filter): iface_name = iface['name'] iface_cfg = iface_contents[iface_name] iface_cfg['VLAN'] = True @@ -341,7 +342,8 @@ class Renderer(renderer.Renderer): @classmethod def _render_bridge_interfaces(cls, network_state, iface_contents): - for iface in network_state.iter_interfaces(renderer.filter_by_type('bridge')): + bridge_filter = renderer.filter_by_type('bridge') + for iface in network_state.iter_interfaces(bridge_filter): iface_name = iface['name'] iface_cfg = iface_contents[iface_name] iface_cfg.kind = 'bridge' -- cgit v1.2.3 From 4fc10ef3f349e6280e2002d980c4c63e6a741d2f Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Mon, 20 Jun 2016 17:07:10 -0400 Subject: add a header to rendered ENI file describing its content --- cloudinit/distros/debian.py | 8 ++++++++ cloudinit/net/eni.py | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'cloudinit/net/eni.py') diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py index 244af349..5ae9a509 100644 --- a/cloudinit/distros/debian.py +++ b/cloudinit/distros/debian.py @@ -43,6 +43,13 @@ APT_GET_WRAPPER = { 'enabled': 'auto', } +ENI_HEADER = """# This file is generated from information provided by +# the datasource. Changes to it will not persist across an instance. +# To disable cloud-init's network configuration capabilities, write a file +# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: +# network: {config: disabled} +""" + class Distro(distros.Distro): hostname_conf_fn = "/etc/hostname" @@ -59,6 +66,7 @@ class Distro(distros.Distro): self.osfamily = 'debian' self._net_renderer = eni.Renderer({ 'eni_path': self.network_conf_fn, + 'eni_header': ENI_HEADER, 'links_prefix_path': None, 'netrules_path': None, }) diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index ccd16ba7..e5ed10fd 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -303,6 +303,7 @@ class Renderer(renderer.Renderer): if not config: config = {} self.eni_path = config.get('eni_path', 'etc/network/interfaces') + self.eni_header = config.get('eni_header', None) self.links_path_prefix = config.get( 'links_path_prefix', 'etc/systemd/network/50-cloud-init-') self.netrules_path = config.get( @@ -417,7 +418,8 @@ class Renderer(renderer.Renderer): def render_network_state(self, target, network_state): fpeni = os.path.join(target, self.eni_path) util.ensure_dir(os.path.dirname(fpeni)) - util.write_file(fpeni, self._render_interfaces(network_state)) + header = self.eni_header if self.eni_header else "" + util.write_file(fpeni, header + self._render_interfaces(network_state)) if self.netrules_path: netrules = os.path.join(target, self.netrules_path) -- cgit v1.2.3