3 # Copyright (c) 2008,2009 Citrix Systems, Inc.
4 # Copyright (c) 2009 Nicira Networks.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU Lesser General Public License as published
8 # by the Free Software Foundation; version 2.1 only. with the special
9 # exception on linking described in file LICENSE.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Lesser General Public License for more details.
18 %(command-name)s <PIF> up
19 %(command-name)s <PIF> down
20 %(command-name)s [<PIF>] rewrite
21 %(command-name)s --force <BRIDGE> up
22 %(command-name)s --force <BRIDGE> down
23 %(command-name)s --force <BRIDGE> rewrite --device=<INTERFACE> <CONFIG>
24 %(command-name)s --force all down
26 where <PIF> is one of:
27 --session <SESSION-REF> --pif <PIF-REF>
29 and <CONFIG> is one of:
31 --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
34 --session A session reference to use to access the xapi DB
35 --pif A PIF reference within the session.
36 --pif-uuid The UUID of a PIF.
37 --force An interface name.
41 # Undocumented parameters for test & dev:
43 # --output-directory=<DIR> Write configuration to <DIR>. Also disables actually
44 # raising/lowering the interfaces
49 # 1. Every pif belongs to exactly one network
50 # 2. Every network has zero or one pifs
51 # 3. A network may have an associated bridge, allowing vifs to be attached
52 # 4. A network may be bridgeless (there's no point having a bridge over a storage pif)
55 import os, sys, getopt, time, signal
60 from xml.dom.minidom import getDOMImplementation
61 from xml.dom.minidom import parse as parseXML
63 output_directory = None
68 vswitch_state_dir = "/var/lib/openvswitch/"
69 dbcache_file = vswitch_state_dir + "dbcache"
72 # Debugging and Logging.
76 return output_directory is not None
84 def log_pif_action(action, pif):
85 pifrec = db.get_pif_record(pif)
87 rec['uuid'] = pifrec['uuid']
88 rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
89 rec['action'] = action
90 rec['pif_netdev_name'] = pif_netdev_name(pif)
91 rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
92 log("%(message)s: %(pif_netdev_name)s configured as %(ip_configuration_mode)s" % rec)
95 def run_command(command):
96 log("Running command: " + ' '.join(command))
97 rc = os.spawnl(os.P_WAIT, command[0], *command)
99 log("Command failed %d: " % rc + ' '.join(command))
107 class Usage(Exception):
108 def __init__(self, msg):
109 Exception.__init__(self)
112 class Error(Exception):
113 def __init__(self, msg):
114 Exception.__init__(self)
118 # Configuration File Handling.
121 class ConfigurationFile(object):
122 """Write a file, tracking old and new versions.
124 Supports writing a new version of a file and applying and
125 reverting those changes.
128 __STATE = {"OPEN":"OPEN",
129 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
130 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
132 def __init__(self, fname, path="/etc/sysconfig/network-scripts"):
134 self.__state = self.__STATE['OPEN']
139 dirname = output_directory
143 self.__path = os.path.join(dirname, fname)
144 self.__oldpath = os.path.join(dirname, "." + fname + ".xapi-old")
145 self.__newpath = os.path.join(dirname, "." + fname + ".xapi-new")
146 self.__unlink = False
148 self.__f = open(self.__newpath, "w")
150 def attach_child(self, child):
151 self.__children.append(child)
158 return open(self.path()).readlines()
162 def write(self, args):
163 if self.__state != self.__STATE['OPEN']:
164 raise Error("Attempt to write to file in state %s" % self.__state)
168 if self.__state != self.__STATE['OPEN']:
169 raise Error("Attempt to unlink file in state %s" % self.__state)
172 self.__state = self.__STATE['NOT-APPLIED']
175 if self.__state != self.__STATE['OPEN']:
176 raise Error("Attempt to close file in state %s" % self.__state)
179 self.__state = self.__STATE['NOT-APPLIED']
182 if self.__state != self.__STATE['NOT-APPLIED']:
183 raise Error("Attempt to compare file in state %s" % self.__state)
188 if self.__state != self.__STATE['NOT-APPLIED']:
189 raise Error("Attempt to apply configuration from state %s" % self.__state)
191 for child in self.__children:
194 log("Applying changes to %s configuration" % self.__fname)
196 # Remove previous backup.
197 if os.access(self.__oldpath, os.F_OK):
198 os.unlink(self.__oldpath)
200 # Save current configuration.
201 if os.access(self.__path, os.F_OK):
202 os.link(self.__path, self.__oldpath)
203 os.unlink(self.__path)
205 # Apply new configuration.
206 assert(os.path.exists(self.__newpath))
207 if not self.__unlink:
208 os.link(self.__newpath, self.__path)
210 pass # implicit unlink of original file
212 # Remove temporary file.
213 os.unlink(self.__newpath)
215 self.__state = self.__STATE['APPLIED']
218 if self.__state != self.__STATE['APPLIED']:
219 raise Error("Attempt to revert configuration from state %s" % self.__state)
221 for child in self.__children:
224 log("Reverting changes to %s configuration" % self.__fname)
226 # Remove existing new configuration
227 if os.access(self.__newpath, os.F_OK):
228 os.unlink(self.__newpath)
230 # Revert new configuration.
231 if os.access(self.__path, os.F_OK):
232 os.link(self.__path, self.__newpath)
233 os.unlink(self.__path)
235 # Revert to old configuration.
236 if os.access(self.__oldpath, os.F_OK):
237 os.link(self.__oldpath, self.__path)
238 os.unlink(self.__oldpath)
240 # Leave .*.xapi-new as an aid to debugging.
242 self.__state = self.__STATE['REVERTED']
245 if self.__state != self.__STATE['APPLIED']:
246 raise Error("Attempt to commit configuration from state %s" % self.__state)
248 for child in self.__children:
251 log("Committing changes to %s configuration" % self.__fname)
253 if os.access(self.__oldpath, os.F_OK):
254 os.unlink(self.__oldpath)
255 if os.access(self.__newpath, os.F_OK):
256 os.unlink(self.__newpath)
258 self.__state = self.__STATE['COMMITTED']
261 # Helper functions for encoding/decoding database attributes to/from XML.
264 def str_to_xml(xml, parent, tag, val):
265 e = xml.createElement(tag)
266 parent.appendChild(e)
267 v = xml.createTextNode(val)
270 def getText(nodelist):
272 for node in nodelist:
273 if node.nodeType == node.TEXT_NODE:
276 return getText(n.childNodes).strip()
278 def bool_to_xml(xml, parent, tag, val):
280 str_to_xml(xml, parent, tag, "True")
282 str_to_xml(xml, parent, tag, "False")
283 def bool_from_xml(n):
290 raise Error("Unknown boolean value %s" % s)
292 def strlist_to_xml(xml, parent, ltag, itag, val):
293 e = xml.createElement(ltag)
294 parent.appendChild(e)
296 c = xml.createElement(itag)
298 cv = xml.createTextNode(v)
300 def strlist_from_xml(n, ltag, itag):
302 for n in n.childNodes:
303 if n.nodeName == itag:
304 ret.append(str_from_xml(n))
307 def otherconfig_to_xml(xml, parent, val, attrs):
308 otherconfig = xml.createElement("other_config")
309 parent.appendChild(otherconfig)
310 for n,v in val.items():
312 raise Error("Unknown other-config attribute: %s" % n)
313 str_to_xml(xml, otherconfig, n, v)
314 def otherconfig_from_xml(n, attrs):
316 for n in n.childNodes:
317 if n.nodeName in attrs:
318 ret[n.nodeName] = str_from_xml(n)
322 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
324 # Each object is defined by a dictionary mapping an attribute name in
325 # the xapi database to a tuple containing two items:
326 # - a function which takes this attribute and encodes it as XML.
327 # - a function which takes XML and decocdes it into a value.
329 # other-config attributes are specified as a simple array of strings
332 VLAN_XML_TAG = "vlan"
333 BOND_XML_TAG = "bond"
334 NETWORK_XML_TAG = "network"
336 ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
338 PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
339 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
340 ETHTOOL_OTHERCONFIG_ATTRS
342 PIF_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
343 'management': (bool_to_xml,bool_from_xml),
344 'network': (str_to_xml,str_from_xml),
345 'device': (str_to_xml,str_from_xml),
346 'bond_master_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
347 lambda n: strlist_from_xml(n, 'bond_master_of', 'slave')),
348 'bond_slave_of': (str_to_xml,str_from_xml),
349 'VLAN': (str_to_xml,str_from_xml),
350 'VLAN_master_of': (str_to_xml,str_from_xml),
351 'VLAN_slave_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
352 lambda n: strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
353 'ip_configuration_mode': (str_to_xml,str_from_xml),
354 'IP': (str_to_xml,str_from_xml),
355 'netmask': (str_to_xml,str_from_xml),
356 'gateway': (str_to_xml,str_from_xml),
357 'DNS': (str_to_xml,str_from_xml),
358 'MAC': (str_to_xml,str_from_xml),
359 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, PIF_OTHERCONFIG_ATTRS),
360 lambda n: otherconfig_from_xml(n, PIF_OTHERCONFIG_ATTRS)),
362 # Special case: We write the current value
363 # PIF.currently-attached to the cache but since it will
364 # not be valid when we come to use the cache later
365 # (i.e. after a reboot) we always read it as False.
366 'currently_attached': (bool_to_xml, lambda n: False),
369 VLAN_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
370 'tagged_PIF': (str_to_xml,str_from_xml),
371 'untagged_PIF': (str_to_xml,str_from_xml),
374 BOND_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
375 'master': (str_to_xml,str_from_xml),
376 'slaves': (lambda x, p, t, v: strlist_to_xml(x, p, 'slaves', 'slave', v),
377 lambda n: strlist_from_xml(n, 'slaves', 'slave')),
380 NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + ETHTOOL_OTHERCONFIG_ATTRS
382 NETWORK_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
383 'bridge': (str_to_xml,str_from_xml),
384 'PIFs': (lambda x, p, t, v: strlist_to_xml(x, p, 'PIFs', 'PIF', v),
385 lambda n: strlist_from_xml(n, 'PIFs', 'PIF')),
386 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, NETWORK_OTHERCONFIG_ATTRS),
387 lambda n: otherconfig_from_xml(n, NETWORK_OTHERCONFIG_ATTRS)),
390 class DatabaseCache(object):
391 def __read_xensource_inventory(self):
392 filename = "/etc/xensource-inventory"
393 f = open(filename, "r")
394 lines = [x.strip("\n") for x in f.readlines()]
397 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
398 defs = [ (a, b.strip("'")) for (a,b) in defs ]
401 def __pif_on_host(self,pif):
402 return self.__pifs.has_key(pif)
404 def __get_pif_records_from_xapi(self, session, host):
406 for (p,rec) in session.xenapi.PIF.get_all_records().items():
407 if rec['host'] != host:
411 self.__pifs[p][f] = rec[f]
412 self.__pifs[p]['other_config'] = {}
413 for f in PIF_OTHERCONFIG_ATTRS:
414 if not rec['other_config'].has_key(f): continue
415 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
417 def __get_vlan_records_from_xapi(self, session):
419 for v in session.xenapi.VLAN.get_all():
420 rec = session.xenapi.VLAN.get_record(v)
421 if not self.__pif_on_host(rec['untagged_PIF']):
425 self.__vlans[v][f] = rec[f]
427 def __get_bond_records_from_xapi(self, session):
429 for b in session.xenapi.Bond.get_all():
430 rec = session.xenapi.Bond.get_record(b)
431 if not self.__pif_on_host(rec['master']):
435 self.__bonds[b][f] = rec[f]
437 def __get_network_records_from_xapi(self, session):
439 for n in session.xenapi.network.get_all():
440 rec = session.xenapi.network.get_record(n)
441 self.__networks[n] = {}
442 for f in NETWORK_ATTRS:
444 # drop PIFs on other hosts
445 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
447 self.__networks[n][f] = rec[f]
448 self.__networks[n]['other_config'] = {}
449 for f in NETWORK_OTHERCONFIG_ATTRS:
450 if not rec['other_config'].has_key(f): continue
451 self.__networks[n]['other_config'][f] = rec['other_config'][f]
453 def __to_xml(self, xml, parent, key, ref, rec, attrs):
454 """Encode a database object as XML"""
455 e = xml.createElement(key)
456 parent.appendChild(e)
458 e.setAttribute('ref', ref)
460 for n,v in rec.items():
465 raise Error("Unknown attribute %s" % n)
466 def __from_xml(self, e, attrs):
467 """Decode a database object from XML"""
468 ref = e.attributes['ref'].value
470 for n in e.childNodes:
471 if n.nodeName in attrs:
472 _,h = attrs[n.nodeName]
473 rec[n.nodeName] = h(n)
476 def __init__(self, session_ref=None, cache_file=None):
477 if session_ref and cache_file:
478 raise Error("can't specify session reference and cache file")
479 if cache_file == None:
480 session = XenAPI.xapi_local()
483 log("No session ref given on command line, logging in.")
484 session.xenapi.login_with_password("root", "")
486 session._session = session_ref
490 inventory = self.__read_xensource_inventory()
491 assert(inventory.has_key('INSTALLATION_UUID'))
492 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
494 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
496 self.__get_pif_records_from_xapi(session, host)
498 self.__get_vlan_records_from_xapi(session)
499 self.__get_bond_records_from_xapi(session)
500 self.__get_network_records_from_xapi(session)
503 session.xenapi.session.logout()
505 log("Loading xapi database cache from %s" % cache_file)
507 xml = parseXML(cache_file)
514 assert(len(xml.childNodes) == 1)
515 toplevel = xml.childNodes[0]
517 assert(toplevel.nodeName == "xenserver-network-configuration")
519 for n in toplevel.childNodes:
520 if n.nodeName == "#text":
522 elif n.nodeName == PIF_XML_TAG:
523 (ref,rec) = self.__from_xml(n, PIF_ATTRS)
524 self.__pifs[ref] = rec
525 elif n.nodeName == BOND_XML_TAG:
526 (ref,rec) = self.__from_xml(n, BOND_ATTRS)
527 self.__bonds[ref] = rec
528 elif n.nodeName == VLAN_XML_TAG:
529 (ref,rec) = self.__from_xml(n, VLAN_ATTRS)
530 self.__vlans[ref] = rec
531 elif n.nodeName == NETWORK_XML_TAG:
532 (ref,rec) = self.__from_xml(n, NETWORK_ATTRS)
533 self.__networks[ref] = rec
535 raise Error("Unknown XML element %s" % n.nodeName)
537 def save(self, cache_file):
539 xml = getDOMImplementation().createDocument(
540 None, "xenserver-network-configuration", None)
541 for (ref,rec) in self.__pifs.items():
542 self.__to_xml(xml, xml.documentElement, PIF_XML_TAG, ref, rec, PIF_ATTRS)
543 for (ref,rec) in self.__bonds.items():
544 self.__to_xml(xml, xml.documentElement, BOND_XML_TAG, ref, rec, BOND_ATTRS)
545 for (ref,rec) in self.__vlans.items():
546 self.__to_xml(xml, xml.documentElement, VLAN_XML_TAG, ref, rec, VLAN_ATTRS)
547 for (ref,rec) in self.__networks.items():
548 self.__to_xml(xml, xml.documentElement, NETWORK_XML_TAG, ref, rec,
551 f = open(cache_file, 'w')
552 f.write(xml.toprettyxml())
555 def get_pif_by_uuid(self, uuid):
556 pifs = map(lambda (ref,rec): ref,
557 filter(lambda (ref,rec): uuid == rec['uuid'],
558 self.__pifs.items()))
560 raise Error("Unknown PIF \"%s\"" % uuid)
562 raise Error("Non-unique PIF \"%s\"" % uuid)
566 def get_pifs_by_device(self, device):
567 return map(lambda (ref,rec): ref,
568 filter(lambda (ref,rec): rec['device'] == device,
569 self.__pifs.items()))
571 def get_pif_by_bridge(self, bridge):
572 networks = map(lambda (ref,rec): ref,
573 filter(lambda (ref,rec): rec['bridge'] == bridge,
574 self.__networks.items()))
575 if len(networks) == 0:
576 raise Error("No matching network \"%s\"" % bridge)
579 for network in networks:
580 nwrec = self.get_network_record(network)
581 for pif in nwrec['PIFs']:
582 pifrec = self.get_pif_record(pif)
584 raise Error("Multiple PIFs on host for network %s" % (bridge))
587 raise Error("No PIF on host for network %s" % (bridge))
590 def get_pif_record(self, pif):
591 if self.__pifs.has_key(pif):
592 return self.__pifs[pif]
593 raise Error("Unknown PIF \"%s\" (get_pif_record)" % pif)
594 def get_all_pifs(self):
596 def pif_exists(self, pif):
597 return self.__pifs.has_key(pif)
599 def get_management_pif(self):
600 """ Returns the management pif on host
602 all = self.get_all_pifs()
604 pifrec = self.get_pif_record(pif)
605 if pifrec['management']: return pif
608 def get_network_record(self, network):
609 if self.__networks.has_key(network):
610 return self.__networks[network]
611 raise Error("Unknown network \"%s\"" % network)
612 def get_all_networks(self):
613 return self.__networks
615 def get_bond_record(self, bond):
616 if self.__bonds.has_key(bond):
617 return self.__bonds[bond]
621 def get_vlan_record(self, vlan):
622 if self.__vlans.has_key(vlan):
623 return self.__vlans[vlan]
628 # Boot from Network filesystem or device.
631 def check_allowed(pif):
632 """Determine whether interface-reconfigure should be manipulating this PIF.
634 Used to prevent system PIFs (such as network root disk) from being interfered with.
637 pifrec = db.get_pif_record(pif)
639 f = open("/proc/ardence")
640 macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
642 if len(macline) == 1:
643 p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
644 if p.match(macline[0]):
645 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
652 # Bare Network Devices -- network devices without IP configuration
655 def netdev_exists(netdev):
656 return os.path.exists("/sys/class/net/" + netdev)
658 def pif_netdev_name(pif):
659 """Get the netdev name for a PIF."""
661 pifrec = db.get_pif_record(pif)
664 return "%(device)s.%(VLAN)s" % pifrec
666 return pifrec['device']
668 def netdev_down(netdev):
669 """Bring down a bare network device"""
672 if not netdev_exists(netdev):
673 log("netdev: down: device %s does not exist, ignoring" % netdev)
675 run_command(["/sbin/ifconfig", netdev, 'down'])
677 def netdev_up(netdev, mtu=None):
678 """Bring up a bare network device"""
681 if not netdev_exists(netdev):
682 raise Error("netdev: up: device %s does not exist" % netdev)
689 run_command(["/sbin/ifconfig", netdev, 'up'] + mtu)
691 def netdev_remap_name(pif, already_renamed=[]):
692 """Check whether 'pif' exists and has the correct MAC.
693 If not, try to find a device with the correct MAC and rename it.
694 'already_renamed' is used to avoid infinite recursion.
700 file = open(name, 'r')
701 return file.readline().rstrip('\n')
706 def get_netdev_mac(device):
708 return read1("/sys/class/net/%s/address" % device)
710 # Probably no such device.
713 def get_netdev_tx_queue_len(device):
715 return int(read1("/sys/class/net/%s/tx_queue_len" % device))
717 # Probably no such device.
720 def get_netdev_by_mac(mac):
721 for device in os.listdir("/sys/class/net"):
722 dev_mac = get_netdev_mac(device)
723 if (dev_mac and mac.lower() == dev_mac.lower() and
724 get_netdev_tx_queue_len(device)):
728 def rename_netdev(old_name, new_name):
729 log("Changing the name of %s to %s" % (old_name, new_name))
730 run_command(['/sbin/ifconfig', old_name, 'down'])
731 if not run_command(['/sbin/ip', 'link', 'set', old_name, 'name', new_name]):
732 raise Error("Could not rename %s to %s" % (old_name, new_name))
734 pifrec = db.get_pif_record(pif)
735 device = pifrec['device']
738 # Is there a network device named 'device' at all?
739 device_exists = netdev_exists(device)
741 # Yes. Does it have MAC 'mac'?
742 found_mac = get_netdev_mac(device)
743 if found_mac and mac.lower() == found_mac.lower():
744 # Yes, everything checks out the way we want. Nothing to do.
747 log("No network device %s" % device)
749 # What device has MAC 'mac'?
750 cur_device = get_netdev_by_mac(mac)
752 log("No network device has MAC %s" % mac)
755 # First rename 'device', if it exists, to get it out of the way
756 # for 'cur_device' to replace it.
758 rename_netdev(device, "dev%d" % random.getrandbits(24))
760 # Rename 'cur_device' to 'device'.
761 rename_netdev(cur_device, device)
764 # IP Network Devices -- network devices with IP configuration
767 def pif_ipdev_name(pif):
768 """Return the ipdev name associated with pif"""
769 pifrec = db.get_pif_record(pif)
770 nwrec = db.get_network_record(pifrec['network'])
773 # TODO: sanity check that nwrec['bridgeless'] != 'true'
774 return nwrec['bridge']
776 # TODO: sanity check that nwrec['bridgeless'] == 'true'
777 return pif_netdev_name(pif)
780 """Bring down a network interface"""
783 if not netdev_exists(netdev):
784 log("ifdown: device %s does not exist, ignoring" % netdev)
786 if not os.path.exists("/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
787 log("ifdown: device %s exists but ifcfg %s does not" % (netdev,netdev))
789 run_command(["/sbin/ifdown", netdev])
792 """Bring up a network interface"""
795 if not netdev_exists(netdev):
796 raise Error("ifup: device %s does not exist, ignoring" % netdev)
797 if not os.path.exists("/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
798 raise Error("ifup: device %s exists but ifcfg-%s does not" % (netdev,netdev))
799 run_command(["/sbin/ifup", netdev])
805 def pif_bridge_name(pif):
806 """Return the bridge name of a pif.
808 PIF must not be a VLAN and must be a bridged PIF."""
810 pifrec = db.get_pif_record(pif)
813 raise Error("PIF %(uuid)s cannot be a bridge, VLAN is %(VLAN)s" % pifrec)
815 nwrec = db.get_network_record(pifrec['network'])
818 return nwrec['bridge']
820 raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
826 def pif_currently_in_use(pif):
827 """Determine if a PIF is currently in use.
829 A PIF is determined to be currently in use if
830 - PIF.currently-attached is true
831 - Any bond master is currently attached
832 - Any VLAN master is currently attached
834 rec = db.get_pif_record(pif)
835 if rec['currently_attached']:
836 log("configure_datapath: %s is currently attached" % (pif_netdev_name(pif)))
838 for b in pif_get_bond_masters(pif):
839 if pif_currently_in_use(b):
840 log("configure_datapath: %s is in use by BOND master %s" % (pif_netdev_name(pif),pif_netdev_name(b)))
842 for v in pif_get_vlan_masters(pif):
843 if pif_currently_in_use(v):
844 log("configure_datapath: %s is in use by VLAN master %s" % (pif_netdev_name(pif),pif_netdev_name(v)))
852 def ethtool_settings(oc):
854 if oc.has_key('ethtool-speed'):
855 val = oc['ethtool-speed']
856 if val in ["10", "100", "1000"]:
857 settings += ['speed', val]
859 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
860 if oc.has_key('ethtool-duplex'):
861 val = oc['ethtool-duplex']
862 if val in ["10", "100", "1000"]:
863 settings += ['duplex', 'val']
865 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
866 if oc.has_key('ethtool-autoneg'):
867 val = oc['ethtool-autoneg']
868 if val in ["true", "on"]:
869 settings += ['autoneg', 'on']
870 elif val in ["false", "off"]:
871 settings += ['autoneg', 'off']
873 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
875 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
876 if oc.has_key("ethtool-" + opt):
877 val = oc["ethtool-" + opt]
878 if val in ["true", "on"]:
879 offload += [opt, 'on']
880 elif val in ["false", "off"]:
881 offload += [opt, 'off']
883 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
884 return settings,offload
887 if oc.has_key('mtu'):
889 int(oc['mtu']) # Check that the value is an integer
891 except ValueError, x:
892 log("Invalid value for mtu = %s" % oc['mtu'])
898 def pif_get_bond_masters(pif):
899 """Returns a list of PIFs which are bond masters of this PIF"""
901 pifrec = db.get_pif_record(pif)
903 bso = pifrec['bond_slave_of']
905 # bond-slave-of is currently a single reference but in principle a
906 # PIF could be a member of several bonds which are not
907 # concurrently attached. Be robust to this possibility.
908 if not bso or bso == "OpaqueRef:NULL":
910 elif not type(bso) == list:
913 bondrecs = [db.get_bond_record(bond) for bond in bso]
914 bondrecs = [rec for rec in bondrecs if rec]
916 return [bond['master'] for bond in bondrecs]
918 def pif_get_bond_slaves(pif):
919 """Returns a list of PIFs which make up the given bonded pif."""
921 pifrec = db.get_pif_record(pif)
923 bmo = pifrec['bond_master_of']
925 raise Error("Bond-master-of contains too many elements")
930 bondrec = db.get_bond_record(bmo[0])
932 raise Error("No bond record for bond master PIF")
934 return bondrec['slaves']
940 def pif_is_vlan(pif):
941 return db.get_pif_record(pif)['VLAN'] != '-1'
943 def pif_get_vlan_slave(pif):
944 """Find the PIF which is the VLAN slave of pif.
946 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
948 pifrec = db.get_pif_record(pif)
950 vlan = pifrec['VLAN_master_of']
951 if not vlan or vlan == "OpaqueRef:NULL":
952 raise Error("PIF is not a VLAN master")
954 vlanrec = db.get_vlan_record(vlan)
956 raise Error("No VLAN record found for PIF")
958 return vlanrec['tagged_PIF']
960 def pif_get_vlan_masters(pif):
961 """Returns a list of PIFs which are VLANs on top of the given pif."""
963 pifrec = db.get_pif_record(pif)
964 vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
965 return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])]
968 # IP device configuration
971 def ipdev_configure_static_routes(interface, oc, f):
972 """Open a route-<interface> file for static routes.
974 Opens the static routes configuration file for interface and writes one
975 line for each route specified in the network's other config "static-routes" value.
977 interface ( RO): xenbr1
978 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
980 Then route-xenbr1 should be
981 172.16.0.0/15 via 192.168.0.3 dev xenbr1
982 172.18.0.0/16 via 192.168.0.4 dev xenbr1
984 fname = "route-%s" % interface
985 if oc.has_key('static-routes'):
986 # The key is present - extract comma seperates entries
987 lines = oc['static-routes'].split(',')
989 # The key is not present, i.e. there are no static routes
992 child = ConfigurationFile(fname)
993 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
994 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
998 network, masklen, gateway = l.split('/')
999 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1001 f.attach_child(child)
1004 except ValueError, e:
1005 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1007 def ipdev_open_ifcfg(pif):
1008 ipdev = pif_ipdev_name(pif)
1010 log("Writing network configuration for %s" % ipdev)
1012 f = ConfigurationFile("ifcfg-%s" % ipdev)
1014 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1015 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1016 f.write("XEMANAGED=yes\n")
1017 f.write("DEVICE=%s\n" % ipdev)
1018 f.write("ONBOOT=no\n")
1022 def ipdev_configure_network(pif):
1023 """Write the configuration file for a network.
1025 Writes configuration derived from the network object into the relevant
1026 ifcfg file. The configuration file is passed in, but if the network is
1027 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1029 This routine may also write ifcfg files of the networks corresponding to other PIFs
1030 in order to maintain consistency.
1033 pif: Opaque_ref of pif
1034 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1037 pifrec = db.get_pif_record(pif)
1038 nwrec = db.get_network_record(pifrec['network'])
1040 ipdev = pif_ipdev_name(pif)
1042 f = ipdev_open_ifcfg(pif)
1044 mode = pifrec['ip_configuration_mode']
1045 log("Configuring %s using %s configuration" % (ipdev, mode))
1048 if pifrec.has_key('other_config'):
1049 oc = pifrec['other_config']
1051 f.write("TYPE=Ethernet\n")
1052 if pifrec['ip_configuration_mode'] == "DHCP":
1053 f.write("BOOTPROTO=dhcp\n")
1054 f.write("PERSISTENT_DHCLIENT=yes\n")
1055 elif pifrec['ip_configuration_mode'] == "Static":
1056 f.write("BOOTPROTO=none\n")
1057 f.write("NETMASK=%(netmask)s\n" % pifrec)
1058 f.write("IPADDR=%(IP)s\n" % pifrec)
1059 f.write("GATEWAY=%(gateway)s\n" % pifrec)
1060 elif pifrec['ip_configuration_mode'] == "None":
1061 f.write("BOOTPROTO=none\n")
1063 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1065 if nwrec.has_key('other_config'):
1066 settings,offload = ethtool_settings(nwrec['other_config'])
1068 f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
1070 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
1072 mtu = mtu_setting(nwrec['other_config'])
1074 f.write("MTU=%s\n" % mtu)
1076 ipdev_configure_static_routes(ipdev, nwrec['other_config'], f)
1078 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1079 ServerList = pifrec['DNS'].split(",")
1080 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1081 if oc and oc.has_key('domain'):
1082 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1084 # We only allow one ifcfg-* to have PEERDNS=yes and there can be
1085 # only one GATEWAYDEV in /etc/sysconfig/network.
1087 # The peerdns pif will be the one with
1088 # pif::other-config:peerdns=true, or the mgmt pif if none have
1091 # The gateway pif will be the one with
1092 # pif::other-config:defaultroute=true, or the mgmt pif if none
1095 # Work out which pif on this host should be the one with
1096 # PEERDNS=yes and which should be the GATEWAYDEV
1098 # Note: we prune out the bond master pif (if it exists). This is
1099 # because when we are called to bring up an interface with a bond
1100 # master, it is implicit that we should bring down that master.
1101 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1102 not __pif in pif_get_bond_masters(pif) ]
1103 other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1106 defaultroute_pif = None
1108 # loop through all the pifs on this host looking for one with
1109 # other-config:peerdns = true, and one with
1110 # other-config:default-route=true
1111 for __pif in pifs_on_host:
1112 __pifrec = db.get_pif_record(__pif)
1113 __oc = __pifrec['other_config']
1114 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1115 if peerdns_pif == None:
1118 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1119 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1120 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1121 if defaultroute_pif == None:
1122 defaultroute_pif = __pif
1124 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1125 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1127 # If no pif is explicitly specified then use the mgmt pif for
1128 # peerdns/defaultroute.
1129 if peerdns_pif == None:
1130 peerdns_pif = management_pif
1131 if defaultroute_pif == None:
1132 defaultroute_pif = management_pif
1134 # Update all the other network's ifcfg files and ensure
1136 for __pif in other_pifs_on_host:
1137 __f = ipdev_open_ifcfg(__pif)
1138 peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1139 lines = __f.readlines()
1141 if not peerdns_line_wanted in lines:
1142 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1144 if not line.lstrip().startswith('PEERDNS'):
1146 log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1147 __f.write(peerdns_line_wanted)
1152 # There is no need to change this ifcfg file. So don't attach_child.
1155 # ... and for this pif too
1156 f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1159 fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1160 for line in fnetwork.readlines():
1161 if line.lstrip().startswith('GATEWAY') :
1163 fnetwork.write(line)
1164 if defaultroute_pif:
1165 gatewaydev = pif_ipdev_name(defaultroute_pif)
1167 gatewaydev = pif_netdev_name(defaultroute_pif)
1168 fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1170 f.attach_child(fnetwork)
1175 # Datapath Configuration
1178 def pif_datapath(pif):
1179 """Return the OpenFlow datapath name associated with pif.
1180 For a non-VLAN PIF, the datapath name is the bridge name.
1181 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
1183 if pif_is_vlan(pif):
1184 return pif_datapath(pif_get_vlan_slave(pif))
1186 pifrec = db.get_pif_record(pif)
1187 nwrec = db.get_network_record(pifrec['network'])
1188 if not nwrec['bridge']:
1189 raise Error("datapath PIF cannot be bridgeless")
1193 def datapath_get_physical_pifs(pif):
1194 """Return the PIFs for the physical network device(s) associated with a datapath PIF.
1195 For a bond master PIF, these are the bond slave PIFs.
1196 For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
1198 A VLAN PIF cannot be a datapath PIF.
1200 pifrec = db.get_pif_record(pif)
1202 if pif_is_vlan(pif):
1203 raise Error("get-physical-pifs should not get passed a VLAN")
1204 elif len(pifrec['bond_master_of']) != 0:
1205 return pif_get_bond_slaves(pif)
1209 def datapath_deconfigure_physical(netdev):
1210 return ['--', '--if-exists', 'del-port', netdev]
1212 def datapath_configure_bond(pif,slaves):
1213 bridge = pif_bridge_name(pif)
1214 pifrec = db.get_pif_record(pif)
1215 interface = pif_netdev_name(pif)
1217 argv = ['--', '--fake-iface', 'add-bond', bridge, interface]
1218 for slave in slaves:
1219 argv += [pif_netdev_name(slave)]
1221 # XXX need ovs-vsctl support
1222 #if pifrec['MAC'] != "":
1223 # argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
1227 "mode": "balance-slb",
1233 # override defaults with values from other-config whose keys
1234 # being with "bond-"
1235 oc = pifrec['other_config']
1236 overrides = filter(lambda (key,val):
1237 key.startswith("bond-"), oc.items())
1238 overrides = map(lambda (key,val): (key[5:], val), overrides)
1239 bond_options.update(overrides)
1240 for (name,val) in bond_options.items():
1241 # XXX need ovs-vsctl support for bond options
1242 #argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
1246 def datapath_deconfigure_bond(netdev):
1247 return ['--', '--if-exists', 'del-port', netdev]
1249 def datapath_deconfigure_ipdev(interface):
1250 return ['--', '--if-exists', 'del-port', interface]
1252 def datapath_modify_config(commands):
1254 log("modifying configuration:")
1258 rc = run_command(['/usr/bin/ovs-vsctl'] + ['--timeout=20']
1259 + [c for c in commands if not c.startswith('#')])
1261 raise Error("Failed to modify vswitch configuration")
1265 # Toplevel Datapath Configuration.
1268 def configure_datapath(pif):
1269 """Bring up the datapath configuration for PIF.
1271 Should be careful not to glitch existing users of the datapath, e.g. other VLANs etc.
1273 Should take care of tearing down other PIFs which encompass common physical devices.
1275 Returns a tuple containing
1276 - A list containing the necessary cfgmod command line arguments
1277 - A list of additional devices which should be brought up after
1278 the configuration is applied.
1284 bridge = pif_bridge_name(pif)
1286 physical_devices = datapath_get_physical_pifs(pif)
1288 # Determine additional devices to deconfigure.
1290 # Given all physical devices which are part of this PIF we need to
1292 # - any additional bond which a physical device is part of.
1293 # - any additional physical devices which are part of an additional bond.
1295 # Any of these which are not currently in use should be brought
1296 # down and deconfigured.
1297 extra_down_bonds = []
1298 extra_down_ports = []
1299 for p in physical_devices:
1300 for bond in pif_get_bond_masters(p):
1302 log("configure_datapath: leaving bond %s up" % pif_netdev_name(bond))
1304 if bond in extra_down_bonds:
1306 if db.get_pif_record(bond)['currently_attached']:
1307 log("configure_datapath: implicitly tearing down currently-attached bond %s" % pif_netdev_name(bond))
1309 extra_down_bonds += [bond]
1311 for s in pif_get_bond_slaves(bond):
1312 if s in physical_devices:
1314 if s in extra_down_ports:
1316 if pif_currently_in_use(s):
1318 extra_down_ports += [s]
1320 log("configure_datapath: bridge - %s" % bridge)
1321 log("configure_datapath: physical - %s" % [pif_netdev_name(p) for p in physical_devices])
1322 log("configure_datapath: extra ports - %s" % [pif_netdev_name(p) for p in extra_down_ports])
1323 log("configure_datapath: extra bonds - %s" % [pif_netdev_name(p) for p in extra_down_bonds])
1325 # Need to fully deconfigure any bridge which any of the:
1326 # - physical devices
1330 for brpif in physical_devices + extra_down_ports + extra_down_bonds:
1333 b = pif_bridge_name(brpif)
1335 cfgmod_argv += ['# remove bridge %s' % b]
1336 cfgmod_argv += ['--', '--if-exists', 'del-br', b]
1338 for n in extra_down_ports:
1339 dev = pif_netdev_name(n)
1340 cfgmod_argv += ['# deconfigure sibling physical device %s' % dev]
1341 cfgmod_argv += datapath_deconfigure_physical(dev)
1344 for n in extra_down_bonds:
1345 dev = pif_netdev_name(n)
1346 cfgmod_argv += ['# deconfigure bond device %s' % dev]
1347 cfgmod_argv += datapath_deconfigure_bond(dev)
1350 for p in physical_devices:
1351 dev = pif_netdev_name(p)
1352 cfgmod_argv += ['# deconfigure physical port %s' % dev]
1353 cfgmod_argv += datapath_deconfigure_physical(dev)
1355 # Check the MAC address of each network device and remap if
1356 # necessary to make names match our expectations.
1357 for p in physical_devices:
1358 netdev_remap_name(p)
1360 # Bring up physical devices early, because ovs-vswitchd initially
1361 # enables or disables bond slaves based on whether carrier is
1362 # detected when they are added, and a network device that is down
1363 # always reports "no carrier".
1364 for p in physical_devices:
1365 oc = db.get_pif_record(p)['other_config']
1367 dev = pif_netdev_name(p)
1369 mtu = mtu_setting(oc)
1373 settings, offload = ethtool_settings(oc)
1375 run_command(['/sbin/ethtool', '-s', dev] + settings)
1377 run_command(['/sbin/ethtool', '-K', dev] + offload)
1379 # XXX It seems like the following should not be necessary...
1380 cfgmod_argv += ['--', '--if-exists', 'del-br', bridge]
1382 if pif_is_vlan(pif):
1383 datapath = pif_datapath(pif)
1384 vlan = db.get_pif_record(pif)['VLAN']
1385 cfgmod_argv += ['--', 'add-br', bridge, datapath, vlan]
1387 cfgmod_argv += ['--', 'add-br', bridge]
1389 if len(physical_devices) > 1:
1390 cfgmod_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
1391 cfgmod_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
1392 cfgmod_argv += ['# configure bond %s' % pif_netdev_name(pif)]
1393 cfgmod_argv += datapath_configure_bond(pif, physical_devices)
1394 extra_up_ports += [pif_netdev_name(pif)]
1396 iface = pif_netdev_name(physical_devices[0])
1397 cfgmod_argv += ['# add physical device %s' % iface]
1398 cfgmod_argv += ['--', 'add-port', bridge, iface]
1400 return cfgmod_argv,extra_up_ports
1402 def deconfigure_datapath(pif):
1405 bridge = pif_bridge_name(pif)
1407 physical_devices = datapath_get_physical_pifs(pif)
1409 log("deconfigure_datapath: bridge - %s" % bridge)
1410 log("deconfigure_datapath: physical devices - %s" % [pif_netdev_name(p) for p in physical_devices])
1412 for p in physical_devices:
1413 dev = pif_netdev_name(p)
1414 cfgmod_argv += ['# deconfigure physical port %s' % dev]
1415 cfgmod_argv += datapath_deconfigure_physical(dev)
1418 if len(physical_devices) > 1:
1419 cfgmod_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
1420 cfgmod_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
1422 cfgmod_argv += ['# deconfigure bridge %s' % bridge]
1423 cfgmod_argv += ['--', '--if-exists', 'del-br', bridge]
1432 pifrec = db.get_pif_record(pif)
1436 ipdev = pif_ipdev_name(pif)
1437 dp = pif_datapath(pif)
1438 bridge = pif_bridge_name(dp)
1440 log("action_up: %s on bridge %s" % (ipdev, bridge))
1445 c,e = configure_datapath(dp)
1449 xs_network_uuids = []
1450 for nwpif in db.get_pifs_by_device(db.get_pif_record(pif)['device']):
1451 rec = db.get_pif_record(nwpif)
1453 # When state is read from dbcache PIF.currently_attached
1454 # is always assumed to be false... Err on the side of
1455 # listing even detached networks for the time being.
1456 #if nwpif != pif and not rec['currently_attached']:
1457 # log("Network PIF %s not currently attached (%s)" % (rec['uuid'],pifrec['uuid']))
1459 nwrec = db.get_network_record(rec['network'])
1460 xs_network_uuids += [nwrec['uuid']]
1461 cfgmod_argv += ['# configure xs-network-uuids']
1462 cfgmod_argv += ['--', 'br-set-external-id', bridge,
1463 'xs-network-uuids', ';'.join(xs_network_uuids)]
1466 cfgmod_argv += ["# deconfigure ipdev %s" % ipdev]
1467 cfgmod_argv += datapath_deconfigure_ipdev(ipdev)
1468 cfgmod_argv += ["# reconfigure ipdev %s" % ipdev]
1469 cfgmod_argv += ['--', 'add-port', bridge, ipdev]
1471 f = ipdev_configure_network(pif)
1474 # Apply updated configuration.
1478 datapath_modify_config(cfgmod_argv)
1482 for p in extra_ports:
1485 # Update /etc/issue (which contains the IP address of the management interface)
1486 os.system("/sbin/update-issue")
1490 log("failed to apply changes: %s" % e.msg)
1494 def action_down(pif):
1495 pifrec = db.get_pif_record(pif)
1498 ipdev = pif_ipdev_name(pif)
1499 dp = pif_datapath(pif)
1500 bridge = pif_bridge_name(dp)
1502 log("action_down: %s on bridge %s" % (ipdev, bridge))
1507 nw = db.get_pif_record(pif)['network']
1508 nwrec = db.get_network_record(nw)
1510 log("deconfigure ipdev %s on %s" % (ipdev,bridge))
1511 cfgmod_argv += ["# deconfigure ipdev %s" % ipdev]
1512 cfgmod_argv += datapath_deconfigure_ipdev(ipdev)
1514 f = ipdev_open_ifcfg(pif)
1517 if pif_is_vlan(pif):
1518 br = ConfigurationFile("br-%s" % bridge, vswitch_state_dir)
1522 # If the VLAN's slave is attached, leave datapath setup.
1523 slave = pif_get_vlan_slave(pif)
1524 if db.get_pif_record(slave)['currently_attached']:
1525 log("action_down: vlan slave is currently attached")
1528 # If the VLAN's slave has other VLANs that are attached, leave datapath setup.
1529 for master in pif_get_vlan_masters(slave):
1530 if master != pif and db.get_pif_record(master)['currently_attached']:
1531 log("action_down: vlan slave has other master: %s" % pif_netdev_name(master))
1534 # Otherwise, take down the datapath too (fall through)
1536 log("action_down: no more masters, bring down slave %s" % bridge)
1538 # Stop here if this PIF has attached VLAN masters.
1539 masters = [db.get_pif_record(m)['VLAN'] for m in pif_get_vlan_masters(pif) if db.get_pif_record(m)['currently_attached']]
1540 if len(masters) > 0:
1541 log("Leaving datapath %s up due to currently attached VLAN masters %s" % (bridge, masters))
1545 cfgmod_argv += deconfigure_datapath(dp)
1550 datapath_modify_config(cfgmod_argv)
1554 log("action_down failed to apply changes: %s" % e.msg)
1558 def action_rewrite(pif):
1559 f = ipdev_configure_network(pif)
1565 log("failed to apply changes: %s" % e.msg)
1569 def action_force_rewrite(bridge, config):
1570 raise Error("Force rewrite is not implemented yet.")
1572 def main(argv=None):
1573 global output_directory, management_pif
1579 force_interface = None
1580 force_management = False
1588 longops = [ "output-directory=",
1589 "pif=", "pif-uuid=",
1594 "device=", "mode=", "ip=", "netmask=", "gateway=",
1596 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1597 except getopt.GetoptError, msg:
1600 force_rewrite_config = {}
1603 if o == "--output-directory":
1604 output_directory = a
1607 elif o == "--pif-uuid":
1609 elif o == "--session":
1611 elif o == "--force-interface" or o == "--force":
1613 elif o == "--management":
1614 force_management = True
1615 elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1616 force_rewrite_config[o[2:]] = a
1617 elif o == "-h" or o == "--help":
1618 print __doc__ % {'command-name': os.path.basename(argv[0])}
1621 if not debug_mode():
1622 syslog.openlog(os.path.basename(argv[0]))
1623 log("Called as " + str.join(" ", argv))
1625 raise Usage("Required option <action> not present")
1627 raise Usage("Too many arguments")
1631 if not action in ["up", "down", "rewrite", "rewrite-configuration"]:
1632 raise Usage("Unknown action \"%s\"" % action)
1634 # backwards compatibility
1635 if action == "rewrite-configuration": action = "rewrite"
1637 if output_directory and ( session or pif ):
1638 raise Usage("--session/--pif cannot be used with --output-directory")
1639 if ( session or pif ) and pif_uuid:
1640 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1641 if ( session and not pif ) or ( not session and pif ):
1642 raise Usage("--session and --pif must be used together.")
1643 if force_interface and ( session or pif or pif_uuid ):
1644 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1645 if force_interface == "all" and action != "down":
1646 raise Usage("\"--force all\" only valid for down action")
1647 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1648 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1652 log("Force interface %s %s" % (force_interface, action))
1654 if action == "rewrite":
1655 action_force_rewrite(force_interface, force_rewrite_config)
1656 elif action in ["up", "down"]:
1657 if action == "down" and force_interface == "all":
1658 raise Error("Force all interfaces down not implemented yet")
1660 db = DatabaseCache(cache_file=dbcache_file)
1661 pif = db.get_pif_by_bridge(force_interface)
1662 management_pif = db.get_management_pif()
1666 elif action == "down":
1669 raise Error("Unknown action %s" % action)
1671 db = DatabaseCache(session_ref=session)
1674 pif = db.get_pif_by_uuid(pif_uuid)
1676 if action == "rewrite" and not pif:
1680 raise Usage("No PIF given")
1682 if force_management:
1683 # pif is going to be the management pif
1684 management_pif = pif
1686 # pif is not going to be the management pif.
1687 # Search DB cache for pif on same host with management=true
1688 pifrec = db.get_pif_record(pif)
1689 management_pif = db.get_management_pif()
1691 log_pif_action(action, pif)
1693 if not check_allowed(pif):
1698 elif action == "down":
1700 elif action == "rewrite":
1703 raise Error("Unknown action %s" % action)
1706 db.save(dbcache_file)
1709 print >>sys.stderr, err.msg
1710 print >>sys.stderr, "For help use --help."
1718 if __name__ == "__main__":
1724 err = traceback.format_exception(*ex)
1728 if not debug_mode():