github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/maas/bridgescript.go (about)

     1  // This file is auto generated. Edits will be lost.
     2  
     3  package maas
     4  
     5  //go:generate make -q
     6  
     7  import "path"
     8  
     9  const bridgeScriptName = "add-juju-bridge.py"
    10  
    11  var bridgeScriptPath = path.Join("/tmp", bridgeScriptName)
    12  
    13  const bridgeScriptPython = `#!/usr/bin/env python
    14  
    15  # Copyright 2015 Canonical Ltd.
    16  # Licensed under the AGPLv3, see LICENCE file for details.
    17  
    18  #
    19  # This file has been and should be formatted using pyfmt(1).
    20  #
    21  
    22  from __future__ import print_function
    23  import argparse
    24  import os
    25  import re
    26  import shutil
    27  import subprocess
    28  import sys
    29  
    30  
    31  class SeekableIterator(object):
    32      """An iterator that supports relative seeking."""
    33  
    34      def __init__(self, iterable):
    35          self.iterable = iterable
    36          self.index = 0
    37  
    38      def __iter__(self):
    39          return self
    40  
    41      def next(self):  # Python 2
    42          try:
    43              value = self.iterable[self.index]
    44              self.index += 1
    45              return value
    46          except IndexError:
    47              raise StopIteration
    48  
    49      def __next__(self):  # Python 3
    50          return self.next()
    51  
    52      def seek(self, n, relative=False):
    53          if relative:
    54              self.index += n
    55          else:
    56              self.index = n
    57          if self.index < 0 or self.index >= len(self.iterable):
    58              raise IndexError
    59  
    60  
    61  class PhysicalInterface(object):
    62      """Represents a physical ('auto') interface."""
    63  
    64      def __init__(self, definition):
    65          self.name = definition.split()[1]
    66  
    67      def __str__(self):
    68          return self.name
    69  
    70  
    71  class LogicalInterface(object):
    72      """Represents a logical ('iface') interface."""
    73  
    74      def __init__(self, definition, options=None):
    75          if not options:
    76              options = []
    77          _, self.name, self.family, self.method = definition.split()
    78          self.options = options
    79          self.is_bonded = [x for x in self.options if "bond-" in x]
    80          self.is_alias = ":" in self.name
    81          self.is_vlan = [x for x in self.options if x.startswith("vlan-raw-device")]
    82          self.is_active = self.method == "dhcp" or self.method == "static"
    83          self.is_bridged = [x for x in self.options if x.startswith("bridge_ports ")]
    84  
    85      def __str__(self):
    86          return self.name
    87  
    88      # Returns an ordered set of stanzas to bridge this interface.
    89      def bridge(self, prefix, bridge_name, add_auto_stanza):
    90          if bridge_name is None:
    91              bridge_name = prefix + self.name
    92          # Note: the testing order here is significant.
    93          if not self.is_active or self.is_bridged:
    94              return self._bridge_unchanged(add_auto_stanza)
    95          elif self.is_alias:
    96              return self._bridge_alias(add_auto_stanza)
    97          elif self.is_vlan:
    98              return self._bridge_vlan(prefix, bridge_name, add_auto_stanza)
    99          elif self.is_bonded:
   100              return self._bridge_bond(prefix, bridge_name, add_auto_stanza)
   101          else:
   102              return self._bridge_device(prefix, bridge_name)
   103  
   104      def _bridge_device(self, prefix, bridge_name):
   105          s1 = IfaceStanza(self.name, self.family, "manual", [])
   106          s2 = AutoStanza(bridge_name)
   107          options = list(self.options)
   108          options.append("bridge_ports {}".format(self.name))
   109          options.append("bridge_stp off")
   110          options.append("bridge_maxwait 0")
   111          s3 = IfaceStanza(bridge_name, self.family, self.method, options)
   112          return [s1, s2, s3]
   113  
   114      def _bridge_vlan(self, prefix, bridge_name, add_auto_stanza):
   115          stanzas = []
   116          s1 = IfaceStanza(self.name, self.family, "manual", self.options)
   117          stanzas.append(s1)
   118          if add_auto_stanza:
   119              stanzas.append(AutoStanza(bridge_name))
   120          options = [x for x in self.options if not x.startswith("vlan")]
   121          options.append("bridge_ports {}".format(self.name))
   122          options.append("bridge_stp off")
   123          options.append("bridge_maxwait 0")
   124          s3 = IfaceStanza(bridge_name, self.family, self.method, options)
   125          stanzas.append(s3)
   126          return stanzas
   127  
   128      def _bridge_alias(self, add_auto_stanza):
   129          stanzas = []
   130          if add_auto_stanza:
   131              stanzas.append(AutoStanza(self.name))
   132          s1 = IfaceStanza(self.name, self.family, self.method, list(self.options))
   133          stanzas.append(s1)
   134          return stanzas
   135  
   136      def _bridge_bond(self, prefix, bridge_name, add_auto_stanza):
   137          stanzas = []
   138          if add_auto_stanza:
   139              stanzas.append(AutoStanza(self.name))
   140          s1 = IfaceStanza(self.name, self.family, "manual", list(self.options))
   141          s2 = AutoStanza(bridge_name)
   142          options = [x for x in self.options if not x.startswith("bond")]
   143          options.append("bridge_ports {}".format(self.name))
   144          options.append("bridge_stp off")
   145          options.append("bridge_maxwait 0")
   146          s3 = IfaceStanza(bridge_name, self.family, self.method, options)
   147          stanzas.extend([s1, s2, s3])
   148          return stanzas
   149  
   150      def _bridge_unchanged(self, add_auto_stanza):
   151          stanzas = []
   152          if add_auto_stanza:
   153              stanzas.append(AutoStanza(self.name))
   154          s1 = IfaceStanza(self.name, self.family, self.method, list(self.options))
   155          stanzas.append(s1)
   156          return stanzas
   157  
   158  
   159  class Stanza(object):
   160      """Represents one stanza together with all of its options."""
   161  
   162      def __init__(self, definition, options=None):
   163          if not options:
   164              options = []
   165          self.definition = definition
   166          self.options = options
   167          self.is_logical_interface = definition.startswith('iface ')
   168          self.is_physical_interface = definition.startswith('auto ')
   169          self.iface = None
   170          self.phy = None
   171          if self.is_logical_interface:
   172              self.iface = LogicalInterface(definition, self.options)
   173          if self.is_physical_interface:
   174              self.phy = PhysicalInterface(definition)
   175  
   176      def __str__(self):
   177          return self.definition
   178  
   179  
   180  class NetworkInterfaceParser(object):
   181      """Parse a network interface file into a set of stanzas."""
   182  
   183      @classmethod
   184      def is_stanza(cls, s):
   185          return re.match(r'^(iface|mapping|auto|allow-|source)', s)
   186  
   187      def __init__(self, filename):
   188          self._stanzas = []
   189          with open(filename, 'r') as f:
   190              lines = f.readlines()
   191          line_iterator = SeekableIterator(lines)
   192          for line in line_iterator:
   193              if self.is_stanza(line):
   194                  stanza = self._parse_stanza(line, line_iterator)
   195                  self._stanzas.append(stanza)
   196  
   197      def _parse_stanza(self, stanza_line, iterable):
   198          stanza_options = []
   199          for line in iterable:
   200              line = line.strip()
   201              if line.startswith('#') or line == "":
   202                  continue
   203              if self.is_stanza(line):
   204                  iterable.seek(-1, True)
   205                  break
   206              stanza_options.append(line)
   207          return Stanza(stanza_line.strip(), stanza_options)
   208  
   209      def stanzas(self):
   210          return [x for x in self._stanzas]
   211  
   212      def physical_interfaces(self):
   213          return {x.phy.name: x.phy for x in [y for y in self._stanzas if y.is_physical_interface]}
   214  
   215      def __iter__(self):  # class iter
   216          for s in self._stanzas:
   217              yield s
   218  
   219  
   220  def uniq_append(dst, src):
   221      for x in src:
   222          if x not in dst:
   223              dst.append(x)
   224      return dst
   225  
   226  
   227  def IfaceStanza(name, family, method, options):
   228      """Convenience function to create a new "iface" stanza.
   229  
   230  Maintains original options order but removes duplicates with the
   231  exception of 'dns-*' options which are normlised as required by
   232  resolvconf(8) and all the dns-* options are moved to the end.
   233  
   234      """
   235  
   236      dns_search = []
   237      dns_nameserver = []
   238      dns_sortlist = []
   239      unique_options = []
   240  
   241      for o in options:
   242          words = o.split()
   243          ident = words[0]
   244          if ident == "dns-nameservers":
   245              dns_nameserver = uniq_append(dns_nameserver, words[1:])
   246          elif ident == "dns-search":
   247              dns_search = uniq_append(dns_search, words[1:])
   248          elif ident == "dns-sortlist":
   249              dns_sortlist = uniq_append(dns_sortlist, words[1:])
   250          elif o not in unique_options:
   251              unique_options.append(o)
   252  
   253      if dns_nameserver:
   254          option = "dns-nameservers " + " ".join(dns_nameserver)
   255          unique_options.append(option)
   256  
   257      if dns_search:
   258          option = "dns-search " + " ".join(dns_search)
   259          unique_options.append(option)
   260  
   261      if dns_sortlist:
   262          option = "dns-sortlist " + " ".join(dns_sortlist)
   263          unique_options.append(option)
   264  
   265      return Stanza("iface {} {} {}".format(name, family, method), unique_options)
   266  
   267  
   268  def AutoStanza(name):
   269      # Convenience function to create a new "auto" stanza.
   270      return Stanza("auto {}".format(name))
   271  
   272  
   273  def print_stanza(s, stream=sys.stdout):
   274      print(s.definition, file=stream)
   275      for o in s.options:
   276          print("   ", o, file=stream)
   277  
   278  
   279  def print_stanzas(stanzas, stream=sys.stdout):
   280      n = len(stanzas)
   281      for i, stanza in enumerate(stanzas):
   282          print_stanza(stanza, stream)
   283          if stanza.is_logical_interface and i + 1 < n:
   284              print(file=stream)
   285  
   286  
   287  def shell_cmd(s):
   288      p = subprocess.Popen(s, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   289      out, err = p.communicate()
   290      return [out, err, p.returncode]
   291  
   292  
   293  def print_shell_cmd(s, verbose=True, exit_on_error=False):
   294      if verbose:
   295          print(s)
   296      out, err, retcode = shell_cmd(s)
   297      if out and len(out) > 0:
   298          print(out.decode().rstrip('\n'))
   299      if err and len(err) > 0:
   300          print(err.decode().rstrip('\n'))
   301      if exit_on_error and retcode != 0:
   302          exit(1)
   303  
   304  
   305  def check_shell_cmd(s, verbose=False):
   306      if verbose:
   307          print(s)
   308      output = subprocess.check_output(s, shell=True, stderr=subprocess.STDOUT).strip().decode("utf-8")
   309      if verbose:
   310          print(output.rstrip('\n'))
   311      return output
   312  
   313  
   314  def arg_parser():
   315      parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
   316      parser.add_argument('--bridge-prefix', help="bridge prefix", type=str, required=False, default='br-')
   317      parser.add_argument('--one-time-backup', help='A one time backup of filename', action='store_true', default=True, required=False)
   318      parser.add_argument('--activate', help='activate new configuration', action='store_true', default=False, required=False)
   319      parser.add_argument('--interface-to-bridge', help="interface to bridge", type=str, required=False)
   320      parser.add_argument('--bridge-name', help="bridge name", type=str, required=False)
   321      parser.add_argument('filename', help="interfaces(5) based filename")
   322      return parser
   323  
   324  
   325  def main(args):
   326      if args.bridge_name and args.interface_to_bridge is None:
   327          sys.stderr.write("error: --interface-to-bridge required when using --bridge-name\n")
   328          exit(1)
   329  
   330      if args.interface_to_bridge and args.bridge_name is None:
   331          sys.stderr.write("error: --bridge-name required when using --interface-to-bridge\n")
   332          exit(1)
   333  
   334      stanzas = []
   335      config_parser = NetworkInterfaceParser(args.filename)
   336      physical_interfaces = config_parser.physical_interfaces()
   337  
   338      # Bridging requires modifying 'auto' and 'iface' stanzas only.
   339      # Calling <iface>.bridge() will return a set of stanzas that cover
   340      # both of those stanzas. The 'elif' clause catches all the other
   341      # stanza types. The args.interface_to_bridge test is to bridge a
   342      # single interface only, which is only used for juju < 2.0. And if
   343      # that argument is specified then args.bridge_name takes
   344      # precendence over any args.bridge_prefix.
   345  
   346      for s in config_parser.stanzas():
   347          if s.is_logical_interface:
   348              add_auto_stanza = s.iface.name in physical_interfaces
   349              if args.interface_to_bridge and args.interface_to_bridge != s.iface.name:
   350                  if add_auto_stanza:
   351                      stanzas.append(AutoStanza(s.iface.name))
   352                  stanzas.append(s)
   353              else:
   354                  stanzas.extend(s.iface.bridge(args.bridge_prefix, args.bridge_name, add_auto_stanza))
   355          elif not s.is_physical_interface:
   356              stanzas.append(s)
   357  
   358      if not args.activate:
   359          print_stanzas(stanzas)
   360          exit(0)
   361  
   362      if args.one_time_backup:
   363          backup_file = "{}-before-add-juju-bridge".format(args.filename)
   364          if not os.path.isfile(backup_file):
   365              shutil.copy2(args.filename, backup_file)
   366  
   367      ifquery = "$(ifquery --interfaces={} --exclude=lo --list)".format(args.filename)
   368  
   369      print("**** Original configuration")
   370      print_shell_cmd("cat {}".format(args.filename))
   371      print_shell_cmd("ifconfig -a")
   372      print_shell_cmd("ifdown --exclude=lo --interfaces={} {}".format(args.filename, ifquery))
   373  
   374      print("**** Activating new configuration")
   375  
   376      with open(args.filename, 'w') as f:
   377          print_stanzas(stanzas, f)
   378          f.close()
   379  
   380      print_shell_cmd("cat {}".format(args.filename))
   381      print_shell_cmd("ifup --exclude=lo --interfaces={} {}".format(args.filename, ifquery))
   382      print_shell_cmd("ip link show up")
   383      print_shell_cmd("ifconfig -a")
   384      print_shell_cmd("ip route show")
   385      print_shell_cmd("brctl show")
   386  
   387  # This script re-renders an interfaces(5) file to add a bridge to
   388  # either all active interfaces, or a specific interface.
   389  
   390  if __name__ == '__main__':
   391      main(arg_parser().parse_args())
   392  `