1
// This file is auto generated. Edits will be lost.
9
const bridgeScriptName = "add-juju-bridge.py"
11
var bridgeScriptPath = path.Join("/var/tmp", bridgeScriptName)
13
const bridgeScriptPython = `#!/usr/bin/env python
15
# Copyright 2015 Canonical Ltd.
16
# Licensed under the AGPLv3, see LICENCE file for details.
19
# This file has been and should be formatted using pyfmt(1).
22
from __future__ import print_function
30
# These options are to be removed from a sub-interface and applied to
31
# the new bridged interface.
33
BRIDGE_ONLY_OPTIONS = {'address', 'gateway', 'netmask', 'dns-nameservers', 'dns-search', 'dns-sortlist'}
36
class SeekableIterator(object):
37
"""An iterator that supports relative seeking."""
39
def __init__(self, iterable):
40
self.iterable = iterable
46
def next(self): # Python 2
48
value = self.iterable[self.index]
54
def __next__(self): # Python 3
57
def seek(self, n, relative=False):
62
if self.index < 0 or self.index >= len(self.iterable):
66
class PhysicalInterface(object):
67
"""Represents a physical ('auto') interface."""
69
def __init__(self, definition):
70
self.name = definition.split()[1]
76
class LogicalInterface(object):
77
"""Represents a logical ('iface') interface."""
79
def __init__(self, definition, options=None):
82
_, self.name, self.family, self.method = definition.split()
83
self.options = options
84
self.is_bonded = [x for x in self.options if "bond-" in x]
85
self.is_alias = ":" in self.name
86
self.is_vlan = [x for x in self.options if x.startswith("vlan-raw-device")]
87
self.is_active = self.method == "dhcp" or self.method == "static"
88
self.is_bridged = [x for x in self.options if x.startswith("bridge_ports ")]
89
self.has_auto_stanza = None
96
def prune_options(cls, options, invalid_options):
100
if words[0] not in invalid_options:
104
# Returns an ordered set of stanzas to bridge this interface.
105
def bridge(self, prefix, bridge_name):
106
if bridge_name is None:
107
bridge_name = prefix + self.name
108
# Note: the testing order here is significant.
109
if not self.is_active or self.is_bridged:
110
return self._bridge_unchanged()
112
if self.parent and self.parent.iface and (not self.parent.iface.is_active or self.parent.iface.is_bridged):
113
# if we didn't change the parent interface
114
# then we don't change the aliases neither.
115
return self._bridge_unchanged()
117
return self._bridge_alias(bridge_name)
119
return self._bridge_vlan(bridge_name)
121
return self._bridge_bond(bridge_name)
123
return self._bridge_device(bridge_name)
125
def _bridge_device(self, bridge_name):
127
if self.has_auto_stanza:
128
stanzas.append(AutoStanza(self.name))
129
options = self.prune_options(self.options, BRIDGE_ONLY_OPTIONS)
130
stanzas.append(IfaceStanza(self.name, self.family, "manual", options))
131
stanzas.append(AutoStanza(bridge_name))
132
options = list(self.options)
133
options.append("bridge_ports {}".format(self.name))
134
options = self.prune_options(options, ['mtu'])
135
stanzas.append(IfaceStanza(bridge_name, self.family, self.method, options))
138
def _bridge_vlan(self, bridge_name):
140
if self.has_auto_stanza:
141
stanzas.append(AutoStanza(self.name))
142
options = self.prune_options(self.options, BRIDGE_ONLY_OPTIONS)
143
stanzas.append(IfaceStanza(self.name, self.family, "manual", options))
144
stanzas.append(AutoStanza(bridge_name))
145
options = list(self.options)
146
options.append("bridge_ports {}".format(self.name))
147
options = self.prune_options(options, ['mtu', 'vlan_id', 'vlan-raw-device'])
148
stanzas.append(IfaceStanza(bridge_name, self.family, self.method, options))
151
def _bridge_alias(self, bridge_name):
153
if self.has_auto_stanza:
154
stanzas.append(AutoStanza(bridge_name))
155
stanzas.append(IfaceStanza(bridge_name, self.family, self.method, list(self.options)))
158
def _bridge_bond(self, bridge_name):
160
if self.has_auto_stanza:
161
stanzas.append(AutoStanza(self.name))
162
options = self.prune_options(self.options, BRIDGE_ONLY_OPTIONS)
163
stanzas.append(IfaceStanza(self.name, self.family, "manual", options))
164
stanzas.append(AutoStanza(bridge_name))
165
options = [x for x in self.options if not x.startswith("bond")]
166
options = self.prune_options(options, ['mtu'])
167
options.append("bridge_ports {}".format(self.name))
168
stanzas.append(IfaceStanza(bridge_name, self.family, self.method, options))
171
def _bridge_unchanged(self):
173
if self.has_auto_stanza:
174
stanzas.append(AutoStanza(self.name))
175
stanzas.append(IfaceStanza(self.name, self.family, self.method, list(self.options)))
179
class Stanza(object):
180
"""Represents one stanza together with all of its options."""
182
def __init__(self, definition, options=None):
185
self.definition = definition
186
self.options = options
187
self.is_logical_interface = definition.startswith('iface ')
188
self.is_physical_interface = definition.startswith('auto ')
191
if self.is_logical_interface:
192
self.iface = LogicalInterface(definition, self.options)
193
if self.is_physical_interface:
194
self.phy = PhysicalInterface(definition)
197
return self.definition
200
class NetworkInterfaceParser(object):
201
"""Parse a network interface file into a set of stanzas."""
204
def is_stanza(cls, s):
205
return re.match(r'^(iface|mapping|auto|allow-|source)', s)
207
def __init__(self, filename):
209
with open(filename, 'r') as f:
210
lines = f.readlines()
211
line_iterator = SeekableIterator(lines)
212
for line in line_iterator:
213
if self.is_stanza(line):
214
stanza = self._parse_stanza(line, line_iterator)
215
self._stanzas.append(stanza)
216
physical_interfaces = self._physical_interfaces()
217
for s in self._stanzas:
218
if not s.is_logical_interface:
220
s.iface.has_auto_stanza = s.iface.name in physical_interfaces
222
self._connect_aliases()
224
def _parse_stanza(self, stanza_line, iterable):
226
for line in iterable:
228
if line.startswith('#') or line == "":
230
if self.is_stanza(line):
231
iterable.seek(-1, True)
233
stanza_options.append(line)
234
return Stanza(stanza_line.strip(), stanza_options)
237
return [x for x in self._stanzas]
239
def _connect_aliases(self):
240
"""Set a reference in the alias interfaces to its related interface"""
243
for stanza in self._stanzas:
244
if stanza.iface is None:
247
if stanza.iface.is_alias:
248
aliases.append(stanza)
250
ifaces[stanza.iface.name] = stanza
252
for alias in aliases:
253
parent_name = alias.iface.name.split(':')[0]
254
if parent_name in ifaces:
255
alias.iface.parent = ifaces[parent_name]
257
def _physical_interfaces(self):
258
return {x.phy.name: x.phy for x in [y for y in self._stanzas if y.is_physical_interface]}
260
def __iter__(self): # class iter
261
for s in self._stanzas:
265
def uniq_append(dst, src):
272
def IfaceStanza(name, family, method, options):
273
"""Convenience function to create a new "iface" stanza.
275
Maintains original options order but removes duplicates with the
276
exception of 'dns-*' options which are normlised as required by
277
resolvconf(8) and all the dns-* options are moved to the end.
289
if ident == "dns-nameservers":
290
dns_nameserver = uniq_append(dns_nameserver, words[1:])
291
elif ident == "dns-search":
292
dns_search = uniq_append(dns_search, words[1:])
293
elif ident == "dns-sortlist":
294
dns_sortlist = uniq_append(dns_sortlist, words[1:])
295
elif o not in unique_options:
296
unique_options.append(o)
299
option = "dns-nameservers " + " ".join(dns_nameserver)
300
unique_options.append(option)
303
option = "dns-search " + " ".join(dns_search)
304
unique_options.append(option)
307
option = "dns-sortlist " + " ".join(dns_sortlist)
308
unique_options.append(option)
310
return Stanza("iface {} {} {}".format(name, family, method), unique_options)
313
def AutoStanza(name):
314
# Convenience function to create a new "auto" stanza.
315
return Stanza("auto {}".format(name))
318
def print_stanza(s, stream=sys.stdout):
319
print(s.definition, file=stream)
321
print(" ", o, file=stream)
324
def print_stanzas(stanzas, stream=sys.stdout):
326
for i, stanza in enumerate(stanzas):
327
print_stanza(stanza, stream)
328
if stanza.is_logical_interface and i + 1 < n:
333
p = subprocess.Popen(s, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
334
out, err = p.communicate()
335
return [out, err, p.returncode]
338
def print_shell_cmd(s, verbose=True, exit_on_error=False):
341
out, err, retcode = shell_cmd(s)
342
if out and len(out) > 0:
343
print(out.decode().rstrip('\n'))
344
if err and len(err) > 0:
345
print(err.decode().rstrip('\n'))
346
if exit_on_error and retcode != 0:
350
def check_shell_cmd(s, verbose=False):
353
output = subprocess.check_output(s, shell=True, stderr=subprocess.STDOUT).strip().decode("utf-8")
355
print(output.rstrip('\n'))
360
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
361
parser.add_argument('--bridge-prefix', help="bridge prefix", type=str, required=False, default='br-')
362
parser.add_argument('--one-time-backup', help='A one time backup of filename', action='store_true', default=True, required=False)
363
parser.add_argument('--activate', help='activate new configuration', action='store_true', default=False, required=False)
364
parser.add_argument('--interface-to-bridge', help="interface to bridge", type=str, required=False)
365
parser.add_argument('--bridge-name', help="bridge name", type=str, required=False)
366
parser.add_argument('filename', help="interfaces(5) based filename")
371
if args.bridge_name and args.interface_to_bridge is None:
372
sys.stderr.write("error: --interface-to-bridge required when using --bridge-name\n")
375
if args.interface_to_bridge and args.bridge_name is None:
376
sys.stderr.write("error: --bridge-name required when using --interface-to-bridge\n")
380
config_parser = NetworkInterfaceParser(args.filename)
382
# Bridging requires modifying 'auto' and 'iface' stanzas only.
383
# Calling <iface>.bridge() will return a set of stanzas that cover
384
# both of those stanzas. The 'elif' clause catches all the other
385
# stanza types. The args.interface_to_bridge test is to bridge a
386
# single interface only, which is only used for juju < 2.0. And if
387
# that argument is specified then args.bridge_name takes
388
# precedence over any args.bridge_prefix.
390
for s in config_parser.stanzas():
391
if s.is_logical_interface:
392
if args.interface_to_bridge and args.interface_to_bridge != s.iface.name:
393
if s.iface.has_auto_stanza:
394
stanzas.append(AutoStanza(s.iface.name))
397
stanzas.extend(s.iface.bridge(args.bridge_prefix, args.bridge_name))
398
elif not s.is_physical_interface:
401
if not args.activate:
402
print_stanzas(stanzas)
405
if args.one_time_backup:
406
backup_file = "{}-before-add-juju-bridge".format(args.filename)
407
if not os.path.isfile(backup_file):
408
shutil.copy2(args.filename, backup_file)
410
ifquery = "$(ifquery --interfaces={} --exclude=lo --list)".format(args.filename)
412
print("**** Original configuration")
413
print_shell_cmd("cat {}".format(args.filename))
414
print_shell_cmd("ifconfig -a")
415
print_shell_cmd("ifdown --exclude=lo --interfaces={} {}".format(args.filename, ifquery))
417
print("**** Activating new configuration")
419
with open(args.filename, 'w') as f:
420
print_stanzas(stanzas, f)
423
# On configurations that have bonds in 802.3ad mode there is a
424
# race condition betweeen an immediate ifdown then ifup.
426
# On the h/w I have a 'sleep 0.1' is sufficient but to accommodate
427
# other setups we arbitrarily choose something larger. We don't
428
# want to massively slow bootstrap down but, equally, 0.1 may be
429
# too small for other configurations.
432
if s.is_logical_interface and s.iface.is_bonded:
433
print("working around https://bugs.launchpad.net/ubuntu/+source/ifenslave/+bug/1269921")
434
print("working around https://bugs.launchpad.net/juju-core/+bug/1594855")
435
print_shell_cmd("sleep 3")
438
print_shell_cmd("cat {}".format(args.filename))
439
print_shell_cmd("ifup --exclude=lo --interfaces={} {}".format(args.filename, ifquery))
440
print_shell_cmd("ip link show up")
441
print_shell_cmd("ifconfig -a")
442
print_shell_cmd("ip route show")
443
print_shell_cmd("brctl show")
445
# This script re-renders an interfaces(5) file to add a bridge to
446
# either all active interfaces, or a specific interface.
448
if __name__ == '__main__':
449
main(arg_parser().parse_args())