From b4bd765983ffbfb52e8084a585b8f5e4b08b5203 Mon Sep 17 00:00:00 2001 From: Mike Mason Date: Thu, 31 Oct 2019 12:19:45 -0500 Subject: [PATCH 1/2] pin black version --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 6d023ac..23a2c38 100644 --- a/.drone.yml +++ b/.drone.yml @@ -9,7 +9,7 @@ pipeline: fmt_and_lint: image: python:3.6-alpine commands: - - pip install black pylama + - pip install black==19.3b0 pylama - black --check --diff . - pylama packetnetworking setup.py From c8665b78892fda98e82f76b054cd919439361158 Mon Sep 17 00:00:00 2001 From: Mike Mason Date: Thu, 31 Oct 2019 11:57:06 -0500 Subject: [PATCH 2/2] Changed to use template files Here I'm pulling the hard coded """{template}""" into actual template files. In doing so, I also brought common files that are shared between network configs such has hostname configuration and hosts files into the distro builder. By doing so, I also had to move the render and execution functions into the distro builder. This I think makes more sense, and allows for a better hierarchical structure to tasks. To better assist with adding tasks and using template files, a new parent class `Tasks` was created which allows a task to be set by calling `self.task(task, content, write_mode, mode, fmt)` which will build the structure accordingly. For a template file you can call `self.task_template(task, rel_path, write_mode, mode, fmt)`. `rel_path` is relative to the `templates_base` variable defined in the distro builder file. --- packetnetworking/cli.py | 4 +- packetnetworking/distros/debian/bonded.py | 91 +----- packetnetworking/distros/debian/builder.py | 9 + packetnetworking/distros/debian/conftest.py | 18 +- packetnetworking/distros/debian/individual.py | 65 +---- .../debian/templates/bonded/etc_modules.j2 | 1 + .../bonded/etc_network_interfaces.j2 | 54 ++++ .../distros/debian/templates/etc_hostname.j2 | 1 + .../distros/debian/templates/etc_hosts.j2 | 6 + .../debian/templates/etc_resolv.conf.j2 | 3 + .../individual/etc_network_interfaces.j2 | 34 +++ packetnetworking/distros/distro_builder.py | 165 ++++++++++- packetnetworking/distros/network_builder.py | 94 +------ packetnetworking/distros/redhat/bonded.py | 147 ++-------- packetnetworking/distros/redhat/builder.py | 9 + packetnetworking/distros/redhat/conftest.py | 18 +- packetnetworking/distros/redhat/individual.py | 104 ++----- .../bonded/etc_modprobe.d_bonding.conf.j2 | 2 + ...c_sysconfig_network-scripts_ifcfg-bond0.j2 | 25 ++ ...sysconfig_network-scripts_ifcfg-bond0_0.j2 | 11 + ...ysconfig_network-scripts_ifcfg-template.j2 | 6 + ...c_sysconfig_network-scripts_route-bond0.j2 | 3 + .../templates/bonded/etc_sysconfig_network.j2 | 9 + .../templates/bonded/sbin_ifup-pre-local.j2 | 12 + .../distros/redhat/templates/etc_hostname.j2 | 1 + .../distros/redhat/templates/etc_hosts.j2 | 2 + .../redhat/templates/etc_resolv.conf.j2 | 3 + ..._sysconfig_network-scripts_ifcfg-iface0.j2 | 23 ++ ...ysconfig_network-scripts_ifcfg-iface0_0.j2 | 11 + ..._sysconfig_network-scripts_route-iface0.j2 | 3 + .../individual/etc_sysconfig_network.j2 | 9 + packetnetworking/distros/suse/bonded.py | 75 +---- packetnetworking/distros/suse/builder.py | 9 + packetnetworking/distros/suse/conftest.py | 18 +- packetnetworking/distros/suse/individual.py | 61 +--- .../bonded/etc_modprobe.d_bonding.conf.j2 | 2 + .../etc_sysconfig_network_ifcfg-bond0.j2 | 14 + .../etc_sysconfig_network_ifcfg-template.j2 | 2 + .../bonded/etc_sysconfig_network_routes.j2 | 4 + .../distros/suse/templates/etc_hostname.j2 | 1 + .../distros/suse/templates/etc_hosts.j2 | 2 + .../distros/suse/templates/etc_resolv.conf.j2 | 3 + .../etc_sysconfig_network_ifcfg-iface0.j2 | 10 + .../etc_sysconfig_network_ifcfg-template.j2 | 2 + .../etc_sysconfig_network_routes.j2 | 4 + .../distros/test_distro_builder.py | 266 ++++++++++++++++-- .../distros/test_network_builder.py | 209 -------------- packetnetworking/utils.py | 25 +- setup.py | 19 ++ 49 files changed, 847 insertions(+), 822 deletions(-) create mode 100644 packetnetworking/distros/debian/templates/bonded/etc_modules.j2 create mode 100644 packetnetworking/distros/debian/templates/bonded/etc_network_interfaces.j2 create mode 100644 packetnetworking/distros/debian/templates/etc_hostname.j2 create mode 100644 packetnetworking/distros/debian/templates/etc_hosts.j2 create mode 100644 packetnetworking/distros/debian/templates/etc_resolv.conf.j2 create mode 100644 packetnetworking/distros/debian/templates/individual/etc_network_interfaces.j2 create mode 100644 packetnetworking/distros/redhat/templates/bonded/etc_modprobe.d_bonding.conf.j2 create mode 100644 packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_ifcfg-bond0.j2 create mode 100644 packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_ifcfg-bond0_0.j2 create mode 100644 packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_ifcfg-template.j2 create mode 100644 packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_route-bond0.j2 create mode 100644 packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network.j2 create mode 100644 packetnetworking/distros/redhat/templates/bonded/sbin_ifup-pre-local.j2 create mode 100644 packetnetworking/distros/redhat/templates/etc_hostname.j2 create mode 100644 packetnetworking/distros/redhat/templates/etc_hosts.j2 create mode 100644 packetnetworking/distros/redhat/templates/etc_resolv.conf.j2 create mode 100644 packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network-scripts_ifcfg-iface0.j2 create mode 100644 packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network-scripts_ifcfg-iface0_0.j2 create mode 100644 packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network-scripts_route-iface0.j2 create mode 100644 packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network.j2 create mode 100644 packetnetworking/distros/suse/templates/bonded/etc_modprobe.d_bonding.conf.j2 create mode 100644 packetnetworking/distros/suse/templates/bonded/etc_sysconfig_network_ifcfg-bond0.j2 create mode 100644 packetnetworking/distros/suse/templates/bonded/etc_sysconfig_network_ifcfg-template.j2 create mode 100644 packetnetworking/distros/suse/templates/bonded/etc_sysconfig_network_routes.j2 create mode 100644 packetnetworking/distros/suse/templates/etc_hostname.j2 create mode 100644 packetnetworking/distros/suse/templates/etc_hosts.j2 create mode 100644 packetnetworking/distros/suse/templates/etc_resolv.conf.j2 create mode 100644 packetnetworking/distros/suse/templates/individual/etc_sysconfig_network_ifcfg-iface0.j2 create mode 100644 packetnetworking/distros/suse/templates/individual/etc_sysconfig_network_ifcfg-template.j2 create mode 100644 packetnetworking/distros/suse/templates/individual/etc_sysconfig_network_routes.j2 delete mode 100644 packetnetworking/distros/test_network_builder.py diff --git a/packetnetworking/cli.py b/packetnetworking/cli.py index 9f51412..597c859 100644 --- a/packetnetworking/cli.py +++ b/packetnetworking/cli.py @@ -104,8 +104,8 @@ def cli( if resolvers: builder.network.resolvers = resolvers - complete = builder.run(rootfs) - if complete is None: + tasks = builder.run(rootfs) + if not tasks: if not quiet: click.echo("No tasks processed", file=sys.stderr) sys.exit(30) diff --git a/packetnetworking/distros/debian/bonded.py b/packetnetworking/distros/debian/bonded.py index 0a3f332..46cdc5e 100644 --- a/packetnetworking/distros/debian/bonded.py +++ b/packetnetworking/distros/debian/bonded.py @@ -10,96 +10,9 @@ def build(self): def build_tasks(self): self.tasks = {} - self.tasks[ - "etc/network/interfaces" - ] = """\ - auto lo - iface lo inet loopback - auto bond0 - iface bond0 inet static - {% if ip4pub %} - address {{ ip4pub.address }} - netmask {{ ip4pub.netmask }} - gateway {{ ip4pub.gateway }} - {% else %} - address {{ ip4priv.address }} - netmask {{ ip4priv.netmask }} - gateway {{ ip4priv.gateway }} - {% endif %} - bond-downdelay 200 - bond-miimon 100 - bond-mode {{ net.bonding.mode }} - bond-updelay 200 - bond-xmit_hash_policy layer3+4 - {% if osinfo.distro == 'ubuntu' and net.bonding.mode == 4 %} - bond-lacp-rate 1 - {% endif %} - bond-slaves{% for iface in interfaces %} {{ iface.name }}{% endfor %} - - dns-nameservers{% for dns in resolvers %} {{ dns }}{% endfor %} - - {% if ip6pub %} - iface bond0 inet6 static - address {{ ip6pub.address }} - netmask {{ ip6pub.cidr }} - gateway {{ ip6pub.gateway }} - {% endif %} - - {% if ip4pub %} - auto bond0:0 - iface bond0:0 inet static - address {{ ip4priv.address }} - netmask {{ ip4priv.netmask }} - {% for subnet in private_subnets %} - post-up route add -net {{ subnet }} gw {{ ip4priv.gateway }} - post-down route del -net {{ subnet }} gw {{ ip4priv.gateway }} - {% endfor %} - {% endif %} - {% if osinfo.distro == 'ubuntu' %} - {% for iface in interfaces %} - - auto {{ iface.name }} - iface {{ iface.name }} inet manual - {% if iface.name != interfaces[0].name %} - pre-up sleep 4 - {% endif %} - bond-master bond0 - {% endfor %} - {% endif %} - """ - - self.tasks["etc/modules"] = { - "file_mode": "a", - "template": """\ - bonding - """, - } - - self.tasks[ - "etc/resolv.conf" - ] = """\ - {% for server in resolvers %} - nameserver {{ server }} - {% endfor %} - """ - - self.tasks[ - "etc/hostname" - ] = """\ - {{ hostname }} - """ - - self.tasks[ - "etc/hosts" - ] = """\ - 127.0.0.1 localhost {{ hostname }} - - # The following lines are desirable for IPv6 capable hosts - ::1 localhost ip6-localhost ip6-loopback - ff02::1 ip6-allnodes - ff02::2 ip6-allrouters - """ + self.task_template("etc/network/interfaces", "bonded/etc_network_interfaces.j2") + self.task_template("etc/modules", "bonded/etc_modules.j2", write_mode="a") if self.metadata.operating_system.version == "14.04": self.tasks.update(generate_persistent_names()) diff --git a/packetnetworking/distros/debian/builder.py b/packetnetworking/distros/debian/builder.py index 0e41897..fff7aba 100644 --- a/packetnetworking/distros/debian/builder.py +++ b/packetnetworking/distros/debian/builder.py @@ -6,3 +6,12 @@ class DebianBuilder(DistroBuilder): distros = ["debian", "ubuntu"] network_builders = [DebianBondedNetwork, DebianIndividualNetwork] + + def build_tasks(self): + self.tasks = {} + + self.task_template("etc/hostname", "etc_hostname.j2") + self.task_template("etc/resolv.conf", "etc_resolv.conf.j2") + self.task_template("etc/hosts", "etc_hosts.j2") + + return self.tasks diff --git a/packetnetworking/distros/debian/conftest.py b/packetnetworking/distros/debian/conftest.py index 78c89a5..8304b81 100644 --- a/packetnetworking/distros/debian/conftest.py +++ b/packetnetworking/distros/debian/conftest.py @@ -44,9 +44,12 @@ def _builder(distro, version, public=True, metadata=None): ) builder = debianbuilder(metadata, public=public) builder.build() - for builder in builder.builders: - if isinstance(builder, DebianBondedNetwork): - return builder + builder.builders = [ + builder + for builder in builder.builders + if isinstance(builder, DebianBondedNetwork) + ] + return builder return _builder @@ -69,8 +72,11 @@ def _builder(distro, version, public=True, metadata=None): ) builder = debianbuilder(metadata, public=public) builder.build() - for builder in builder.builders: - if isinstance(builder, DebianIndividualNetwork): - return builder + builder.builders = [ + builder + for builder in builder.builders + if isinstance(builder, DebianIndividualNetwork) + ] + return builder return _builder diff --git a/packetnetworking/distros/debian/individual.py b/packetnetworking/distros/debian/individual.py index a00b1fa..7f03e2b 100644 --- a/packetnetworking/distros/debian/individual.py +++ b/packetnetworking/distros/debian/individual.py @@ -10,69 +10,10 @@ def build(self): def build_tasks(self): self.tasks = {} - self.tasks[ - "etc/network/interfaces" - ] = """\ - auto lo - iface lo inet loopback - auto {{ iface0.name }} - iface {{ iface0.name }} inet static - {% if ip4pub %} - address {{ ip4pub.address }} - netmask {{ ip4pub.netmask }} - gateway {{ ip4pub.gateway }} - {% else %} - address {{ ip4priv.address }} - netmask {{ ip4priv.netmask }} - gateway {{ ip4priv.gateway }} - {% endif %} - - dns-nameservers{% for dns in resolvers %} {{ dns }}{% endfor %} - - {% if ip6pub %} - iface {{ iface0.name }} inet6 static - address {{ ip6pub.address }} - netmask {{ ip6pub.cidr }} - gateway {{ ip6pub.gateway }} - {% endif %} - - {% if ip4pub %} - auto {{ iface0.name }}:0 - iface {{ iface0.name }}:0 inet static - address {{ ip4priv.address }} - netmask {{ ip4priv.netmask }} - {% for subnet in private_subnets %} - post-up route add -net {{ subnet }} gw {{ ip4priv.gateway }} - post-down route del -net {{ subnet }} gw {{ ip4priv.gateway }} - {% endfor %} - {% endif %} - """ - - self.tasks[ - "etc/resolv.conf" - ] = """\ - {% for server in resolvers %} - nameserver {{ server }} - {% endfor %} - """ - - self.tasks[ - "etc/hostname" - ] = """\ - {{ hostname }} - """ - - self.tasks[ - "etc/hosts" - ] = """\ - 127.0.0.1 localhost {{ hostname }} - - # The following lines are desirable for IPv6 capable hosts - ::1 localhost ip6-localhost ip6-loopback - ff02::1 ip6-allnodes - ff02::2 ip6-allrouters - """ + self.task_template( + "etc/network/interfaces", "individual/etc_network_interfaces.j2" + ) if self.metadata.operating_system.version == "14.04": self.tasks.update(generate_persistent_names()) diff --git a/packetnetworking/distros/debian/templates/bonded/etc_modules.j2 b/packetnetworking/distros/debian/templates/bonded/etc_modules.j2 new file mode 100644 index 0000000..29a76c4 --- /dev/null +++ b/packetnetworking/distros/debian/templates/bonded/etc_modules.j2 @@ -0,0 +1 @@ +bonding diff --git a/packetnetworking/distros/debian/templates/bonded/etc_network_interfaces.j2 b/packetnetworking/distros/debian/templates/bonded/etc_network_interfaces.j2 new file mode 100644 index 0000000..f617edc --- /dev/null +++ b/packetnetworking/distros/debian/templates/bonded/etc_network_interfaces.j2 @@ -0,0 +1,54 @@ +auto lo +iface lo inet loopback + +auto bond0 +iface bond0 inet static + {% if ip4pub %} + address {{ ip4pub.address }} + netmask {{ ip4pub.netmask }} + gateway {{ ip4pub.gateway }} + {% else %} + address {{ ip4priv.address }} + netmask {{ ip4priv.netmask }} + gateway {{ ip4priv.gateway }} + {% endif %} + bond-downdelay 200 + bond-miimon 100 + bond-mode {{ net.bonding.mode }} + bond-updelay 200 + bond-xmit_hash_policy layer3+4 + {% if osinfo.distro == 'ubuntu' and net.bonding.mode == 4 %} + bond-lacp-rate 1 + {% endif %} + bond-slaves{% for iface in interfaces %} {{ iface.name }}{% endfor %} + + dns-nameservers{% for dns in resolvers %} {{ dns }}{% endfor %} + +{% if ip6pub %} +iface bond0 inet6 static + address {{ ip6pub.address }} + netmask {{ ip6pub.cidr }} + gateway {{ ip6pub.gateway }} +{% endif %} + +{% if ip4pub %} +auto bond0:0 +iface bond0:0 inet static + address {{ ip4priv.address }} + netmask {{ ip4priv.netmask }} + {% for subnet in private_subnets %} + post-up route add -net {{ subnet }} gw {{ ip4priv.gateway }} + post-down route del -net {{ subnet }} gw {{ ip4priv.gateway }} + {% endfor %} +{% endif %} +{% if osinfo.distro == 'ubuntu' %} +{% for iface in interfaces %} + +auto {{ iface.name }} +iface {{ iface.name }} inet manual +{% if iface.name != interfaces[0].name %} + pre-up sleep 4 +{% endif %} + bond-master bond0 +{% endfor %} +{% endif %} diff --git a/packetnetworking/distros/debian/templates/etc_hostname.j2 b/packetnetworking/distros/debian/templates/etc_hostname.j2 new file mode 100644 index 0000000..56baac7 --- /dev/null +++ b/packetnetworking/distros/debian/templates/etc_hostname.j2 @@ -0,0 +1 @@ +{{ hostname }} diff --git a/packetnetworking/distros/debian/templates/etc_hosts.j2 b/packetnetworking/distros/debian/templates/etc_hosts.j2 new file mode 100644 index 0000000..7a263ce --- /dev/null +++ b/packetnetworking/distros/debian/templates/etc_hosts.j2 @@ -0,0 +1,6 @@ +127.0.0.1 localhost {{ hostname }} + +# The following lines are desirable for IPv6 capable hosts +::1 localhost ip6-localhost ip6-loopback +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters diff --git a/packetnetworking/distros/debian/templates/etc_resolv.conf.j2 b/packetnetworking/distros/debian/templates/etc_resolv.conf.j2 new file mode 100644 index 0000000..a3a2c65 --- /dev/null +++ b/packetnetworking/distros/debian/templates/etc_resolv.conf.j2 @@ -0,0 +1,3 @@ +{% for server in resolvers %} +nameserver {{ server }} +{% endfor %} diff --git a/packetnetworking/distros/debian/templates/individual/etc_network_interfaces.j2 b/packetnetworking/distros/debian/templates/individual/etc_network_interfaces.j2 new file mode 100644 index 0000000..f4a9eb1 --- /dev/null +++ b/packetnetworking/distros/debian/templates/individual/etc_network_interfaces.j2 @@ -0,0 +1,34 @@ +auto lo +iface lo inet loopback + +auto {{ iface0.name }} +iface {{ iface0.name }} inet static + {% if ip4pub %} + address {{ ip4pub.address }} + netmask {{ ip4pub.netmask }} + gateway {{ ip4pub.gateway }} + {% else %} + address {{ ip4priv.address }} + netmask {{ ip4priv.netmask }} + gateway {{ ip4priv.gateway }} + {% endif %} + + dns-nameservers{% for dns in resolvers %} {{ dns }}{% endfor %} + +{% if ip6pub %} +iface {{ iface0.name }} inet6 static + address {{ ip6pub.address }} + netmask {{ ip6pub.cidr }} + gateway {{ ip6pub.gateway }} +{% endif %} + +{% if ip4pub %} +auto {{ iface0.name }}:0 +iface {{ iface0.name }}:0 inet static + address {{ ip4priv.address }} + netmask {{ ip4priv.netmask }} + {% for subnet in private_subnets %} + post-up route add -net {{ subnet }} gw {{ ip4priv.gateway }} + post-down route del -net {{ subnet }} gw {{ ip4priv.gateway }} + {% endfor %} +{% endif %} diff --git a/packetnetworking/distros/distro_builder.py b/packetnetworking/distros/distro_builder.py index 612b1ec..18148ce 100644 --- a/packetnetworking/distros/distro_builder.py +++ b/packetnetworking/distros/distro_builder.py @@ -1,27 +1,176 @@ -class DistroBuilder: +import os +import sys +import logging +from textwrap import dedent + +from jinja2 import Template, StrictUndefined + +from ..utils import Tasks, package_dir + +log = logging.getLogger() + + +def get_templates_dir(instance): + instance_dir = os.path.abspath( + os.path.dirname(sys.modules[instance.__class__.__module__].__file__) + ) + return os.path.join(os.path.relpath(instance_dir, package_dir), "templates") + + +class DistroBuilder(Tasks): distros = None network_builders = [] def __init__(self, metadata): + self.templates_base = get_templates_dir(self) self.metadata = metadata + self.network = self.metadata.network self.builders = [] + self.tasks = None + + @property + def ipv4pub(self): + return self.network.addresses.management.public.ipv4 + + @property + def ipv6pub(self): + return self.network.addresses.management.public.ipv6 + + @property + def ipv4priv(self): + return self.network.addresses.management.private.ipv4 def build(self): + """ + Build triggers all build functions to build the list of tasks needing + to be applied. + """ + self.build_tasks() for NetworkBuilder in self.network_builders: builder = NetworkBuilder(self.metadata) + if not hasattr(builder, "templates_base"): + builder.templates_base = self.templates_base self.builders.append(builder) builder.build() return True - def run(self, rootfs_path): + def build_tasks(self): + pass + + def context(self): + return { + "hostname": self.metadata.hostname, + "interfaces": self.network.interfaces, + "iface0": self.network.interfaces[0], + "ip4priv": self.ipv4priv.first, + "ip4pub": self.ipv4pub.first, + "ip6pub": self.ipv6pub.first, + "net": self.network, + "osinfo": self.metadata.operating_system, + "resolvers": self.network.resolvers, + "private_subnets": self.network.private_subnets, + } + + @property + def all_tasks(self): + """ + all_tasks combines DistroBuilder tasks and all NetworkBuilder tasks + into a single dictionary used by the `DistroBuilder.render` function. + """ + tasks = {} + if self.tasks: + tasks.update(self.tasks) if self.builders: - tasks_found = False for builder in self.builders: - if builder.run(rootfs_path): - tasks_found = True - if not tasks_found: - return None - return True + if builder.tasks: + tasks.update(builder.tasks) + return tasks + + def render(self): + """ + Render compiles each task template into the final content. + """ + if self.tasks is None: + self.build() + rendered_tasks = {} + if not self.all_tasks: + return {} + for path, template in self.all_tasks.items(): + log.debug("Rendering task: '{}'".format(path)) + if template is None: + rendered_tasks[path] = template + continue + + file_mode = None + mode = None + if isinstance(template, dict): + file_mode = template.get("file_mode") + mode = template.get("mode", None) + fmt = template.get("fmt") + template_path = template.get("template_path") + template = template.get("template") + if template_path is not None: + with open(template_path, "r") as f: + template = f.read() + if fmt: + template = template.format(**fmt) + + template = Template( + dedent(template), + keep_trailing_newline=True, + lstrip_blocks=True, + trim_blocks=True, + undefined=StrictUndefined, + ) + if file_mode or mode: + rendered_tasks[path] = { + "file_mode": file_mode, + "mode": mode, + "content": template.render(self.context()), + } + else: + rendered_tasks[path] = template.render(self.context()) + return rendered_tasks + + def run(self, rootfs_path): + """ + Run processes the rendered tasks and writes them to the filesystem. + """ + rendered_tasks = self.render() + if not rendered_tasks: + return {} + for relpath, content in rendered_tasks.items(): + log.debug("Processing task: '{}'".format(relpath)) + abspath = os.path.join(rootfs_path, relpath) + if content is None: + if os.path.lexists(abspath): + log.info("Removing '{}'".format(abspath)) + os.remove(abspath) + else: + log.debug( + "Skipped removing '{}' Path doesn't exist".format(abspath) + ) + continue + + file_mode = "w" + mode = None + if isinstance(content, dict): + file_mode = content.get("file_mode") or file_mode + mode = content.get("mode", None) + content = content.get("content") + + dirname = os.path.dirname(abspath) + if dirname and not os.path.lexists(dirname): + log.debug("Making directory '{}'".format(dirname)) + os.makedirs(dirname, exist_ok=True) + + log.debug("Writing content to '{}'".format(abspath)) + with open(abspath, file_mode) as f: + f.write(content) + + if mode: + os.chmod(abspath, mode) + return rendered_tasks def get_distro_builder(distro): diff --git a/packetnetworking/distros/network_builder.py b/packetnetworking/distros/network_builder.py index ce658ad..7236e7d 100644 --- a/packetnetworking/distros/network_builder.py +++ b/packetnetworking/distros/network_builder.py @@ -1,12 +1,7 @@ -import os -import logging -from textwrap import dedent -from jinja2 import Template, StrictUndefined +from ..utils import Tasks -log = logging.getLogger() - -class NetworkBuilder: +class NetworkBuilder(Tasks): def __init__(self, metadata): self.metadata = metadata self.network = self.metadata.network @@ -23,88 +18,3 @@ def ipv6pub(self): @property def ipv4priv(self): return self.network.addresses.management.private.ipv4 - - def context(self): - return { - "hostname": self.metadata.hostname, - "interfaces": self.network.interfaces, - "iface0": self.network.interfaces[0], - "ip4priv": self.ipv4priv.first, - "ip4pub": self.ipv4pub.first, - "ip6pub": self.ipv6pub.first, - "net": self.network, - "osinfo": self.metadata.operating_system, - "resolvers": self.network.resolvers, - "private_subnets": self.network.private_subnets, - } - - def render(self): - if self.tasks is None: - self.build() - rendered_tasks = {} - if not self.tasks: - return rendered_tasks - for path, template in self.tasks.items(): - log.debug("Rendering task: '{}'".format(path)) - if template is None: - rendered_tasks[path] = template - continue - - file_mode = None - mode = None - if isinstance(template, dict): - file_mode = template.get("file_mode") - mode = template.get("mode", None) - template = template.get("template") - - template = Template( - dedent(template), - keep_trailing_newline=True, - lstrip_blocks=True, - trim_blocks=True, - undefined=StrictUndefined, - ) - if file_mode or mode: - rendered_tasks[path] = { - "file_mode": file_mode, - "mode": mode, - "content": template.render(self.context()), - } - else: - rendered_tasks[path] = template.render(self.context()) - return rendered_tasks - - def run(self, rootfs_path): - rendered_tasks = self.render() - for relpath, content in rendered_tasks.items(): - log.debug("Processing task: '{}'".format(relpath)) - abspath = os.path.join(rootfs_path, relpath) - if content is None: - if os.path.lexists(abspath): - log.info("Removing '{}'".format(abspath)) - os.remove(abspath) - else: - log.debug( - "Skipped removing '{}' Path doesn't exist".format(abspath) - ) - continue - - file_mode = "w" - mode = None - if isinstance(content, dict): - file_mode = content.get("file_mode") or file_mode - mode = content.get("mode", None) - content = content.get("content") - - name_dir = os.path.dirname(abspath) - if name_dir and not os.path.lexists(name_dir): - log.debug("Making directory '{}'".format(name_dir)) - os.makedirs(name_dir, exist_ok=True) - - log.debug("Writing content to '{}'".format(abspath)) - with open(abspath, file_mode) as f: - f.write(content) - - if mode: - os.chmod(abspath, mode) - return rendered_tasks diff --git a/packetnetworking/distros/redhat/bonded.py b/packetnetworking/distros/redhat/bonded.py index b47fd0b..22cc073 100644 --- a/packetnetworking/distros/redhat/bonded.py +++ b/packetnetworking/distros/redhat/bonded.py @@ -11,135 +11,40 @@ def build(self): def build_tasks(self): self.tasks = {} - self.tasks[ - "etc/sysconfig/network" - ] = """\ - NETWORKING=yes - HOSTNAME={{ hostname }} - {% if ip4pub %} - GATEWAY={{ ip4pub.gateway }} - {% else %} - GATEWAY={{ ip4priv.gateway }} - {% endif %} - GATEWAYDEV=bond0 - NOZEROCONF=yes - """ - self.tasks[ - "etc/modprobe.d/bonding.conf" - ] = """\ - alias bond0 bonding - options bond0 mode={{ net.bonding.mode }} miimon=100 downdelay=200 updelay=200 xmit_hash_policy=layer3+4 lacp_rate=1 - """ - - self.tasks[ - "etc/sysconfig/network-scripts/ifcfg-bond0" - ] = """\ - DEVICE=bond0 - NAME=bond0 - {% if ip4pub %} - IPADDR={{ ip4pub.address }} - NETMASK={{ ip4pub.netmask }} - GATEWAY={{ ip4pub.gateway }} - {% else %} - IPADDR={{ ip4priv.address }} - NETMASK={{ ip4priv.netmask }} - GATEWAY={{ ip4priv.gateway }} - {% endif %} - BOOTPROTO=none - ONBOOT=yes - USERCTL=no - TYPE=Bond - BONDING_OPTS="mode={{ net.bonding.mode }} miimon=100 downdelay=200 updelay=200" - - {% if ip6pub %} - IPV6INIT=yes - IPV6ADDR={{ ip6pub.address }}/{{ ip6pub.cidr }} - IPV6_DEFAULTGW={{ ip6pub.gateway }} - {% endif %} - {% for dns in resolvers %} - DNS{{ loop.index }}={{ dns }} - {% endfor %} - """ + self.task_template("etc/sysconfig/network", "bonded/etc_sysconfig_network.j2") + self.task_template( + "etc/modprobe.d/bonding.conf", "bonded/etc_modprobe.d_bonding.conf.j2" + ) + self.task_template( + "etc/sysconfig/network-scripts/ifcfg-bond0", + "bonded/etc_sysconfig_network-scripts_ifcfg-bond0.j2", + ) if self.ipv4pub: - self.tasks[ - "etc/sysconfig/network-scripts/ifcfg-bond0:0" - ] = """\ - DEVICE=bond0:0 - NAME=bond0:0 - IPADDR={{ ip4priv.address }} - NETMASK={{ ip4priv.netmask }} - GATEWAY={{ ip4priv.gateway }} - BOOTPROTO=none - ONBOOT=yes - USERCTL=no - {% for dns in resolvers %} - DNS{{ loop.index }}={{ dns }} - {% endfor %} - """ - - # If no ip4pub is specified, the ip4priv is configured on the bond0 interface - # so there is no need to add the custom route - self.tasks[ - "etc/sysconfig/network-scripts/route-bond0" - ] = """\ - {% for subnet in private_subnets %} - {{ subnet }} via {{ ip4priv.gateway }} dev bond0:0 - {% endfor %} - """ + # Only needed when a public ip is used, otherwise private ip is + # already set and no special routes are needed. + self.task_template( + "etc/sysconfig/network-scripts/ifcfg-bond0:0", + "bonded/etc_sysconfig_network-scripts_ifcfg-bond0_0.j2", + ) + self.task_template( + "etc/sysconfig/network-scripts/route-bond0", + "bonded/etc_sysconfig_network-scripts_route-bond0.j2", + ) - ifcfg = """\ - DEVICE={iface} - ONBOOT=yes - HWADDR={{{{ interfaces[{i}].mac }}}} - MASTER=bond0 - SLAVE=yes - BOOTPROTO=none - """ for i in range(len(self.network.interfaces)): name = self.network.interfaces[i]["name"] - cfg = ifcfg.format(iface=name, i=i) - self.tasks["etc/sysconfig/network-scripts/ifcfg-" + name] = cfg - - self.tasks[ - "etc/resolv.conf" - ] = """\ - {% for server in resolvers %} - nameserver {{ server }} - {% endfor %} - """ - - self.tasks[ - "etc/hostname" - ] = """\ - {{ hostname }} - """ - - self.tasks["etc/hosts"] = ( - "127.0.0.1 localhost localhost.localdomain localhost4 " - + "localhost4.localdomain4\n" - + "::1 localhost localhost.localdomain localhost6 " - + "localhost6.localdomain6\n" + self.task_template( + "etc/sysconfig/network-scripts/ifcfg-" + name, + "bonded/etc_sysconfig_network-scripts_ifcfg-template.j2", + fmt={"iface": name, "i": i}, + ) + + self.task_template( + "sbin/ifup-pre-local", "bonded/sbin_ifup-pre-local.j2", mode=0o755 ) - ifup_pre_local = """\ - #!/bin/bash - - set -o errexit -o nounset -o pipefail -o xtrace - - iface=${1#*-} - case "$iface" in - bond0 | {{interfaces[0].name}}) ip link set "$iface" address {{interfaces[0].mac}} ;; - {% for iface in interfaces[1:] %} - {{iface.name}}) ip link set "$iface" address {{iface.mac}} && sleep 4 ;; - {% endfor %} - *) echo "ignoring unknown interface $iface" && exit 0 ;; - esac - """ # noqa - - self.tasks["sbin/ifup-pre-local"] = {"template": ifup_pre_local, "mode": 0o755} - if self.metadata.operating_system.distro not in ( "scientificcernslc", "redhatenterpriseserver", diff --git a/packetnetworking/distros/redhat/builder.py b/packetnetworking/distros/redhat/builder.py index 170651e..1b69fad 100644 --- a/packetnetworking/distros/redhat/builder.py +++ b/packetnetworking/distros/redhat/builder.py @@ -11,3 +11,12 @@ class RedhatBuilder(DistroBuilder): "scientificcernslc", ] network_builders = [RedhatBondedNetwork, RedhatIndividualNetwork] + + def build_tasks(self): + self.tasks = {} + + self.task_template("etc/hostname", "etc_hostname.j2") + self.task_template("etc/resolv.conf", "etc_resolv.conf.j2") + self.task_template("etc/hosts", "etc_hosts.j2") + + return self.tasks diff --git a/packetnetworking/distros/redhat/conftest.py b/packetnetworking/distros/redhat/conftest.py index ccf4b36..abf4f03 100644 --- a/packetnetworking/distros/redhat/conftest.py +++ b/packetnetworking/distros/redhat/conftest.py @@ -44,9 +44,12 @@ def _builder(distro, version, public=True, metadata=None): ) builder = redhatbuilder(metadata, public=public) builder.build() - for builder in builder.builders: - if isinstance(builder, RedhatBondedNetwork): - return builder + builder.builders = [ + builder + for builder in builder.builders + if isinstance(builder, RedhatBondedNetwork) + ] + return builder return _builder @@ -69,8 +72,11 @@ def _builder(distro, version, public=True, metadata=None): ) builder = redhatbuilder(metadata, public=public) builder.build() - for builder in builder.builders: - if isinstance(builder, RedhatIndividualNetwork): - return builder + builder.builders = [ + builder + for builder in builder.builders + if isinstance(builder, RedhatIndividualNetwork) + ] + return builder return _builder diff --git a/packetnetworking/distros/redhat/individual.py b/packetnetworking/distros/redhat/individual.py index 91396c1..82b05dd 100644 --- a/packetnetworking/distros/redhat/individual.py +++ b/packetnetworking/distros/redhat/individual.py @@ -14,99 +14,29 @@ def build_tasks(self): iface0 = self.network.interfaces[0] - self.tasks[ - "etc/sysconfig/network" - ] = """\ - NETWORKING=yes - HOSTNAME={{ hostname }} - {% if ip4pub %} - GATEWAY={{ ip4pub.gateway }} - {% else %} - GATEWAY={{ ip4priv.gateway }} - {% endif %} - GATEWAYDEV={{ iface0.name }} - NOZEROCONF=yes - """ - - self.tasks[ - "etc/sysconfig/network-scripts/ifcfg-{iface0.name}".format(iface0=iface0) - ] = """\ - DEVICE={{ iface0.name }} - NAME={{ iface0.name }} - {% if ip4pub %} - IPADDR={{ ip4pub.address }} - NETMASK={{ ip4pub.netmask }} - GATEWAY={{ ip4pub.gateway }} - {% else %} - IPADDR={{ ip4priv.address }} - NETMASK={{ ip4priv.netmask }} - GATEWAY={{ ip4priv.gateway }} - {% endif %} - BOOTPROTO=none - ONBOOT=yes - USERCTL=no - - {% if ip6pub %} - IPV6INIT=yes - IPV6ADDR={{ ip6pub.address }}/{{ ip6pub.cidr }} - IPV6_DEFAULTGW={{ ip6pub.gateway }} - {% endif %} - {% for dns in resolvers %} - DNS{{ loop.index }}={{ dns }} - {% endfor %} - """ + self.task_template( + "etc/sysconfig/network", "individual/etc_sysconfig_network.j2" + ) + self.task_template( + "etc/sysconfig/network-scripts/ifcfg-{iface0.name}".format(iface0=iface0), + "individual/etc_sysconfig_network-scripts_ifcfg-iface0.j2", + ) if self.ipv4pub: - self.tasks[ + # Only needed when a public ip is used, otherwise private ip is + # already set and no special routes are needed. + self.task_template( "etc/sysconfig/network-scripts/ifcfg-{iface0.name}:0".format( iface0=iface0 - ) - ] = """\ - DEVICE={{ iface0.name }}:0 - NAME={{ iface0.name }}:0 - IPADDR={{ ip4priv.address }} - NETMASK={{ ip4priv.netmask }} - GATEWAY={{ ip4priv.gateway }} - BOOTPROTO=none - ONBOOT=yes - USERCTL=no - {% for dns in resolvers %} - DNS{{ loop.index }}={{ dns }} - {% endfor %} - """ - - # If no ip4pub is specified, the ip4priv is configured on the eth0 interface - # so there is no need to add the custom route - self.tasks[ + ), + "individual/etc_sysconfig_network-scripts_ifcfg-iface0_0.j2", + ) + self.task_template( "etc/sysconfig/network-scripts/route-{iface0.name}".format( iface0=iface0 - ) - ] = """\ - {% for subnet in private_subnets %} - {{ subnet }} via {{ ip4priv.gateway }} dev {{ iface0.name }}:0 - {% endfor %} - """ - - self.tasks[ - "etc/resolv.conf" - ] = """\ - {% for server in resolvers %} - nameserver {{ server }} - {% endfor %} - """ - - self.tasks[ - "etc/hostname" - ] = """\ - {{ hostname }} - """ - - self.tasks["etc/hosts"] = ( - "127.0.0.1 localhost localhost.localdomain localhost4 " - + "localhost4.localdomain4\n" - + "::1 localhost localhost.localdomain localhost6 " - + "localhost6.localdomain6\n" - ) + ), + "individual/etc_sysconfig_network-scripts_route-iface0.j2", + ) if self.metadata.operating_system.distro not in ( "scientificcernslc", diff --git a/packetnetworking/distros/redhat/templates/bonded/etc_modprobe.d_bonding.conf.j2 b/packetnetworking/distros/redhat/templates/bonded/etc_modprobe.d_bonding.conf.j2 new file mode 100644 index 0000000..fecbe38 --- /dev/null +++ b/packetnetworking/distros/redhat/templates/bonded/etc_modprobe.d_bonding.conf.j2 @@ -0,0 +1,2 @@ +alias bond0 bonding +options bond0 mode={{ net.bonding.mode }} miimon=100 downdelay=200 updelay=200 xmit_hash_policy=layer3+4 lacp_rate=1 diff --git a/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_ifcfg-bond0.j2 b/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_ifcfg-bond0.j2 new file mode 100644 index 0000000..cf96fb2 --- /dev/null +++ b/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_ifcfg-bond0.j2 @@ -0,0 +1,25 @@ +DEVICE=bond0 +NAME=bond0 +{% if ip4pub %} +IPADDR={{ ip4pub.address }} +NETMASK={{ ip4pub.netmask }} +GATEWAY={{ ip4pub.gateway }} +{% else %} +IPADDR={{ ip4priv.address }} +NETMASK={{ ip4priv.netmask }} +GATEWAY={{ ip4priv.gateway }} +{% endif %} +BOOTPROTO=none +ONBOOT=yes +USERCTL=no +TYPE=Bond +BONDING_OPTS="mode={{ net.bonding.mode }} miimon=100 downdelay=200 updelay=200" + +{% if ip6pub %} +IPV6INIT=yes +IPV6ADDR={{ ip6pub.address }}/{{ ip6pub.cidr }} +IPV6_DEFAULTGW={{ ip6pub.gateway }} +{% endif %} +{% for dns in resolvers %} +DNS{{ loop.index }}={{ dns }} +{% endfor %} diff --git a/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_ifcfg-bond0_0.j2 b/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_ifcfg-bond0_0.j2 new file mode 100644 index 0000000..d588cc0 --- /dev/null +++ b/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_ifcfg-bond0_0.j2 @@ -0,0 +1,11 @@ +DEVICE=bond0:0 +NAME=bond0:0 +IPADDR={{ ip4priv.address }} +NETMASK={{ ip4priv.netmask }} +GATEWAY={{ ip4priv.gateway }} +BOOTPROTO=none +ONBOOT=yes +USERCTL=no +{% for dns in resolvers %} +DNS{{ loop.index }}={{ dns }} +{% endfor %} diff --git a/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_ifcfg-template.j2 b/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_ifcfg-template.j2 new file mode 100644 index 0000000..2f265b9 --- /dev/null +++ b/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_ifcfg-template.j2 @@ -0,0 +1,6 @@ +DEVICE={iface} +ONBOOT=yes +HWADDR={{{{ interfaces[{i}].mac }}}} +MASTER=bond0 +SLAVE=yes +BOOTPROTO=none diff --git a/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_route-bond0.j2 b/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_route-bond0.j2 new file mode 100644 index 0000000..21ce10f --- /dev/null +++ b/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network-scripts_route-bond0.j2 @@ -0,0 +1,3 @@ +{% for subnet in private_subnets %} +{{ subnet }} via {{ ip4priv.gateway }} dev bond0:0 +{% endfor %} diff --git a/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network.j2 b/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network.j2 new file mode 100644 index 0000000..c4b60fa --- /dev/null +++ b/packetnetworking/distros/redhat/templates/bonded/etc_sysconfig_network.j2 @@ -0,0 +1,9 @@ +NETWORKING=yes +HOSTNAME={{ hostname }} +{% if ip4pub %} +GATEWAY={{ ip4pub.gateway }} +{% else %} +GATEWAY={{ ip4priv.gateway }} +{% endif %} +GATEWAYDEV=bond0 +NOZEROCONF=yes diff --git a/packetnetworking/distros/redhat/templates/bonded/sbin_ifup-pre-local.j2 b/packetnetworking/distros/redhat/templates/bonded/sbin_ifup-pre-local.j2 new file mode 100644 index 0000000..7c55056 --- /dev/null +++ b/packetnetworking/distros/redhat/templates/bonded/sbin_ifup-pre-local.j2 @@ -0,0 +1,12 @@ +#!/bin/bash + +set -o errexit -o nounset -o pipefail -o xtrace + +iface=${1#*-} +case "$iface" in +bond0 | {{interfaces[0].name}}) ip link set "$iface" address {{interfaces[0].mac}} ;; +{% for iface in interfaces[1:] %} + {{iface.name}}) ip link set "$iface" address {{iface.mac}} && sleep 4 ;; +{% endfor %} +*) echo "ignoring unknown interface $iface" && exit 0 ;; +esac diff --git a/packetnetworking/distros/redhat/templates/etc_hostname.j2 b/packetnetworking/distros/redhat/templates/etc_hostname.j2 new file mode 100644 index 0000000..56baac7 --- /dev/null +++ b/packetnetworking/distros/redhat/templates/etc_hostname.j2 @@ -0,0 +1 @@ +{{ hostname }} diff --git a/packetnetworking/distros/redhat/templates/etc_hosts.j2 b/packetnetworking/distros/redhat/templates/etc_hosts.j2 new file mode 100644 index 0000000..849c10d --- /dev/null +++ b/packetnetworking/distros/redhat/templates/etc_hosts.j2 @@ -0,0 +1,2 @@ +127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 +::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 diff --git a/packetnetworking/distros/redhat/templates/etc_resolv.conf.j2 b/packetnetworking/distros/redhat/templates/etc_resolv.conf.j2 new file mode 100644 index 0000000..a3a2c65 --- /dev/null +++ b/packetnetworking/distros/redhat/templates/etc_resolv.conf.j2 @@ -0,0 +1,3 @@ +{% for server in resolvers %} +nameserver {{ server }} +{% endfor %} diff --git a/packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network-scripts_ifcfg-iface0.j2 b/packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network-scripts_ifcfg-iface0.j2 new file mode 100644 index 0000000..c562ddc --- /dev/null +++ b/packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network-scripts_ifcfg-iface0.j2 @@ -0,0 +1,23 @@ +DEVICE={{ iface0.name }} +NAME={{ iface0.name }} +{% if ip4pub %} +IPADDR={{ ip4pub.address }} +NETMASK={{ ip4pub.netmask }} +GATEWAY={{ ip4pub.gateway }} +{% else %} +IPADDR={{ ip4priv.address }} +NETMASK={{ ip4priv.netmask }} +GATEWAY={{ ip4priv.gateway }} +{% endif %} +BOOTPROTO=none +ONBOOT=yes +USERCTL=no + +{% if ip6pub %} +IPV6INIT=yes +IPV6ADDR={{ ip6pub.address }}/{{ ip6pub.cidr }} +IPV6_DEFAULTGW={{ ip6pub.gateway }} +{% endif %} +{% for dns in resolvers %} +DNS{{ loop.index }}={{ dns }} +{% endfor %} diff --git a/packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network-scripts_ifcfg-iface0_0.j2 b/packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network-scripts_ifcfg-iface0_0.j2 new file mode 100644 index 0000000..52eb87c --- /dev/null +++ b/packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network-scripts_ifcfg-iface0_0.j2 @@ -0,0 +1,11 @@ +DEVICE={{ iface0.name }}:0 +NAME={{ iface0.name }}:0 +IPADDR={{ ip4priv.address }} +NETMASK={{ ip4priv.netmask }} +GATEWAY={{ ip4priv.gateway }} +BOOTPROTO=none +ONBOOT=yes +USERCTL=no +{% for dns in resolvers %} +DNS{{ loop.index }}={{ dns }} +{% endfor %} diff --git a/packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network-scripts_route-iface0.j2 b/packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network-scripts_route-iface0.j2 new file mode 100644 index 0000000..2e95451 --- /dev/null +++ b/packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network-scripts_route-iface0.j2 @@ -0,0 +1,3 @@ +{% for subnet in private_subnets %} +{{ subnet }} via {{ ip4priv.gateway }} dev {{ iface0.name }}:0 +{% endfor %} diff --git a/packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network.j2 b/packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network.j2 new file mode 100644 index 0000000..a51a0b2 --- /dev/null +++ b/packetnetworking/distros/redhat/templates/individual/etc_sysconfig_network.j2 @@ -0,0 +1,9 @@ +NETWORKING=yes +HOSTNAME={{ hostname }} +{% if ip4pub %} +GATEWAY={{ ip4pub.gateway }} +{% else %} +GATEWAY={{ ip4priv.gateway }} +{% endif %} +GATEWAYDEV={{ iface0.name }} +NOZEROCONF=yes diff --git a/packetnetworking/distros/suse/bonded.py b/packetnetworking/distros/suse/bonded.py index 84a96e2..8cf615e 100644 --- a/packetnetworking/distros/suse/bonded.py +++ b/packetnetworking/distros/suse/bonded.py @@ -10,68 +10,23 @@ def build(self): def build_tasks(self): self.tasks = {} - self.tasks[ - "etc/modprobe.d/bonding.conf" - ] = """\ - alias bond0 bonding - options bond0 mode={{ net.bonding.mode }} miimon=100 downdelay=200 updelay=200 xmit_hash_policy=layer3+4 lacp_rate=1 - """ + self.task_template( + "etc/modprobe.d/bonding.conf", "bonded/etc_modprobe.d_bonding.conf.j2" + ) + self.task_template( + "etc/sysconfig/network/ifcfg-bond0", + "bonded/etc_sysconfig_network_ifcfg-bond0.j2", + ) + self.task_template( + "etc/sysconfig/network/routes", "bonded/etc_sysconfig_network_routes.j2" + ) - self.tasks[ - "etc/sysconfig/network/ifcfg-bond0" - ] = """\ - STARTMODE='onboot' - BOOTPROTO='static' - IPADDR='{{ ip4pub.address }}/{{ ip4pub.cidr }}' - BONDING_MASTER='yes' - BONDING_SLAVE_0='{{ interfaces[0].name }}' - BONDING_SLAVE_1='{{ interfaces[1].name }}' - BONDING_MODULE_OPTS='mode={{ net.bonding.mode }} miimon=100' - IPADDR1='{{ ip4priv.address }}' - NETMASK1='{{ ip4priv.netmask }}' - GATEWAY1='{{ ip4priv.gateway }}' - LABEL1='0' - IPADDR2='{{ ip6pub.address }}/{{ ip6pub.cidr }}' - GATEWAY2='{{ ip6pub.gateway }}' - LABEL2='1' - """ - - self.tasks[ - "etc/sysconfig/network/routes" - ] = """\ - default {{ ip4pub.gateway }} - {% for subnets in private_subnets %} - {{ subnets }} {{ ip4priv.gateway }} - {% endfor %} - """ - - ifcfg = """\ - STARTMODE='hotplug' - BOOTPROTO='none' - """ for i in range(len(self.network.interfaces)): name = self.network.interfaces[i]["name"] - cfg = ifcfg.format(iface=name, i=i) - self.tasks["etc/sysconfig/network/ifcfg-" + name] = cfg - - self.tasks[ - "etc/resolv.conf" - ] = """\ - {% for server in resolvers %} - nameserver {{ server }} - {% endfor %} - """ - - self.tasks[ - "etc/hostname" - ] = """\ - {{ hostname }} - """ + self.task_template( + "etc/sysconfig/network/ifcfg-" + name, + "bonded/etc_sysconfig_network_ifcfg-template.j2", + fmt={"iface": name, "i": i}, + ) - self.tasks[ - "etc/hosts" - ] = """\ - 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 - ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 - """ return self.tasks diff --git a/packetnetworking/distros/suse/builder.py b/packetnetworking/distros/suse/builder.py index b00cb37..a1a5a3f 100644 --- a/packetnetworking/distros/suse/builder.py +++ b/packetnetworking/distros/suse/builder.py @@ -6,3 +6,12 @@ class SuseBuilder(DistroBuilder): distros = ["opensuseproject", "suselinux", "suse"] network_builders = [SuseBondedNetwork, SuseIndividualNetwork] + + def build_tasks(self): + self.tasks = {} + + self.task_template("etc/hostname", "etc_hostname.j2") + self.task_template("etc/resolv.conf", "etc_resolv.conf.j2") + self.task_template("etc/hosts", "etc_hosts.j2") + + return self.tasks diff --git a/packetnetworking/distros/suse/conftest.py b/packetnetworking/distros/suse/conftest.py index d11f546..2c30a73 100644 --- a/packetnetworking/distros/suse/conftest.py +++ b/packetnetworking/distros/suse/conftest.py @@ -44,9 +44,12 @@ def _builder(distro, version, public=True, metadata=None): ) builder = susebuilder(metadata, public=public) builder.build() - for builder in builder.builders: - if isinstance(builder, SuseBondedNetwork): - return builder + builder.builders = [ + builder + for builder in builder.builders + if isinstance(builder, SuseBondedNetwork) + ] + return builder return _builder @@ -69,8 +72,11 @@ def _builder(distro, version, public=True, metadata=None): ) builder = susebuilder(metadata, public=public) builder.build() - for builder in builder.builders: - if isinstance(builder, SuseIndividualNetwork): - return builder + builder.builders = [ + builder + for builder in builder.builders + if isinstance(builder, SuseIndividualNetwork) + ] + return builder return _builder diff --git a/packetnetworking/distros/suse/individual.py b/packetnetworking/distros/suse/individual.py index cefd5dd..262d727 100644 --- a/packetnetworking/distros/suse/individual.py +++ b/packetnetworking/distros/suse/individual.py @@ -12,59 +12,22 @@ def build_tasks(self): iface0 = self.network.interfaces[0] - self.tasks[ - "etc/sysconfig/network/ifcfg-" + iface0.name - ] = """\ - STARTMODE='onboot' - BOOTPROTO='static' - IPADDR='{{ ip4pub.address }}/{{ ip4pub.cidr }}' - IPADDR1='{{ ip4priv.address }}' - NETMASK1='{{ ip4priv.netmask }}' - GATEWAY1='{{ ip4priv.gateway }}' - LABEL1='0' - IPADDR2='{{ ip6pub.address }}/{{ ip6pub.cidr }}' - GATEWAY2='{{ ip6pub.gateway }}' - LABEL2='1' - """ + self.task_template( + "etc/sysconfig/network/ifcfg-" + iface0.name, + "individual/etc_sysconfig_network_ifcfg-iface0.j2", + ) + self.task_template( + "etc/sysconfig/network/routes", "individual/etc_sysconfig_network_routes.j2" + ) - self.tasks[ - "etc/sysconfig/network/routes" - ] = """\ - default {{ ip4pub.gateway }} - {% for subnet in private_subnets %} - {{ subnet }} {{ ip4priv.gateway }} - {% endfor %} - """ - - ifcfg = """\ - STARTMODE='hotplug' - BOOTPROTO='none' - """ for i, iface in enumerate(self.network.interfaces): if iface == iface0: # skip interface since it is already configured above continue - cfg = ifcfg.format(iface=iface.name, i=i) - self.tasks["etc/sysconfig/network/ifcfg-" + iface.name] = cfg - - self.tasks[ - "etc/resolv.conf" - ] = """\ - {% for server in resolvers %} - nameserver {{ server }} - {% endfor %} - """ - - self.tasks[ - "etc/hostname" - ] = """\ - {{ hostname }} - """ + self.task_template( + "etc/sysconfig/network/ifcfg-" + iface.name, + "individual/etc_sysconfig_network_ifcfg-template.j2", + fmt={"iface": iface.name, "i": i}, + ) - self.tasks[ - "etc/hosts" - ] = """\ - 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 - ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 - """ return self.tasks diff --git a/packetnetworking/distros/suse/templates/bonded/etc_modprobe.d_bonding.conf.j2 b/packetnetworking/distros/suse/templates/bonded/etc_modprobe.d_bonding.conf.j2 new file mode 100644 index 0000000..fecbe38 --- /dev/null +++ b/packetnetworking/distros/suse/templates/bonded/etc_modprobe.d_bonding.conf.j2 @@ -0,0 +1,2 @@ +alias bond0 bonding +options bond0 mode={{ net.bonding.mode }} miimon=100 downdelay=200 updelay=200 xmit_hash_policy=layer3+4 lacp_rate=1 diff --git a/packetnetworking/distros/suse/templates/bonded/etc_sysconfig_network_ifcfg-bond0.j2 b/packetnetworking/distros/suse/templates/bonded/etc_sysconfig_network_ifcfg-bond0.j2 new file mode 100644 index 0000000..8b5873a --- /dev/null +++ b/packetnetworking/distros/suse/templates/bonded/etc_sysconfig_network_ifcfg-bond0.j2 @@ -0,0 +1,14 @@ +STARTMODE='onboot' +BOOTPROTO='static' +IPADDR='{{ ip4pub.address }}/{{ ip4pub.cidr }}' +BONDING_MASTER='yes' +BONDING_SLAVE_0='{{ interfaces[0].name }}' +BONDING_SLAVE_1='{{ interfaces[1].name }}' +BONDING_MODULE_OPTS='mode={{ net.bonding.mode }} miimon=100' +IPADDR1='{{ ip4priv.address }}' +NETMASK1='{{ ip4priv.netmask }}' +GATEWAY1='{{ ip4priv.gateway }}' +LABEL1='0' +IPADDR2='{{ ip6pub.address }}/{{ ip6pub.cidr }}' +GATEWAY2='{{ ip6pub.gateway }}' +LABEL2='1' diff --git a/packetnetworking/distros/suse/templates/bonded/etc_sysconfig_network_ifcfg-template.j2 b/packetnetworking/distros/suse/templates/bonded/etc_sysconfig_network_ifcfg-template.j2 new file mode 100644 index 0000000..236c78b --- /dev/null +++ b/packetnetworking/distros/suse/templates/bonded/etc_sysconfig_network_ifcfg-template.j2 @@ -0,0 +1,2 @@ +STARTMODE='hotplug' +BOOTPROTO='none' diff --git a/packetnetworking/distros/suse/templates/bonded/etc_sysconfig_network_routes.j2 b/packetnetworking/distros/suse/templates/bonded/etc_sysconfig_network_routes.j2 new file mode 100644 index 0000000..35e0af0 --- /dev/null +++ b/packetnetworking/distros/suse/templates/bonded/etc_sysconfig_network_routes.j2 @@ -0,0 +1,4 @@ +default {{ ip4pub.gateway }} +{% for subnets in private_subnets %} +{{ subnets }} {{ ip4priv.gateway }} +{% endfor %} diff --git a/packetnetworking/distros/suse/templates/etc_hostname.j2 b/packetnetworking/distros/suse/templates/etc_hostname.j2 new file mode 100644 index 0000000..56baac7 --- /dev/null +++ b/packetnetworking/distros/suse/templates/etc_hostname.j2 @@ -0,0 +1 @@ +{{ hostname }} diff --git a/packetnetworking/distros/suse/templates/etc_hosts.j2 b/packetnetworking/distros/suse/templates/etc_hosts.j2 new file mode 100644 index 0000000..849c10d --- /dev/null +++ b/packetnetworking/distros/suse/templates/etc_hosts.j2 @@ -0,0 +1,2 @@ +127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 +::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 diff --git a/packetnetworking/distros/suse/templates/etc_resolv.conf.j2 b/packetnetworking/distros/suse/templates/etc_resolv.conf.j2 new file mode 100644 index 0000000..a3a2c65 --- /dev/null +++ b/packetnetworking/distros/suse/templates/etc_resolv.conf.j2 @@ -0,0 +1,3 @@ +{% for server in resolvers %} +nameserver {{ server }} +{% endfor %} diff --git a/packetnetworking/distros/suse/templates/individual/etc_sysconfig_network_ifcfg-iface0.j2 b/packetnetworking/distros/suse/templates/individual/etc_sysconfig_network_ifcfg-iface0.j2 new file mode 100644 index 0000000..81f2c1e --- /dev/null +++ b/packetnetworking/distros/suse/templates/individual/etc_sysconfig_network_ifcfg-iface0.j2 @@ -0,0 +1,10 @@ +STARTMODE='onboot' +BOOTPROTO='static' +IPADDR='{{ ip4pub.address }}/{{ ip4pub.cidr }}' +IPADDR1='{{ ip4priv.address }}' +NETMASK1='{{ ip4priv.netmask }}' +GATEWAY1='{{ ip4priv.gateway }}' +LABEL1='0' +IPADDR2='{{ ip6pub.address }}/{{ ip6pub.cidr }}' +GATEWAY2='{{ ip6pub.gateway }}' +LABEL2='1' diff --git a/packetnetworking/distros/suse/templates/individual/etc_sysconfig_network_ifcfg-template.j2 b/packetnetworking/distros/suse/templates/individual/etc_sysconfig_network_ifcfg-template.j2 new file mode 100644 index 0000000..236c78b --- /dev/null +++ b/packetnetworking/distros/suse/templates/individual/etc_sysconfig_network_ifcfg-template.j2 @@ -0,0 +1,2 @@ +STARTMODE='hotplug' +BOOTPROTO='none' diff --git a/packetnetworking/distros/suse/templates/individual/etc_sysconfig_network_routes.j2 b/packetnetworking/distros/suse/templates/individual/etc_sysconfig_network_routes.j2 new file mode 100644 index 0000000..6be90f6 --- /dev/null +++ b/packetnetworking/distros/suse/templates/individual/etc_sysconfig_network_routes.j2 @@ -0,0 +1,4 @@ +default {{ ip4pub.gateway }} +{% for subnet in private_subnets %} +{{ subnet }} {{ ip4priv.gateway }} +{% endfor %} diff --git a/packetnetworking/distros/test_distro_builder.py b/packetnetworking/distros/test_distro_builder.py index cc9d347..6c71ed5 100644 --- a/packetnetworking/distros/test_distro_builder.py +++ b/packetnetworking/distros/test_distro_builder.py @@ -1,6 +1,10 @@ -from .distro_builder import DistroBuilder, get_distro_builder +import mock import pytest +from .distro_builder import DistroBuilder, get_distro_builder +from .. import utils +from ..builder import NetworkData + @pytest.fixture def fake_distro_builder(): @@ -18,6 +22,9 @@ class FakeDistroBuilder(DistroBuilder): @pytest.fixture def fake_network_builder(): class FakeNetworkBuilder: + templates_base = "fake/distro/templates" + tasks = None + def __init__(self, metadata): pass @@ -30,6 +37,39 @@ def run(self, rootfs_path): return FakeNetworkBuilder +@pytest.fixture +def fake_metadata(metadata): + return metadata({"network": None}) + + +@pytest.fixture +def fake_distro_builder_with_metadata(mockit, fake, metadata, patch_dict): + gen_metadata = metadata + + def func(metadata=None, public=True): + meta_interfaces = [ + {"name": "eth0", "mac": "00:0c:29:51:53:a1", "bond": "bond0"}, + {"name": "eth1", "mac": "00:0c:29:51:53:a2", "bond": "bond0"}, + ] + phys_interfaces = [ + {"name": "enp0", "mac": "00:0c:29:51:53:a1"}, + {"name": "enp1", "mac": "00:0c:29:51:53:a2"}, + ] + _metadata = {"network": {"interfaces": meta_interfaces}} + if metadata: + patch_dict(_metadata, metadata) + md = gen_metadata(_metadata, public=public) + + network_data = NetworkData() + with mockit(utils.get_interfaces, return_value=phys_interfaces): + network_data.load(md.network) + md.network = network_data + + return DistroBuilder(md) + + return func + + def test_get_distro_builder_finds_correct_builder(fake_distro_builder): fake_distro = fake_distro_builder(["fakeos1", "fakeos2"]) assert get_distro_builder("fakeos1") is fake_distro @@ -45,44 +85,224 @@ def test_get_distro_builder_finds_catch_all(fake_distro_builder): def test_distro_builder_initializes_network_builders( - fake_distro_builder, fake_network_builder + fake_distro_builder, fake_network_builder, fake_metadata ): fake_distro = fake_distro_builder(["fakeos4"]) fake_distro.network_builders = [fake_network_builder] - distro = fake_distro(None) + distro = fake_distro(fake_metadata) distro.build() assert isinstance(distro.builders[0], fake_network_builder) -def test_distro_builder_returns_true_with_tasks( - fake_distro_builder, fake_network_builder +def test_distro_builder_returns_dict_with_tasks( + fake_distro_builder, fake_network_builder, fake_metadata ): - def true_run(self, rootfs_path): - return True - - fake_network_builder.run = true_run - fake_distro = fake_distro_builder(["fakeos5"]) - fake_distro.network_builders = [fake_network_builder] + distro = fake_distro(fake_metadata) + distro.build() + with mock.patch.object(distro, "render", return_value={"task": None}): + with mock.patch("os.path.lexists") as mock_lexists: + with mock.patch("os.remove") as mock_remove: + mock_lexists.return_value = True + mock_remove.return_value = True + assert isinstance(distro.run("/path/to/rootfs"), dict) + - distro = fake_distro(None) +def test_distro_builder_returns_dict_without_tasks( + fake_distro_builder, fake_network_builder, fake_metadata +): + fake_distro = fake_distro_builder(["fakeos6"]) + distro = fake_distro(fake_metadata) distro.build() - assert distro.run(None) is True + with mock.patch.object(distro, "render", return_value=None): + with mock.patch("os.path.lexists") as mock_lexists: + with mock.patch("os.remove") as mock_remove: + mock_lexists.return_value = True + mock_remove.return_value = True + resp = distro.run("/path/to/rootfs") + assert isinstance(resp, dict) + assert len(resp) == 0 -def test_distro_builder_returns_none_without_tasks( - fake_distro_builder, fake_network_builder +def test_distro_builder_has_correct_ipv_responses( + fake_distro_builder_with_metadata, fake_address ): - def false_run(self, rootfs_path): - return False + pub_ipv4_1 = fake_address() + pub_ipv4_2 = fake_address() + pub_ipv6_1 = fake_address( + address_family=6, netmask="ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe" + ) + pub_ipv6_2 = fake_address( + address_family=6, netmask="ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe" + ) + priv_ipv4_1 = fake_address(public=False) + priv_ipv4_2 = fake_address(public=False) + fake_distro = fake_distro_builder_with_metadata( + { + "network": { + "!addresses": [ + pub_ipv4_1, + pub_ipv4_2, + pub_ipv6_1, + pub_ipv6_2, + priv_ipv4_1, + priv_ipv4_2, + ] + } + } + ) - fake_network_builder.run = false_run + assert fake_distro.ipv4pub == [pub_ipv4_1, pub_ipv4_2] + assert fake_distro.ipv6pub == [pub_ipv6_1, pub_ipv6_2] + assert fake_distro.ipv4priv == [priv_ipv4_1, priv_ipv4_2] - fake_distro = fake_distro_builder(["fakeos6"]) - fake_distro.network_builders = [fake_network_builder] + assert fake_distro.ipv4pub.first == pub_ipv4_1 + assert fake_distro.ipv6pub.first == pub_ipv6_1 + assert fake_distro.ipv4priv.first == priv_ipv4_1 - distro = fake_distro(None) - distro.build() - assert distro.run(None) is None + +def test_distro_builder_context_as_expected(fake_distro_builder_with_metadata): + fake_distro = fake_distro_builder_with_metadata() + context = fake_distro.context() + wanted_context = { + "hostname": fake_distro.metadata.hostname, + "interfaces": fake_distro.network.interfaces, + "iface0": fake_distro.network.interfaces[0], + "ip4priv": fake_distro.ipv4priv.first, + "ip4pub": fake_distro.ipv4pub.first, + "ip6pub": fake_distro.ipv6pub.first, + "net": fake_distro.network, + "osinfo": fake_distro.metadata.operating_system, + "private_subnets": fake_distro.network.private_subnets, + "resolvers": fake_distro.network.resolvers, + } + + assert context == wanted_context + + +def test_distro_builder_render_does_nothing_with_delete_tasks( + fake_distro_builder_with_metadata +): + fake_distro = fake_distro_builder_with_metadata() + fake_distro.tasks = {"path/to/file": None} + + rendered_tasks = fake_distro.render() + assert rendered_tasks["path/to/file"] is None + + +def test_distro_builder_render_renders_string_tasks(fake_distro_builder_with_metadata): + fake_distro = fake_distro_builder_with_metadata() + fake_distro.tasks = { + "path/to/file": """ + hostname = {{ hostname }} + """ + } + rendered_tasks = fake_distro.render() + + wanted_content = "\nhostname = {hostname}\n".format(**fake_distro.context()) + assert rendered_tasks["path/to/file"] == wanted_content + + +def test_distro_builder_render_renders_dict_tasks(fake_distro_builder_with_metadata): + fake_distro = fake_distro_builder_with_metadata() + fake_distro.tasks = { + "path/to/file": { + "mode": 0o755, + "template": """ + hostname = {{ hostname }} + """, + } + } + rendered_tasks = fake_distro.render() + + wanted_mode = 0o755 + wanted_content = "\nhostname = {hostname}\n".format(**fake_distro.context()) + assert rendered_tasks["path/to/file"]["mode"] == wanted_mode + assert rendered_tasks["path/to/file"]["content"] == wanted_content + + +def test_distro_builder_run_deletes_file(fake_distro_builder_with_metadata): + fake_distro = fake_distro_builder_with_metadata() + fake_distro.tasks = {"path/to/file": None} + + with mock.patch("os.path.lexists") as mock_lexists: + with mock.patch("os.remove") as mock_remove: + mock_lexists.return_value = True + mock_remove.return_value = True + fake_distro.run("/path/to/rootfs") + + mock_lexists.assert_called_with("/path/to/rootfs/path/to/file") + mock_remove.assert_called_with("/path/to/rootfs/path/to/file") + + +def test_distro_builder_run_skips_deleting_missing_file( + fake_distro_builder_with_metadata +): + fake_distro = fake_distro_builder_with_metadata() + fake_distro.tasks = {"path/to/file": None} + + with mock.patch("os.path.lexists") as mock_lexists: + with mock.patch("os.remove") as mock_remove: + mock_lexists.return_value = False + mock_remove.return_value = True + fake_distro.run("/path/to/rootfs") + + mock_lexists.assert_called_with("/path/to/rootfs/path/to/file") + mock_remove.assert_not_called() + + +def test_distro_builder_creates_file(fake_distro_builder_with_metadata): + fake_distro = fake_distro_builder_with_metadata() + fake_distro.tasks = { + "path/to/file": """ + hostname = {{ hostname }} + """ + } + + with mock.patch("os.path.lexists") as mock_lexists: + with mock.patch("os.makedirs") as mock_makedirs: + open_ = mock.mock_open() + with mock.patch("builtins.open", open_) as mock_open: + mock_lexists.return_value = False + mock_makedirs.return_value = True + fake_distro.run("/path/to/rootfs") + + mock_lexists.assert_called_with("/path/to/rootfs/path/to") + mock_makedirs.assert_called_with("/path/to/rootfs/path/to", exist_ok=True) + fh = mock_open.return_value.__enter__.return_value + fh.write.assert_called_with( + "\nhostname = {hostname}\n".format(**fake_distro.context()) + ) + + +def test_distro_builder_creates_file_with_mode(fake_distro_builder_with_metadata): + fake_distro = fake_distro_builder_with_metadata() + fake_distro.tasks = { + "path/to/file": { + "mode": 0o755, + "template": """ + hostname = {{ hostname }} + """, + } + } + + with mock.patch("os.path.lexists") as mock_lexists: + with mock.patch("os.makedirs") as mock_makedirs: + open_ = mock.mock_open() + with mock.patch("builtins.open", open_) as mock_open: + with mock.patch("os.chmod") as mock_chmod: + mock_lexists.return_value = False + mock_makedirs.return_value = True + mock_chmod.return_value = True + fake_distro.run("/path/to/rootfs") + + mock_lexists.assert_called_with("/path/to/rootfs/path/to") + mock_makedirs.assert_called_with("/path/to/rootfs/path/to", exist_ok=True) + mock_open.assert_called_with("/path/to/rootfs/path/to/file", "w") + fh = mock_open() + fh.write.assert_called_with( + "\nhostname = {hostname}\n".format(**fake_distro.context()) + ) + mock_chmod.assert_called_with("/path/to/rootfs/path/to/file", 0o755) diff --git a/packetnetworking/distros/test_network_builder.py b/packetnetworking/distros/test_network_builder.py deleted file mode 100644 index ab50551..0000000 --- a/packetnetworking/distros/test_network_builder.py +++ /dev/null @@ -1,209 +0,0 @@ -from .network_builder import NetworkBuilder -from ..builder import NetworkData -from .. import utils -import mock -import pytest - - -@pytest.fixture -def fake_network_builder(mockit, fake, metadata, patch_dict): - gen_metadata = metadata - - def func(metadata=None, public=True): - meta_interfaces = [ - {"name": "eth0", "mac": "00:0c:29:51:53:a1", "bond": "bond0"}, - {"name": "eth1", "mac": "00:0c:29:51:53:a2", "bond": "bond0"}, - ] - phys_interfaces = [ - {"name": "enp0", "mac": "00:0c:29:51:53:a1"}, - {"name": "enp1", "mac": "00:0c:29:51:53:a2"}, - ] - _metadata = {"network": {"interfaces": meta_interfaces}} - if metadata: - patch_dict(_metadata, metadata) - md = gen_metadata(_metadata, public=public) - - network_data = NetworkData() - with mockit(utils.get_interfaces, return_value=phys_interfaces): - network_data.load(md.network) - md.network = network_data - - return NetworkBuilder(md) - - return func - - -def test_network_builder_has_correct_ipv_responses(fake_network_builder, fake_address): - pub_ipv4_1 = fake_address() - pub_ipv4_2 = fake_address() - pub_ipv6_1 = fake_address( - address_family=6, netmask="ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe" - ) - pub_ipv6_2 = fake_address( - address_family=6, netmask="ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe" - ) - priv_ipv4_1 = fake_address(public=False) - priv_ipv4_2 = fake_address(public=False) - fake_network = fake_network_builder( - { - "network": { - "!addresses": [ - pub_ipv4_1, - pub_ipv4_2, - pub_ipv6_1, - pub_ipv6_2, - priv_ipv4_1, - priv_ipv4_2, - ] - } - } - ) - - assert fake_network.ipv4pub == [pub_ipv4_1, pub_ipv4_2] - assert fake_network.ipv6pub == [pub_ipv6_1, pub_ipv6_2] - assert fake_network.ipv4priv == [priv_ipv4_1, priv_ipv4_2] - - assert fake_network.ipv4pub.first == pub_ipv4_1 - assert fake_network.ipv6pub.first == pub_ipv6_1 - assert fake_network.ipv4priv.first == priv_ipv4_1 - - -def test_network_builder_context_as_expected(fake_network_builder): - fake_network = fake_network_builder() - context = fake_network.context() - wanted_context = { - "hostname": fake_network.metadata.hostname, - "interfaces": fake_network.network.interfaces, - "iface0": fake_network.network.interfaces[0], - "ip4priv": fake_network.ipv4priv.first, - "ip4pub": fake_network.ipv4pub.first, - "ip6pub": fake_network.ipv6pub.first, - "net": fake_network.network, - "osinfo": fake_network.metadata.operating_system, - "private_subnets": fake_network.network.private_subnets, - "resolvers": fake_network.network.resolvers, - } - - assert context == wanted_context - - -def test_network_builder_render_does_nothing_with_delete_tasks(fake_network_builder): - fake_network = fake_network_builder() - fake_network.tasks = {"path/to/file": None} - - rendered_tasks = fake_network.render() - assert rendered_tasks["path/to/file"] is None - - -def test_network_builder_render_renders_string_tasks(fake_network_builder): - fake_network = fake_network_builder() - fake_network.tasks = { - "path/to/file": """ - hostname = {{ hostname }} - """ - } - rendered_tasks = fake_network.render() - - wanted_content = "\nhostname = {hostname}\n".format(**fake_network.context()) - assert rendered_tasks["path/to/file"] == wanted_content - - -def test_network_builder_render_renders_dict_tasks(fake_network_builder): - fake_network = fake_network_builder() - fake_network.tasks = { - "path/to/file": { - "mode": 0o755, - "template": """ - hostname = {{ hostname }} - """, - } - } - rendered_tasks = fake_network.render() - - wanted_mode = 0o755 - wanted_content = "\nhostname = {hostname}\n".format(**fake_network.context()) - assert rendered_tasks["path/to/file"]["mode"] == wanted_mode - assert rendered_tasks["path/to/file"]["content"] == wanted_content - - -def test_network_builder_run_deletes_file(fake_network_builder): - fake_network = fake_network_builder() - fake_network.tasks = {"path/to/file": None} - - with mock.patch("os.path.lexists") as mock_lexists: - with mock.patch("os.remove") as mock_remove: - mock_lexists.return_value = True - mock_remove.return_value = True - fake_network.run("/path/to/rootfs") - - mock_lexists.assert_called_with("/path/to/rootfs/path/to/file") - mock_remove.assert_called_with("/path/to/rootfs/path/to/file") - - -def test_network_builder_run_skips_deleting_missing_file(fake_network_builder): - fake_network = fake_network_builder() - fake_network.tasks = {"path/to/file": None} - - with mock.patch("os.path.lexists") as mock_lexists: - with mock.patch("os.remove") as mock_remove: - mock_lexists.return_value = False - mock_remove.return_value = True - fake_network.run("/path/to/rootfs") - - mock_lexists.assert_called_with("/path/to/rootfs/path/to/file") - mock_remove.assert_not_called() - - -def test_network_builder_creates_file(fake_network_builder): - fake_network = fake_network_builder() - fake_network.tasks = { - "path/to/file": """ - hostname = {{ hostname }} - """ - } - - with mock.patch("os.path.lexists") as mock_lexists: - with mock.patch("os.makedirs") as mock_makedirs: - open_ = mock.mock_open() - with mock.patch("builtins.open", open_) as mock_open: - mock_lexists.return_value = False - mock_makedirs.return_value = True - fake_network.run("/path/to/rootfs") - - mock_lexists.assert_called_with("/path/to/rootfs/path/to") - mock_makedirs.assert_called_with("/path/to/rootfs/path/to", exist_ok=True) - fh = mock_open.return_value.__enter__.return_value - fh.write.assert_called_with( - "\nhostname = {hostname}\n".format(**fake_network.context()) - ) - - -def test_network_builder_creates_file_with_mode(fake_network_builder): - fake_network = fake_network_builder() - fake_network.tasks = { - "path/to/file": { - "mode": 0o755, - "template": """ - hostname = {{ hostname }} - """, - } - } - - with mock.patch("os.path.lexists") as mock_lexists: - with mock.patch("os.makedirs") as mock_makedirs: - open_ = mock.mock_open() - with mock.patch("builtins.open", open_) as mock_open: - with mock.patch("os.chmod") as mock_chmod: - mock_lexists.return_value = False - mock_makedirs.return_value = True - mock_chmod.return_value = True - fake_network.run("/path/to/rootfs") - - mock_lexists.assert_called_with("/path/to/rootfs/path/to") - mock_makedirs.assert_called_with("/path/to/rootfs/path/to", exist_ok=True) - mock_open.assert_called_with("/path/to/rootfs/path/to/file", "w") - fh = mock_open() - fh.write.assert_called_with( - "\nhostname = {hostname}\n".format(**fake_network.context()) - ) - mock_chmod.assert_called_with("/path/to/rootfs/path/to/file", 0o755) diff --git a/packetnetworking/utils.py b/packetnetworking/utils.py index 93ddc3f..f32eb2f 100644 --- a/packetnetworking/utils.py +++ b/packetnetworking/utils.py @@ -1,8 +1,12 @@ +import json +import os import re +import subprocess import sys -import json + import click -import subprocess + +package_dir = os.path.abspath(os.path.dirname(os.path.abspath(__file__))) class DictAttributes(dict): @@ -132,6 +136,23 @@ def where(self, *args, **kwargs): return IPAddressList(super().where(*args, **kwargs)) +class Tasks(object): + def task(self, task, content, write_mode=None, mode=None, fmt=None): + self.tasks[task] = { + "file_mode": write_mode, + "mode": mode, + "template": content, + "fmt": fmt, + } + return self.tasks[task] + + def task_template(self, task, path, write_mode=None, mode=None, fmt=None): + path = os.path.join(package_dir, self.templates_base, path) + t = self.task(task, None, write_mode, mode, fmt) + t["template_path"] = path + return t + + def jfind(j, fn): result = [] for i in j: diff --git a/setup.py b/setup.py index ca483c3..29f24f3 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,25 @@ #!/usr/bin/env python3 +import os from setuptools import setup, find_packages +package_dir = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "packetnetworking" +) + + +def find_templates(): + files = [] + for (path, directories, filenames) in os.walk(package_dir): + if "templates" not in path.split(os.path.sep): + continue + for file in filenames: + if not file.endswith(".j2"): + continue + files.append(os.path.relpath(os.path.join(path, file), package_dir)) + return files + + test_reqs = ["pytest", "pytest-cov", "mock", "faker", "netaddr", "tox"] setup( @@ -17,6 +35,7 @@ "jinja2 >=2.9,<2.10", "requests >=2.21.0,<2.23", ], + package_data={"packetnetworking": find_templates()}, extras_require={"test": test_reqs}, tests_require=test_reqs, entry_points="""