remove your print statements. i guess go ahead and fix the TODO that you added.
running 'tox' fails for me, always good to fix that . the rest of it looks reasonable. Diff comments: > > === added file 'cloudinit/net/sysconfig.py' > --- cloudinit/net/sysconfig.py 1970-01-01 00:00:00 +0000 > +++ cloudinit/net/sysconfig.py 2016-06-14 17:59:09 +0000 > @@ -0,0 +1,463 @@ > +# 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 <http://www.gnu.org/licenses/>. > + > +import os > +import re > + > +import six > + > +from cloudinit.distros.parsers import resolv_conf > +from cloudinit import util > + > +from . import network_state > +from .udev import generate_udev_rule > + > + > +def _make_header(sep='#'): > + lines = [ > + "Created by cloud-init on instance boot automatically, do not edit.", > + "", > + ] > + for i in range(0, len(lines)): > + if lines[i]: > + lines[i] = sep + " " + lines[i] > + else: > + lines[i] = 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 > + if route['network'] == '0.0.0.0' and route['netmask'] == '0.0.0.0': > + return True > + return False > + > + > +def _quote_value(value): > + if re.search(r"\s", value): > + # This doesn't handle complex cases... > + if value.startswith('"') and value.endswith('"'): > + return value > + else: > + return '"%s"' % value > + else: > + return value > + > + > +class ConfigMap(object): > + """Sysconfig like dictionary object.""" > + > + # Why does redhat prefer yes/no to true/false?? > + _bool_map = { > + True: 'yes', > + False: 'no', > + } > + > + def __init__(self): > + self._conf = {} > + > + def __setitem__(self, key, value): > + self._conf[key] = value > + > + def drop(self, key): > + self._conf.pop(key, None) > + > + def __len__(self): > + return len(self._conf) > + > + def to_string(self): > + buf = six.StringIO() > + buf.write(_make_header()) > + if self._conf: > + buf.write("\n") > + for key in sorted(self._conf.keys()): > + value = self._conf[key] > + if isinstance(value, bool): > + value = self._bool_map[value] > + if not isinstance(value, six.string_types): > + value = str(value) > + buf.write("%s=%s\n" % (key, _quote_value(value))) > + return buf.getvalue() > + > + > +class Route(ConfigMap): > + """Represents a route configuration.""" > + > + route_fn_tpl = '%(base)s/network-scripts/route-%(name)s' > + > + def __init__(self, route_name, base_sysconf_dir): > + super(Route, self).__init__() > + self.last_idx = 1 > + self.has_set_default = False > + self._route_name = route_name > + self._base_sysconf_dir = base_sysconf_dir > + > + def copy(self): > + r = Route(self._route_name, self._base_sysconf_dir) > + r._conf = self._conf.copy() > + r.last_idx = self.last_idx > + r.has_set_default = self.has_set_default > + return r > + > + @property > + def path(self): > + return self.route_fn_tpl % ({'base': self._base_sysconf_dir, > + 'name': self._route_name}) > + > + > +class NetInterface(ConfigMap): > + """Represents a sysconfig/networking-script (and its config + > children).""" > + > + iface_fn_tpl = '%(base)s/network-scripts/ifcfg-%(name)s' > + > + iface_types = { > + 'ethernet': 'Ethernet', > + 'bond': 'Bond', > + 'bridge': 'Bridge', > + } > + > + def __init__(self, iface_name, base_sysconf_dir, kind='ethernet'): > + super(NetInterface, self).__init__() > + self.children = [] > + self.routes = Route(iface_name, base_sysconf_dir) > + self._kind = kind > + self._iface_name = iface_name > + self._conf['DEVICE'] = iface_name > + self._conf['TYPE'] = self.iface_types[kind] > + self._base_sysconf_dir = base_sysconf_dir > + > + @property > + def name(self): > + return self._iface_name > + > + @name.setter > + def name(self, iface_name): > + self._iface_name = iface_name > + self._conf['DEVICE'] = iface_name > + > + @property > + def kind(self): > + return self._kind > + > + @kind.setter > + def kind(self, kind): > + self._kind = kind > + self._conf['TYPE'] = self.iface_types[kind] > + > + @property > + def path(self): > + return self.iface_fn_tpl % ({'base': self._base_sysconf_dir, > + 'name': self.name}) > + > + def copy(self, copy_children=False, copy_routes=False): > + c = NetInterface(self.name, self._base_sysconf_dir, kind=self._kind) > + c._conf = self._conf.copy() > + if copy_children: > + c.children = list(self.children) > + if copy_routes: > + c.routes = self.routes.copy() > + return c > + > + > +class Renderer(object): > + """Renders network information in a /etc/sysconfig format.""" > + > + # See: https://access.redhat.com/documentation/en-US/\ > + # Red_Hat_Enterprise_Linux/6/html/Deployment_Guide/\ > + # s1-networkscripts-interfaces.html (or other docs for > + # details about this) > + > + iface_defaults = tuple([ > + ('ONBOOT', True), > + ('USERCTL', False), > + ('NM_CONTROLLED', False), > + ('BOOTPROTO', 'none'), > + ]) > + > + # If these keys exist, then there values will be used to form > + # a BONDING_OPTS grouping; otherwise no grouping will be set. > + bond_tpl_opts = tuple([ > + ('bond_mode', "mode=%s"), > + ('bond_xmit_hash_policy', "xmit_hash_policy=%s"), > + ('bond_miimon', "miimon=%s"), > + ]) > + > + bridge_opts_keys = tuple([ > + ('bridge_stp', 'STP'), > + ('bridge_ageing', 'AGEING'), > + ('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() > + > + @classmethod > + def _render_iface_shared(cls, iface, iface_cfg): > + for k, v in cls.iface_defaults: > + iface_cfg[k] = v > + for (old_key, new_key) in [('mac_address', 'HWADDR'), ('mtu', > 'MTU')]: > + old_value = iface.get(old_key) > + if old_value is not None: > + iface_cfg[new_key] = old_value > + > + @classmethod > + def _render_subnet(cls, iface_cfg, route_cfg, subnet): > + subnet_type = subnet.get('type') > + if subnet_type == 'dhcp6': > + iface_cfg['DHCPV6C'] = True > + iface_cfg['IPV6INIT'] = True > + iface_cfg['BOOTPROTO'] = 'dhcp' > + elif subnet_type in ['dhcp4', 'dhcp']: > + iface_cfg['BOOTPROTO'] = 'dhcp' > + elif subnet_type == 'static': > + iface_cfg['BOOTPROTO'] = 'static' > + if subnet.get('ipv6'): > + iface_cfg['IPV6ADDR'] = subnet['address'] > + iface_cfg['IPV6INIT'] = True > + else: > + iface_cfg['IPADDR'] = subnet['address'] > + else: > + raise ValueError("Unknown subnet type '%s' found" > + " for interface '%s'" % (subnet_type, > + iface_cfg.name)) > + if 'netmask' in subnet: > + iface_cfg['NETMASK'] = subnet['netmask'] > + for route in subnet.get('routes', []): > + if _is_default_route(route): > + if route_cfg.has_set_default: > + raise ValueError("Duplicate declaration of default" > + " route found for interface '%s'" > + % (iface_cfg.name)) > + # NOTE(harlowja): ipv6 and ipv4 default gateways > + gw_key = 'GATEWAY0' > + nm_key = 'NETMASK0' > + addr_key = 'ADDRESS0' > + # The owning interface provides the default route. > + # > + # TODO(harlowja): add validation that no other iface has > + # also provided the default route? > + iface_cfg['DEFROUTE'] = True > + if 'gateway' in route: > + iface_cfg['GATEWAY'] = route['gateway'] > + route_cfg.has_set_default = True > + else: > + gw_key = 'GATEWAY%s' % route_cfg.last_idx > + nm_key = 'NETMASK%s' % route_cfg.last_idx > + addr_key = 'ADDRESS%s' % route_cfg.last_idx > + route_cfg.last_idx += 1 > + for (old_key, new_key) in [('gateway', gw_key), > + ('netmask', nm_key), > + ('network', addr_key)]: > + if old_key in route: > + route_cfg[new_key] = route[old_key] > + > + @classmethod > + def _render_bonding_opts(cls, iface_cfg, iface): > + bond_opts = [] > + for (bond_key, value_tpl) in cls.bond_tpl_opts: > + # Seems like either dash or underscore is possible? > + bond_keys = [bond_key, bond_key.replace("_", "-")] > + for bond_key in bond_keys: > + if bond_key in iface: > + bond_value = iface[bond_key] > + if isinstance(bond_value, (tuple, list)): > + bond_value = " ".join(bond_value) > + bond_opts.append(value_tpl % (bond_value)) > + break > + if bond_opts: > + iface_cfg['BONDING_OPTS'] = " ".join(bond_opts) > + > + @classmethod > + def _render_physical_interfaces(cls, network_state, iface_contents): > + for iface in network_state.iter_interfaces(_filter_by_physical): > + iface_name = iface['name'] > + iface_subnets = iface.get("subnets", []) > + iface_cfg = iface_contents[iface_name] > + route_cfg = iface_cfg.routes > + if len(iface_subnets) == 1: > + cls._render_subnet(iface_cfg, route_cfg, iface_subnets[0]) > + elif len(iface_subnets) > 1: > + for i, iface_subnet in enumerate(iface_subnets, > + start=len(iface.children)): > + iface_sub_cfg = iface_cfg.copy() > + iface_sub_cfg.name = "%s:%s" % (iface_name, i) > + iface.children.append(iface_sub_cfg) > + cls._render_subnet(iface_sub_cfg, route_cfg, > iface_subnet) > + > + @classmethod > + def _render_bond_interfaces(cls, network_state, iface_contents): > + for iface in network_state.iter_interfaces(_filter_by_type('bond')): > + iface_name = iface['name'] > + iface_cfg = iface_contents[iface_name] > + cls._render_bonding_opts(iface_cfg, iface) > + iface_master_name = iface['bond-master'] > + iface_cfg['MASTER'] = iface_master_name > + iface_cfg['SLAVE'] = True > + # Ensure that the master interface (and any of its children) > + # are actually marked as being bond types... > + master_cfg = iface_contents[iface_master_name] > + master_cfgs = [master_cfg] > + master_cfgs.extend(master_cfg.children) > + for master_cfg in master_cfgs: > + master_cfg['BONDING_MASTER'] = True > + master_cfg.kind = 'bond' > + > + @staticmethod > + def _render_vlan_interfaces(network_state, iface_contents): > + for iface in network_state.iter_interfaces(_filter_by_type('vlan')): > + iface_name = iface['name'] > + iface_cfg = iface_contents[iface_name] > + iface_cfg['VLAN'] = True > + iface_cfg['PHYSDEV'] = iface_name[:iface_name.rfind('.')] > + > + @staticmethod > + def _render_dns(network_state, existing_dns_path=None): > + 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) > + 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')): > + iface_name = iface['name'] > + iface_cfg = iface_contents[iface_name] > + iface_cfg.kind = 'bridge' > + for old_key, new_key in cls.bridge_opts_keys: > + if old_key in iface: > + iface_cfg[new_key] = iface[old_key] > + # Is this the right key to get all the connected interfaces? > + for bridged_iface_name in iface.get('bridge_ports', []): > + # Ensure all bridged interfaces are correctly tagged > + # as being bridged to this interface. > + bridged_cfg = iface_contents[bridged_iface_name] > + bridged_cfgs = [bridged_cfg] > + bridged_cfgs.extend(bridged_cfg.children) > + for bridge_cfg in bridged_cfgs: > + bridge_cfg['BRIDGE'] = iface_name > + > + @classmethod > + def _render_sysconfig(cls, base_sysconf_dir, network_state): > + '''Given state, return /etc/sysconfig files + contents''' > + iface_contents = {} > + for iface in network_state.iter_interfaces(): > + iface_name = iface['name'] > + iface_cfg = NetInterface(iface_name, base_sysconf_dir) > + cls._render_iface_shared(iface, iface_cfg) > + iface_contents[iface_name] = iface_cfg > + cls._render_physical_interfaces(network_state, iface_contents) > + cls._render_bond_interfaces(network_state, iface_contents) > + cls._render_vlan_interfaces(network_state, iface_contents) > + cls._render_bridge_interfaces(network_state, iface_contents) > + contents = {} > + for iface_name, iface_cfg in iface_contents.items(): > + if iface_cfg or iface_cfg.children: > + contents[iface_cfg.path] = iface_cfg.to_string() > + for iface_cfg in iface_cfg.children: > + if iface_cfg: > + contents[iface_cfg.path] = iface_cfg.to_string() > + if iface_cfg.routes: > + 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 > + 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) remove 'print' statements. replace with log at your discression. > + 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: > + 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() -- https://code.launchpad.net/~harlowja/cloud-init/cloud-init-net-sysconfig/+merge/297115 Your team cloud init development team is requested to review the proposed merge of lp:~harlowja/cloud-init/cloud-init-net-sysconfig into lp:cloud-init. _______________________________________________ Mailing list: https://launchpad.net/~cloud-init-dev Post to : cloud-init-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~cloud-init-dev More help : https://help.launchpad.net/ListHelp