github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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("/var/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 # These options are to be removed from a sub-interface and applied to 31 # the new bridged interface. 32 33 BRIDGE_ONLY_OPTIONS = {'address', 'gateway', 'netmask', 'dns-nameservers', 'dns-search', 'dns-sortlist'} 34 35 36 class SeekableIterator(object): 37 """An iterator that supports relative seeking.""" 38 39 def __init__(self, iterable): 40 self.iterable = iterable 41 self.index = 0 42 43 def __iter__(self): 44 return self 45 46 def next(self): # Python 2 47 try: 48 value = self.iterable[self.index] 49 self.index += 1 50 return value 51 except IndexError: 52 raise StopIteration 53 54 def __next__(self): # Python 3 55 return self.next() 56 57 def seek(self, n, relative=False): 58 if relative: 59 self.index += n 60 else: 61 self.index = n 62 if self.index < 0 or self.index >= len(self.iterable): 63 raise IndexError 64 65 66 class PhysicalInterface(object): 67 """Represents a physical ('auto') interface.""" 68 69 def __init__(self, definition): 70 self.name = definition.split()[1] 71 72 def __str__(self): 73 return self.name 74 75 76 class LogicalInterface(object): 77 """Represents a logical ('iface') interface.""" 78 79 def __init__(self, definition, options=None): 80 if not options: 81 options = [] 82 _, self.name, self.family, self.method = definition.split() 83 self.options = options 84 self.is_loopback = self.method == 'loopback' 85 self.is_bonded = [x for x in self.options if "bond-" in x] 86 self.has_bond_master_option, self.bond_master_options = self.has_option(['bond-master']) 87 self.is_alias = ":" in self.name 88 self.is_vlan = [x for x in self.options if x.startswith("vlan-raw-device")] 89 self.is_bridged, self.bridge_ports = self.has_option(['bridge_ports']) 90 self.has_auto_stanza = None 91 self.parent = None 92 93 def __str__(self): 94 return self.name 95 96 def has_option(self, options): 97 for o in self.options: 98 words = o.split() 99 ident = words[0] 100 if ident in options: 101 return True, words[1:] 102 return False, [] 103 104 @classmethod 105 def prune_options(cls, options, invalid_options): 106 result = [] 107 for o in options: 108 words = o.split() 109 if words[0] not in invalid_options: 110 result.append(o) 111 return result 112 113 # Returns an ordered set of stanzas to bridge this interface. 114 def _bridge(self, prefix, bridge_name): 115 if bridge_name is None: 116 bridge_name = prefix + self.name 117 # Note: the testing order here is significant. 118 if self.is_loopback or self.is_bridged or self.has_bond_master_option: 119 return self._bridge_unchanged() 120 elif self.is_alias: 121 if self.parent and self.parent.iface and self.parent.iface.is_bridged: 122 # if we didn't change the parent interface 123 # then we don't change the aliases neither. 124 return self._bridge_unchanged() 125 else: 126 return self._bridge_alias(bridge_name) 127 elif self.is_vlan: 128 return self._bridge_vlan(bridge_name) 129 elif self.is_bonded: 130 return self._bridge_bond(bridge_name) 131 else: 132 return self._bridge_device(bridge_name) 133 134 def _bridge_device(self, bridge_name): 135 stanzas = [] 136 if self.has_auto_stanza: 137 stanzas.append(AutoStanza(self.name)) 138 options = self.prune_options(self.options, BRIDGE_ONLY_OPTIONS) 139 stanzas.append(IfaceStanza(self.name, self.family, "manual", options)) 140 stanzas.append(AutoStanza(bridge_name)) 141 options = list(self.options) 142 options.append("bridge_ports {}".format(self.name)) 143 options = self.prune_options(options, ['mtu']) 144 stanzas.append(IfaceStanza(bridge_name, self.family, self.method, options)) 145 return stanzas 146 147 def _bridge_vlan(self, bridge_name): 148 stanzas = [] 149 if self.has_auto_stanza: 150 stanzas.append(AutoStanza(self.name)) 151 options = self.prune_options(self.options, BRIDGE_ONLY_OPTIONS) 152 stanzas.append(IfaceStanza(self.name, self.family, "manual", options)) 153 stanzas.append(AutoStanza(bridge_name)) 154 options = list(self.options) 155 options.append("bridge_ports {}".format(self.name)) 156 options = self.prune_options(options, ['mtu', 'vlan_id', 'vlan-raw-device']) 157 stanzas.append(IfaceStanza(bridge_name, self.family, self.method, options)) 158 return stanzas 159 160 def _bridge_alias(self, bridge_name): 161 stanzas = [] 162 if self.has_auto_stanza: 163 stanzas.append(AutoStanza(bridge_name)) 164 stanzas.append(IfaceStanza(bridge_name, self.family, self.method, list(self.options))) 165 return stanzas 166 167 def _bridge_bond(self, bridge_name): 168 stanzas = [] 169 if self.has_auto_stanza: 170 stanzas.append(AutoStanza(self.name)) 171 options = self.prune_options(self.options, BRIDGE_ONLY_OPTIONS) 172 stanzas.append(IfaceStanza(self.name, self.family, "manual", options)) 173 stanzas.append(AutoStanza(bridge_name)) 174 options = [x for x in self.options if not x.startswith("bond")] 175 options = self.prune_options(options, ['mtu']) 176 options.append("bridge_ports {}".format(self.name)) 177 stanzas.append(IfaceStanza(bridge_name, self.family, self.method, options)) 178 return stanzas 179 180 def _bridge_unchanged(self): 181 stanzas = [] 182 if self.has_auto_stanza: 183 stanzas.append(AutoStanza(self.name)) 184 stanzas.append(IfaceStanza(self.name, self.family, self.method, list(self.options))) 185 return stanzas 186 187 188 class Stanza(object): 189 """Represents one stanza together with all of its options.""" 190 191 def __init__(self, definition, options=None): 192 if not options: 193 options = [] 194 self.definition = definition 195 self.options = options 196 self.is_logical_interface = definition.startswith('iface ') 197 self.is_physical_interface = definition.startswith('auto ') 198 self.iface = None 199 self.phy = None 200 if self.is_logical_interface: 201 self.iface = LogicalInterface(definition, self.options) 202 if self.is_physical_interface: 203 self.phy = PhysicalInterface(definition) 204 205 def __str__(self): 206 return self.definition 207 208 209 class NetworkInterfaceParser(object): 210 """Parse a network interface file into a set of stanzas.""" 211 212 @classmethod 213 def is_stanza(cls, s): 214 return re.match(r'^(iface|mapping|auto|allow-|source)', s) 215 216 def __init__(self, filename): 217 self._stanzas = [] 218 with open(filename, 'r') as f: 219 lines = f.readlines() 220 line_iterator = SeekableIterator(lines) 221 for line in line_iterator: 222 if self.is_stanza(line): 223 stanza = self._parse_stanza(line, line_iterator) 224 self._stanzas.append(stanza) 225 physical_interfaces = self._physical_interfaces() 226 for s in self._stanzas: 227 if not s.is_logical_interface: 228 continue 229 s.iface.has_auto_stanza = s.iface.name in physical_interfaces 230 231 self._connect_aliases() 232 self._bridged_interfaces = self._find_bridged_ifaces() 233 234 def _parse_stanza(self, stanza_line, iterable): 235 stanza_options = [] 236 for line in iterable: 237 line = line.strip() 238 if line.startswith('#') or line == "": 239 continue 240 if self.is_stanza(line): 241 iterable.seek(-1, True) 242 break 243 stanza_options.append(line) 244 return Stanza(stanza_line.strip(), stanza_options) 245 246 def stanzas(self): 247 return [x for x in self._stanzas] 248 249 def _connect_aliases(self): 250 """Set a reference in the alias interfaces to its related interface""" 251 ifaces = {} 252 aliases = [] 253 for stanza in self._stanzas: 254 if stanza.iface is None: 255 continue 256 257 if stanza.iface.is_alias: 258 aliases.append(stanza) 259 else: 260 ifaces[stanza.iface.name] = stanza 261 262 for alias in aliases: 263 parent_name = alias.iface.name.split(':')[0] 264 if parent_name in ifaces: 265 alias.iface.parent = ifaces[parent_name] 266 267 def _find_bridged_ifaces(self): 268 bridged_ifaces = {} 269 for stanza in self._stanzas: 270 if not stanza.is_logical_interface: 271 continue 272 if stanza.iface.is_bridged: 273 bridged_ifaces[stanza.iface.name] = stanza.iface 274 return bridged_ifaces 275 276 def _physical_interfaces(self): 277 return {x.phy.name: x.phy for x in [y for y in self._stanzas if y.is_physical_interface]} 278 279 def __iter__(self): # class iter 280 for s in self._stanzas: 281 yield s 282 283 def _is_already_bridged(self, name, bridge_port): 284 iface = self._bridged_interfaces.get(name, None) 285 if iface: 286 return bridge_port in iface.bridge_ports 287 return False 288 289 def bridge(self, interface_names_to_bridge, bridge_prefix, bridge_name): 290 bridged_stanzas = [] 291 for s in self.stanzas(): 292 if s.is_logical_interface: 293 if s.iface.name not in interface_names_to_bridge: 294 if s.iface.has_auto_stanza: 295 bridged_stanzas.append(AutoStanza(s.iface.name)) 296 bridged_stanzas.append(s) 297 else: 298 existing_bridge_name = bridge_prefix + s.iface.name 299 if self._is_already_bridged(existing_bridge_name, s.iface.name): 300 if s.iface.has_auto_stanza: 301 bridged_stanzas.append(AutoStanza(s.iface.name)) 302 bridged_stanzas.append(s) 303 else: 304 bridged_stanzas.extend(s.iface._bridge(bridge_prefix, bridge_name)) 305 elif not s.is_physical_interface: 306 bridged_stanzas.append(s) 307 return bridged_stanzas 308 309 310 def uniq_append(dst, src): 311 for x in src: 312 if x not in dst: 313 dst.append(x) 314 return dst 315 316 317 def IfaceStanza(name, family, method, options): 318 """Convenience function to create a new "iface" stanza. 319 320 Maintains original options order but removes duplicates with the 321 exception of 'dns-*' options which are normalised as required by 322 resolvconf(8) and all the dns-* options are moved to the end. 323 324 """ 325 326 dns_search = [] 327 dns_nameserver = [] 328 dns_sortlist = [] 329 unique_options = [] 330 331 for o in options: 332 words = o.split() 333 ident = words[0] 334 if ident == "dns-nameservers": 335 dns_nameserver = uniq_append(dns_nameserver, words[1:]) 336 elif ident == "dns-search": 337 dns_search = uniq_append(dns_search, words[1:]) 338 elif ident == "dns-sortlist": 339 dns_sortlist = uniq_append(dns_sortlist, words[1:]) 340 elif o not in unique_options: 341 unique_options.append(o) 342 343 if dns_nameserver: 344 option = "dns-nameservers " + " ".join(dns_nameserver) 345 unique_options.append(option) 346 347 if dns_search: 348 option = "dns-search " + " ".join(dns_search) 349 unique_options.append(option) 350 351 if dns_sortlist: 352 option = "dns-sortlist " + " ".join(dns_sortlist) 353 unique_options.append(option) 354 355 return Stanza("iface {} {} {}".format(name, family, method), unique_options) 356 357 358 def AutoStanza(name): 359 # Convenience function to create a new "auto" stanza. 360 return Stanza("auto {}".format(name)) 361 362 363 def print_stanza(s, stream=sys.stdout): 364 print(s.definition, file=stream) 365 for o in s.options: 366 print(" ", o, file=stream) 367 368 369 def print_stanzas(stanzas, stream=sys.stdout): 370 n = len(stanzas) 371 for i, stanza in enumerate(stanzas): 372 print_stanza(stanza, stream) 373 if stanza.is_logical_interface and i + 1 < n: 374 print(file=stream) 375 376 377 def shell_cmd(s): 378 p = subprocess.Popen(s, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 379 out, err = p.communicate() 380 return [out, err, p.returncode] 381 382 383 def print_shell_cmd(s, verbose=True, exit_on_error=False): 384 if verbose: 385 print(s) 386 out, err, retcode = shell_cmd(s) 387 if out and len(out) > 0: 388 print(out.decode().rstrip('\n')) 389 if err and len(err) > 0: 390 print(err.decode().rstrip('\n')) 391 if exit_on_error and retcode != 0: 392 exit(1) 393 394 395 def check_shell_cmd(s, verbose=False): 396 if verbose: 397 print(s) 398 output = subprocess.check_output(s, shell=True, stderr=subprocess.STDOUT).strip().decode("utf-8") 399 if verbose: 400 print(output.rstrip('\n')) 401 return output 402 403 404 def arg_parser(): 405 parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) 406 parser.add_argument('--bridge-prefix', help="bridge prefix", type=str, required=False, default='br-') 407 parser.add_argument('--one-time-backup', help='A one time backup of filename', action='store_true', default=True, required=False) 408 parser.add_argument('--activate', help='activate new configuration', action='store_true', default=False, required=False) 409 parser.add_argument('--interfaces-to-bridge', help="interfaces to bridge; space delimited", type=str, required=True) 410 parser.add_argument('--bridge-name', help="bridge name", type=str, required=False) 411 parser.add_argument('filename', help="interfaces(5) based filename") 412 return parser 413 414 415 def main(args): 416 interfaces = args.interfaces_to_bridge.split() 417 418 if len(interfaces) == 0: 419 sys.stderr.write("error: no interfaces specified\n") 420 exit(1) 421 422 if args.bridge_name and len(interfaces) > 1: 423 sys.stderr.write("error: cannot use single bridge name '{}' against multiple interface names\n".format(args.bridge_name)) 424 exit(1) 425 426 parser = NetworkInterfaceParser(args.filename) 427 stanzas = parser.bridge(interfaces, args.bridge_prefix, args.bridge_name) 428 429 if not args.activate: 430 print_stanzas(stanzas) 431 exit(0) 432 433 if args.one_time_backup: 434 backup_file = "{}-before-add-juju-bridge".format(args.filename) 435 if not os.path.isfile(backup_file): 436 shutil.copy2(args.filename, backup_file) 437 438 ifquery = "$(ifquery --interfaces={} --exclude=lo --list)".format(args.filename) 439 440 print("**** Original configuration") 441 print_shell_cmd("cat {}".format(args.filename)) 442 print_shell_cmd("ifconfig -a") 443 print_shell_cmd("ifdown --exclude=lo --interfaces={} {}".format(args.filename, ifquery)) 444 445 print("**** Activating new configuration") 446 447 with open(args.filename, 'w') as f: 448 print_stanzas(stanzas, f) 449 f.close() 450 451 # On configurations that have bonds in 802.3ad mode there is a 452 # race condition betweeen an immediate ifdown then ifup. 453 # 454 # On the h/w I have a 'sleep 0.1' is sufficient but to accommodate 455 # other setups we arbitrarily choose something larger. We don't 456 # want to massively slow bootstrap down but, equally, 0.1 may be 457 # too small for other configurations. 458 459 for s in stanzas: 460 if s.is_logical_interface and s.iface.is_bonded: 461 print("working around https://bugs.launchpad.net/ubuntu/+source/ifenslave/+bug/1269921") 462 print("working around https://bugs.launchpad.net/juju-core/+bug/1594855") 463 print_shell_cmd("sleep 3") 464 break 465 466 print_shell_cmd("cat {}".format(args.filename)) 467 print_shell_cmd("ifup --exclude=lo --interfaces={} {}".format(args.filename, ifquery)) 468 print_shell_cmd("ip link show up") 469 print_shell_cmd("ifconfig -a") 470 print_shell_cmd("ip route show") 471 print_shell_cmd("brctl show") 472 473 # This script re-renders an interfaces(5) file to add a bridge to 474 # either all active interfaces, or a specific interface. 475 476 if __name__ == '__main__': 477 main(arg_parser().parse_args()) 478 `