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 `