3 # Copyright (c) 2008,2009 Citrix Systems, Inc. All rights reserved.
4 # Copyright (c) 2009 Nicira Networks.
8 %(command-name)s --session <SESSION-REF> --pif <PIF-REF> [up|down|rewrite]
9 %(command-name)s --force <BRIDGE> [up|down|rewrite <CONFIG>]
10 %(command-name)s --force all down
13 <CONFIG> = --device=<INTERFACE> --mode=dhcp
14 <CONFIG> = --device=<INTERFACE> --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
17 --session A session reference to use to access the xapi DB
18 --pif A PIF reference.
19 --force-interface An interface name. Mutually exclusive with --session/--pif.
21 Either both --session and --pif or just --pif-uuid.
23 <ACTION> is either "up" or "down" or "rewrite"
27 # Undocumented parameters for test & dev:
29 # --output-directory=<DIR> Write configuration to <DIR>. Also disables actually
30 # raising/lowering the interfaces
31 # --pif-uuid A PIF UUID, use instead of --session/--pif.
36 # 1. Every pif belongs to exactly one network
37 # 2. Every network has zero or one pifs
38 # 3. A network may have an associated bridge, allowing vifs to be attached
39 # 4. A network may be bridgeless (there's no point having a bridge over a storage pif)
42 import os, sys, getopt, time, signal
48 from xml.dom.minidom import getDOMImplementation
49 from xml.dom.minidom import parse as parseXML
51 output_directory = None
56 vswitch_state_dir = "/var/lib/openvswitch/"
57 dbcache_file = vswitch_state_dir + "dbcache"
59 class Usage(Exception):
60 def __init__(self, msg):
61 Exception.__init__(self)
64 class Error(Exception):
65 def __init__(self, msg):
66 Exception.__init__(self)
69 class ConfigurationFile(object):
70 """Write a file, tracking old and new versions.
72 Supports writing a new version of a file and applying and
73 reverting those changes.
76 __STATE = {"OPEN":"OPEN",
77 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
78 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
80 def __init__(self, fname, path="/etc/sysconfig/network-scripts"):
82 self.__state = self.__STATE['OPEN']
87 dirname = output_directory
91 self.__path = os.path.join(dirname, fname)
92 self.__oldpath = os.path.join(dirname, "." + fname + ".xapi-old")
93 self.__newpath = os.path.join(dirname, "." + fname + ".xapi-new")
96 self.__f = open(self.__newpath, "w")
98 def attach_child(self, child):
99 self.__children.append(child)
106 return open(self.path()).readlines()
110 def write(self, args):
111 if self.__state != self.__STATE['OPEN']:
112 raise Error("Attempt to write to file in state %s" % self.__state)
116 if self.__state != self.__STATE['OPEN']:
117 raise Error("Attempt to unlink file in state %s" % self.__state)
120 self.__state = self.__STATE['NOT-APPLIED']
123 if self.__state != self.__STATE['OPEN']:
124 raise Error("Attempt to close file in state %s" % self.__state)
127 self.__state = self.__STATE['NOT-APPLIED']
130 if self.__state != self.__STATE['NOT-APPLIED']:
131 raise Error("Attempt to compare file in state %s" % self.__state)
136 if self.__state != self.__STATE['NOT-APPLIED']:
137 raise Error("Attempt to apply configuration from state %s" % self.__state)
139 for child in self.__children:
142 log("Applying changes to %s configuration" % self.__fname)
144 # Remove previous backup.
145 if os.access(self.__oldpath, os.F_OK):
146 os.unlink(self.__oldpath)
148 # Save current configuration.
149 if os.access(self.__path, os.F_OK):
150 os.link(self.__path, self.__oldpath)
151 os.unlink(self.__path)
153 # Apply new configuration.
154 assert(os.path.exists(self.__newpath))
155 if not self.__unlink:
156 os.link(self.__newpath, self.__path)
158 pass # implicit unlink of original file
160 # Remove temporary file.
161 os.unlink(self.__newpath)
163 self.__state = self.__STATE['APPLIED']
166 if self.__state != self.__STATE['APPLIED']:
167 raise Error("Attempt to revert configuration from state %s" % self.__state)
169 for child in self.__children:
172 log("Reverting changes to %s configuration" % self.__fname)
174 # Remove existing new configuration
175 if os.access(self.__newpath, os.F_OK):
176 os.unlink(self.__newpath)
178 # Revert new configuration.
179 if os.access(self.__path, os.F_OK):
180 os.link(self.__path, self.__newpath)
181 os.unlink(self.__path)
183 # Revert to old configuration.
184 if os.access(self.__oldpath, os.F_OK):
185 os.link(self.__oldpath, self.__path)
186 os.unlink(self.__oldpath)
188 # Leave .*.xapi-new as an aid to debugging.
190 self.__state = self.__STATE['REVERTED']
193 if self.__state != self.__STATE['APPLIED']:
194 raise Error("Attempt to commit configuration from state %s" % self.__state)
196 for child in self.__children:
199 log("Committing changes to %s configuration" % self.__fname)
201 if os.access(self.__oldpath, os.F_OK):
202 os.unlink(self.__oldpath)
203 if os.access(self.__newpath, os.F_OK):
204 os.unlink(self.__newpath)
206 self.__state = self.__STATE['COMMITTED']
209 return output_directory is not None
213 print >>sys.stderr, s
217 def check_allowed(pif):
218 pifrec = db.get_pif_record(pif)
220 f = open("/proc/ardence")
221 macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
223 if len(macline) == 1:
224 p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
225 if p.match(macline[0]):
226 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
232 def interface_exists(i):
233 return os.path.exists("/sys/class/net/" + i)
235 def get_netdev_mac(device):
237 return read_first_line_of_file("/sys/class/net/%s/address" % device)
239 # Probably no such device.
242 def get_netdev_tx_queue_len(device):
244 return int(read_first_line_of_file("/sys/class/net/%s/tx_queue_len"
247 # Probably no such device.
250 def get_netdev_by_mac(mac):
251 for device in os.listdir("/sys/class/net"):
252 dev_mac = get_netdev_mac(device)
253 if (dev_mac and mac.lower() == dev_mac.lower() and
254 get_netdev_tx_queue_len(device)):
259 # Helper functions for encoding/decoding database attributes to/from XML.
261 def str_to_xml(xml, parent, tag, val):
262 e = xml.createElement(tag)
263 parent.appendChild(e)
264 v = xml.createTextNode(val)
267 def getText(nodelist):
269 for node in nodelist:
270 if node.nodeType == node.TEXT_NODE:
273 return getText(n.childNodes).strip()
276 def bool_to_xml(xml, parent, tag, val):
278 str_to_xml(xml, parent, tag, "True")
280 str_to_xml(xml, parent, tag, "False")
281 def bool_from_xml(n):
288 raise Error("Unknown boolean value %s" % s);
290 def strlist_to_xml(xml, parent, ltag, itag, val):
291 e = xml.createElement(ltag)
292 parent.appendChild(e)
294 c = xml.createElement(itag)
296 cv = xml.createTextNode(v)
298 def strlist_from_xml(n, ltag, itag):
300 for n in n.childNodes:
301 if n.nodeName == itag:
302 ret.append(str_from_xml(n))
305 def otherconfig_to_xml(xml, parent, val, attrs):
306 otherconfig = xml.createElement("other_config")
307 parent.appendChild(otherconfig)
308 for n,v in val.items():
310 raise Error("Unknown other-config attribute: %s" % n)
311 str_to_xml(xml, otherconfig, n, v)
312 def otherconfig_from_xml(n, attrs):
314 for n in n.childNodes:
315 if n.nodeName in attrs:
316 ret[n.nodeName] = str_from_xml(n)
320 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
322 # Each object is defined by a dictionary mapping an attribute name in
323 # the xapi database to a tuple containing two items:
324 # - a function which takes this attribute and encodes it as XML.
325 # - a function which takes XML and decocdes it into a value.
327 # other-config attributes are specified as a simple array of strings
330 VLAN_XML_TAG = "vlan"
331 BOND_XML_TAG = "bond"
332 NETWORK_XML_TAG = "network"
334 ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
336 PIF_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
337 'management': (bool_to_xml,bool_from_xml),
338 'network': (str_to_xml,str_from_xml),
339 'device': (str_to_xml,str_from_xml),
340 'bond_master_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
341 lambda n: strlist_from_xml(n, 'bond_master_of', 'slave')),
342 'bond_slave_of': (str_to_xml,str_from_xml),
343 'VLAN': (str_to_xml,str_from_xml),
344 'VLAN_master_of': (str_to_xml,str_from_xml),
345 'VLAN_slave_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
346 lambda n: strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
347 'ip_configuration_mode': (str_to_xml,str_from_xml),
348 'IP': (str_to_xml,str_from_xml),
349 'netmask': (str_to_xml,str_from_xml),
350 'gateway': (str_to_xml,str_from_xml),
351 'DNS': (str_to_xml,str_from_xml),
352 'MAC': (str_to_xml,str_from_xml),
353 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, PIF_OTHERCONFIG_ATTRS),
354 lambda n: otherconfig_from_xml(n, PIF_OTHERCONFIG_ATTRS)),
356 # Special case: We write the current value
357 # PIF.currently-attached to the cache but since it will
358 # not be valid when we come to use the cache later
359 # (i.e. after a reboot) we always read it as False.
360 'currently_attached': (bool_to_xml, lambda n: False),
363 PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
364 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
365 ETHTOOL_OTHERCONFIG_ATTRS
367 VLAN_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
368 'tagged_PIF': (str_to_xml,str_from_xml),
369 'untagged_PIF': (str_to_xml,str_from_xml),
372 BOND_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
373 'master': (str_to_xml,str_from_xml),
374 'slaves': (lambda x, p, t, v: strlist_to_xml(x, p, 'slaves', 'slave', v),
375 lambda n: strlist_from_xml(n, 'slaves', 'slave')),
378 NETWORK_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
379 'bridge': (str_to_xml,str_from_xml),
380 'PIFs': (lambda x, p, t, v: strlist_to_xml(x, p, 'PIFs', 'PIF', v),
381 lambda n: strlist_from_xml(n, 'PIFs', 'PIF')),
382 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, NETWORK_OTHERCONFIG_ATTRS),
383 lambda n: otherconfig_from_xml(n, NETWORK_OTHERCONFIG_ATTRS)),
386 NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + ETHTOOL_OTHERCONFIG_ATTRS
388 class DatabaseCache(object):
389 def __read_xensource_inventory(self):
390 filename = "/etc/xensource-inventory"
391 f = open(filename, "r")
392 lines = [x.strip("\n") for x in f.readlines()]
395 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
396 defs = [ (a, b.strip("'")) for (a,b) in defs ]
399 def __pif_on_host(self,pif):
400 return self.__pifs.has_key(pif)
402 def __get_pif_records_from_xapi(self, session, host):
404 for (p,rec) in session.xenapi.PIF.get_all_records().items():
405 if rec['host'] != host:
409 self.__pifs[p][f] = rec[f]
410 self.__pifs[p]['other_config'] = {}
411 for f in PIF_OTHERCONFIG_ATTRS:
412 if not rec['other_config'].has_key(f): continue
413 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
415 def __get_vlan_records_from_xapi(self, session):
417 for v in session.xenapi.VLAN.get_all():
418 rec = session.xenapi.VLAN.get_record(v)
419 if not self.__pif_on_host(rec['untagged_PIF']):
423 self.__vlans[v][f] = rec[f]
425 def __get_bond_records_from_xapi(self, session):
427 for b in session.xenapi.Bond.get_all():
428 rec = session.xenapi.Bond.get_record(b)
429 if not self.__pif_on_host(rec['master']):
433 self.__bonds[b][f] = rec[f]
435 def __get_network_records_from_xapi(self, session):
437 for n in session.xenapi.network.get_all():
438 rec = session.xenapi.network.get_record(n)
439 self.__networks[n] = {}
440 for f in NETWORK_ATTRS:
442 # drop PIFs on other hosts
443 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
445 self.__networks[n][f] = rec[f]
446 self.__networks[n]['other_config'] = {}
447 for f in NETWORK_OTHERCONFIG_ATTRS:
448 if not rec['other_config'].has_key(f): continue
449 self.__networks[n]['other_config'][f] = rec['other_config'][f]
451 def __to_xml(self, xml, parent, key, ref, rec, attrs):
452 """Encode a database object as XML"""
453 e = xml.createElement(key)
454 parent.appendChild(e)
456 e.setAttribute('ref', ref)
458 for n,v in rec.items():
463 raise Error("Unknown attribute %s" % n)
464 def __from_xml(self, e, attrs):
465 """Decode a database object from XML"""
466 ref = e.attributes['ref'].value
468 for n in e.childNodes:
469 if n.nodeName in attrs:
470 _,h = attrs[n.nodeName]
471 rec[n.nodeName] = h(n)
474 def __init__(self, session_ref=None, cache_file=None):
475 if session_ref and cache_file:
476 raise Error("can't specify session reference and cache file")
477 if cache_file == None:
478 session = XenAPI.xapi_local()
481 log("No session ref given on command line, logging in.")
482 session.xenapi.login_with_password("root", "")
484 session._session = session_ref
488 inventory = self.__read_xensource_inventory()
489 assert(inventory.has_key('INSTALLATION_UUID'))
490 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
492 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
494 self.__get_pif_records_from_xapi(session, host)
496 self.__get_vlan_records_from_xapi(session)
497 self.__get_bond_records_from_xapi(session)
498 self.__get_network_records_from_xapi(session)
501 session.xenapi.session.logout()
503 log("Loading xapi database cache from %s" % cache_file)
505 xml = parseXML(cache_file)
512 assert(len(xml.childNodes) == 1)
513 toplevel = xml.childNodes[0]
515 assert(toplevel.nodeName == "xenserver-network-configuration")
517 for n in toplevel.childNodes:
518 if n.nodeName == "#text":
520 elif n.nodeName == PIF_XML_TAG:
521 (ref,rec) = self.__from_xml(n, PIF_ATTRS)
522 self.__pifs[ref] = rec
523 elif n.nodeName == BOND_XML_TAG:
524 (ref,rec) = self.__from_xml(n, BOND_ATTRS)
525 self.__bonds[ref] = rec
526 elif n.nodeName == VLAN_XML_TAG:
527 (ref,rec) = self.__from_xml(n, VLAN_ATTRS)
528 self.__vlans[ref] = rec
529 elif n.nodeName == NETWORK_XML_TAG:
530 (ref,rec) = self.__from_xml(n, NETWORK_ATTRS)
531 self.__networks[ref] = rec
533 raise Error("Unknown XML element %s" % n.nodeName)
535 def save(self, cache_file):
537 xml = getDOMImplementation().createDocument(
538 None, "xenserver-network-configuration", None)
539 for (ref,rec) in self.__pifs.items():
540 self.__to_xml(xml, xml.documentElement, PIF_XML_TAG, ref, rec, PIF_ATTRS)
541 for (ref,rec) in self.__bonds.items():
542 self.__to_xml(xml, xml.documentElement, BOND_XML_TAG, ref, rec, BOND_ATTRS)
543 for (ref,rec) in self.__vlans.items():
544 self.__to_xml(xml, xml.documentElement, VLAN_XML_TAG, ref, rec, VLAN_ATTRS)
545 for (ref,rec) in self.__networks.items():
546 self.__to_xml(xml, xml.documentElement, NETWORK_XML_TAG, ref, rec,
549 f = open(cache_file, 'w')
550 f.write(xml.toprettyxml())
553 def get_pif_by_uuid(self, uuid):
554 pifs = map(lambda (ref,rec): ref,
555 filter(lambda (ref,rec): uuid == rec['uuid'],
556 self.__pifs.items()))
558 raise Error("Unknown PIF \"%s\"" % uuid)
560 raise Error("Non-unique PIF \"%s\"" % uuid)
564 def get_pifs_by_device(self, device):
565 return map(lambda (ref,rec): ref,
566 filter(lambda (ref,rec): rec['device'] == device,
567 self.__pifs.items()))
569 def get_pif_by_bridge(self, bridge):
570 networks = map(lambda (ref,rec): ref,
571 filter(lambda (ref,rec): rec['bridge'] == bridge,
572 self.__networks.items()))
573 if len(networks) == 0:
574 raise Error("No matching network \"%s\"")
577 for network in networks:
578 nwrec = self.get_network_record(network)
579 for pif in nwrec['PIFs']:
580 pifrec = self.get_pif_record(pif)
582 raise Error("Multiple PIFs on host for network %s" % (bridge))
585 raise Error("No PIF on host for network %s" % (bridge))
588 def get_pif_record(self, pif):
589 if self.__pifs.has_key(pif):
590 return self.__pifs[pif]
591 raise Error("Unknown PIF \"%s\" (get_pif_record)" % pif)
592 def get_all_pifs(self):
594 def pif_exists(self, pif):
595 return self.__pifs.has_key(pif)
597 def get_management_pif(self):
598 """ Returns the management pif on host
600 all = self.get_all_pifs()
602 pifrec = self.get_pif_record(pif)
603 if pifrec['management']: return pif
606 def get_network_record(self, network):
607 if self.__networks.has_key(network):
608 return self.__networks[network]
609 raise Error("Unknown network \"%s\"" % network)
610 def get_all_networks(self):
611 return self.__networks
613 def get_bond_record(self, bond):
614 if self.__bonds.has_key(bond):
615 return self.__bonds[bond]
619 def get_vlan_record(self, vlan):
620 if self.__vlans.has_key(vlan):
621 return self.__vlans[vlan]
625 def bridge_name(pif):
626 """Return the bridge name associated with pif, or None if network is bridgeless"""
627 pifrec = db.get_pif_record(pif)
628 nwrec = db.get_network_record(pifrec['network'])
631 # TODO: sanity check that nwrec['bridgeless'] != 'true'
632 return nwrec['bridge']
634 # TODO: sanity check that nwrec['bridgeless'] == 'true'
637 def interface_name(pif):
638 """Construct an interface name from the given PIF record."""
640 pifrec = db.get_pif_record(pif)
642 if pifrec['VLAN'] == '-1':
643 return pifrec['device']
645 return "%(device)s.%(VLAN)s" % pifrec
647 def datapath_name(pif):
648 """Return the OpenFlow datapath name associated with pif.
649 For a non-VLAN PIF, the datapath name is the bridge name.
650 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
651 (xapi will create a datapath named with the bridge name even though we won't
656 pifrec = db.get_pif_record(pif)
658 if pifrec['VLAN'] == '-1':
659 return bridge_name(pif)
661 return bridge_name(get_vlan_slave_of_pif(pif))
664 """Return the the name of the network device that carries the
665 IP configuration (if any) associated with pif.
666 The ipdev name is the same as the bridge name.
669 pifrec = db.get_pif_record(pif)
670 return bridge_name(pif)
672 def get_physdev_pifs(pif):
673 """Return the PIFs for the physical network device(s) associated with pif.
674 For a VLAN PIF, this is the VLAN slave's physical device PIF.
675 For a bond master PIF, these are the bond slave PIFs.
676 For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
678 pifrec = db.get_pif_record(pif)
680 if pifrec['VLAN'] != '-1':
681 return get_physdev_pifs(get_vlan_slave_of_pif(pif))
682 elif len(pifrec['bond_master_of']) != 0:
683 return get_bond_slaves_of_pif(pif)
687 def get_physdev_names(pif):
688 """Return the name(s) of the physical network device(s) associated with pif.
689 For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
690 For a bond master PIF, the physical devices are the bond slaves.
691 For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
694 return [db.get_pif_record(phys)['device'] for phys in get_physdev_pifs(pif)]
696 def log_pif_action(action, pif):
697 pifrec = db.get_pif_record(pif)
699 rec['uuid'] = pifrec['uuid']
700 rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
701 rec['action'] = action
702 rec['interface-name'] = interface_name(pif)
703 if action == "rewrite":
704 rec['message'] = "Rewrite PIF %(uuid)s configuration" % rec
706 rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
707 log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % rec)
709 def get_bond_masters_of_pif(pif):
710 """Returns a list of PIFs which are bond masters of this PIF"""
712 pifrec = db.get_pif_record(pif)
714 bso = pifrec['bond_slave_of']
716 # bond-slave-of is currently a single reference but in principle a
717 # PIF could be a member of several bonds which are not
718 # concurrently attached. Be robust to this possibility.
719 if not bso or bso == "OpaqueRef:NULL":
721 elif not type(bso) == list:
724 bondrecs = [db.get_bond_record(bond) for bond in bso]
725 bondrecs = [rec for rec in bondrecs if rec]
727 return [bond['master'] for bond in bondrecs]
729 def get_bond_slaves_of_pif(pif):
730 """Returns a list of PIFs which make up the given bonded pif."""
732 pifrec = db.get_pif_record(pif)
734 bmo = pifrec['bond_master_of']
736 raise Error("Bond-master-of contains too many elements")
741 bondrec = db.get_bond_record(bmo[0])
743 raise Error("No bond record for bond master PIF")
745 return bondrec['slaves']
747 def get_vlan_slave_of_pif(pif):
748 """Find the PIF which is the VLAN slave of pif.
750 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
752 pifrec = db.get_pif_record(pif)
754 vlan = pifrec['VLAN_master_of']
755 if not vlan or vlan == "OpaqueRef:NULL":
756 raise Error("PIF is not a VLAN master")
758 vlanrec = db.get_vlan_record(vlan)
760 raise Error("No VLAN record found for PIF")
762 return vlanrec['tagged_PIF']
764 def get_vlan_masters_of_pif(pif):
765 """Returns a list of PIFs which are VLANs on top of the given pif."""
767 pifrec = db.get_pif_record(pif)
768 vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
769 return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])]
771 def interface_deconfigure_commands(interface):
772 # The use of [!0-9] keeps an interface of 'eth0' from matching
773 # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
775 return ['--del-match=bridge.*.port=%s' % interface,
776 '--del-match=bonding.%s.[!0-9]*' % interface,
777 '--del-match=bonding.*.slave=%s' % interface,
778 '--del-match=vlan.%s.[!0-9]*' % interface,
779 '--del-match=port.%s.[!0-9]*' % interface,
780 '--del-match=iface.%s.[!0-9]*' % interface]
782 def run_command(command):
783 log("Running command: " + ' '.join(command))
784 if os.spawnl(os.P_WAIT, command[0], *command) != 0:
785 log("Command failed: " + ' '.join(command))
789 def rename_netdev(old_name, new_name):
790 log("Changing the name of %s to %s" % (old_name, new_name))
791 run_command(['/sbin/ifconfig', old_name, 'down'])
792 if not run_command(['/sbin/ip', 'link', 'set', old_name,
794 raise Error("Could not rename %s to %s" % (old_name, new_name))
796 # Check whether 'pif' exists and has the correct MAC.
797 # If not, try to find a device with the correct MAC and rename it.
798 # 'already_renamed' is used to avoid infinite recursion.
799 def remap_pif(pif, already_renamed=[]):
800 pifrec = db.get_pif_record(pif)
801 device = pifrec['device']
804 # Is there a network device named 'device' at all?
805 device_exists = interface_exists(device)
807 # Yes. Does it have MAC 'mac'?
808 found_mac = get_netdev_mac(device)
809 if found_mac and mac.lower() == found_mac.lower():
810 # Yes, everything checks out the way we want. Nothing to do.
813 log("No network device %s" % device)
815 # What device has MAC 'mac'?
816 cur_device = get_netdev_by_mac(mac)
818 log("No network device has MAC %s" % mac)
821 # First rename 'device', if it exists, to get it out of the way
822 # for 'cur_device' to replace it.
824 rename_netdev(device, "dev%d" % random.getrandbits(24))
826 # Rename 'cur_device' to 'device'.
827 rename_netdev(cur_device, device)
829 def read_first_line_of_file(name):
832 file = open(name, 'r')
833 return file.readline().rstrip('\n')
838 def down_netdev(interface, deconfigure=True):
839 if not interface_exists(interface):
840 log("down_netdev: interface %s does not exist, ignoring" % interface)
844 pidfile_name = '/var/run/dhclient-%s.pid' % interface
846 os.kill(int(read_first_line_of_file(pidfile_name)), signal.SIGTERM)
850 # Remove dhclient pidfile.
852 os.remove(pidfile_name)
856 run_command(["/sbin/ifconfig", interface, '0.0.0.0'])
858 run_command(["/sbin/ifconfig", interface, 'down'])
860 def up_netdev(interface):
861 run_command(["/sbin/ifconfig", interface, 'up'])
863 def find_distinguished_pifs(pif):
864 """Returns the PIFs on host that own DNS and the default route.
865 The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
866 The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
868 Note: we prune out the bond master pif (if it exists).
869 This is because when we are called to bring up an interface with a bond master, it is implicit that
870 we should bring down that master."""
872 pifrec = db.get_pif_record(pif)
874 pifs = [ __pif for __pif in db.get_all_pifs() if
875 (not __pif in get_bond_masters_of_pif(pif)) ]
878 defaultroute_pif = None
880 # loop through all the pifs on this host looking for one with
881 # other-config:peerdns = true, and one with
882 # other-config:default-route=true
884 __pifrec = db.get_pif_record(__pif)
885 __oc = __pifrec['other_config']
886 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
887 if peerdns_pif == None:
890 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
891 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
892 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
893 if defaultroute_pif == None:
894 defaultroute_pif = __pif
896 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
897 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
899 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
900 if peerdns_pif == None:
901 peerdns_pif = management_pif
902 if defaultroute_pif == None:
903 defaultroute_pif = management_pif
905 return peerdns_pif, defaultroute_pif
907 def run_ethtool(device, oc):
908 # Run "ethtool -s" if there are any settings.
910 if oc.has_key('ethtool-speed'):
911 val = oc['ethtool-speed']
912 if val in ["10", "100", "1000"]:
913 settings += ['speed', val]
915 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
916 if oc.has_key('ethtool-duplex'):
917 val = oc['ethtool-duplex']
918 if val in ["10", "100", "1000"]:
919 settings += ['duplex', 'val']
921 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
922 if oc.has_key('ethtool-autoneg'):
923 val = oc['ethtool-autoneg']
924 if val in ["true", "on"]:
925 settings += ['autoneg', 'on']
926 elif val in ["false", "off"]:
927 settings += ['autoneg', 'off']
929 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
931 run_command(['/sbin/ethtool', '-s', device] + settings)
933 # Run "ethtool -K" if there are any offload settings.
935 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
936 if oc.has_key("ethtool-" + opt):
937 val = oc["ethtool-" + opt]
938 if val in ["true", "on"]:
939 offload += [opt, 'on']
940 elif val in ["false", "off"]:
941 offload += [opt, 'off']
943 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
945 run_command(['/sbin/ethtool', '-K', device] + offload)
948 if oc.has_key('mtu'):
950 int(oc['mtu']) # Check that the value is an integer
951 return ['mtu', oc['mtu']]
952 except ValueError, x:
953 log("Invalid value for mtu = %s" % mtu)
956 def configure_local_port(pif):
957 pifrec = db.get_pif_record(pif)
958 datapath = datapath_name(pif)
959 ipdev = ipdev_name(pif)
961 nw = pifrec['network']
962 nwrec = db.get_network_record(nw)
964 pif_oc = pifrec['other_config']
965 nw_oc = nwrec['other_config']
967 # IP (except DHCP) and MTU.
968 ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up']
970 if pifrec['ip_configuration_mode'] == "DHCP":
972 elif pifrec['ip_configuration_mode'] == "Static":
973 ifconfig_argv += [pifrec['IP']]
974 ifconfig_argv += ['netmask', pifrec['netmask']]
975 gateway = pifrec['gateway']
976 elif pifrec['ip_configuration_mode'] == "None":
980 raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode'])
981 ifconfig_argv += mtu_setting(nw_oc)
982 run_command(ifconfig_argv)
984 (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif)
987 if peerdns_pif == pif:
988 f = ConfigurationFile('resolv.conf', "/etc")
989 if pif_oc.has_key('domain'):
990 f.write("search %s\n" % pif_oc['domain'])
991 for dns in pifrec['DNS'].split(","):
992 f.write("nameserver %s\n" % dns)
998 if defaultroute_pif == pif and gateway != '':
999 run_command(['/sbin/ip', 'route', 'replace', 'default',
1000 'via', gateway, 'dev', ipdev])
1001 if nw_oc.has_key('static-routes'):
1002 for line in nw_oc['static-routes'].split(','):
1003 network, masklen, gateway = line.split('/')
1004 run_command(['/sbin/ip', 'route', 'add',
1005 '%s/%s' % (network, masklen), 'via', gateway,
1009 run_ethtool(ipdev, nw_oc)
1012 if pifrec['ip_configuration_mode'] == "DHCP":
1014 print "Determining IP information for %s..." % ipdev,
1015 argv = ['/sbin/dhclient', '-q',
1016 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev,
1017 '-pf', '/var/run/dhclient-%s.pid' % ipdev,
1019 if run_command(argv):
1024 def configure_physdev(pif):
1025 pifrec = db.get_pif_record(pif)
1026 device = pifrec['device']
1027 oc = pifrec['other_config']
1029 run_command(['/sbin/ifconfig', device, 'up'] + mtu_setting(oc))
1030 run_ethtool(device, oc)
1032 def modify_config(commands):
1033 run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer',
1034 '-F', '/etc/ovs-vswitchd.conf']
1035 + commands + ['-c'])
1036 run_command(['/sbin/service', 'vswitch', 'reload'])
1038 def is_bond_pif(pif):
1039 pifrec = db.get_pif_record(pif)
1040 return len(pifrec['bond_master_of']) != 0
1042 def configure_bond(pif):
1043 pifrec = db.get_pif_record(pif)
1044 interface = interface_name(pif)
1045 ipdev = ipdev_name(pif)
1046 datapath = datapath_name(pif)
1047 physdev_names = get_physdev_names(pif)
1049 argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
1050 argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
1051 for slave in physdev_names]
1052 argv += ['--add=bonding.%s.fake-iface=true' % interface]
1054 if pifrec['MAC'] != "":
1055 argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
1059 "mode": "balance-slb",
1065 # override defaults with values from other-config whose keys
1066 # being with "bond-"
1067 oc = pifrec['other_config']
1068 overrides = filter(lambda (key,val):
1069 key.startswith("bond-"), oc.items())
1070 overrides = map(lambda (key,val): (key[5:], val), overrides)
1071 bond_options.update(overrides)
1072 for (name,val) in bond_options.items():
1073 argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
1077 pifrec = db.get_pif_record(pif)
1079 bridge = bridge_name(pif)
1080 interface = interface_name(pif)
1081 ipdev = ipdev_name(pif)
1082 datapath = datapath_name(pif)
1083 physdev_pifs = get_physdev_pifs(pif)
1084 physdev_names = get_physdev_names(pif)
1086 if pifrec['VLAN'] != '-1':
1087 vlan_slave = get_vlan_slave_of_pif(pif)
1088 if vlan_slave and is_bond_pif(vlan_slave):
1089 bond_master = vlan_slave
1090 elif is_bond_pif(pif):
1095 bond_slaves = get_bond_slaves_of_pif(bond_master)
1098 bond_masters = get_bond_masters_of_pif(pif)
1100 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1101 # files up-to-date, even though we don't use them or need them.
1102 f = configure_pif(pif)
1103 mode = pifrec['ip_configuration_mode']
1105 log("Configuring %s using %s configuration" % (bridge, mode))
1106 br = open_network_ifcfg(pif)
1107 configure_network(pif, br)
1111 log("Configuring %s using %s configuration" % (interface, mode))
1112 configure_network(pif, f)
1114 for master in bond_masters:
1115 master_bridge = bridge_name(master)
1116 removed = unconfigure_pif(master)
1117 f.attach_child(removed)
1119 removed = open_network_ifcfg(master)
1120 log("Unlinking stale file %s" % removed.path())
1122 f.attach_child(removed)
1124 # /etc/xensource/scripts/vif needs to know where to add VIFs.
1126 if not os.path.exists(vswitch_state_dir):
1127 os.mkdir(vswitch_state_dir)
1128 br = ConfigurationFile("br-%s" % bridge, vswitch_state_dir)
1129 br.write("VLAN_SLAVE=%s\n" % datapath)
1130 br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
1134 # Update all configuration files (both ours and Centos's).
1138 # Check the MAC address of each network device and remap if
1139 # necessary to make names match our expectations.
1140 for physdev_pif in physdev_pifs:
1141 remap_pif(physdev_pif)
1143 # "ifconfig down" the network device and delete its IP address, etc.
1145 for physdev_name in physdev_names:
1146 down_netdev(physdev_name)
1148 # If we are bringing up a bond, remove IP addresses from the
1149 # slaves (because we are implicitly being asked to take them down).
1151 # Conversely, if we are bringing up an interface that has bond
1152 # masters, remove IP addresses from the bond master (because we
1153 # are implicitly being asked to take it down).
1154 for bond_pif in bond_slaves + bond_masters:
1155 run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0'])
1157 # Remove all keys related to pif and any bond masters linked to PIF.
1158 del_ports = [ipdev] + physdev_names + bond_masters
1159 if vlan_slave and bond_master:
1160 del_ports += [interface_name(bond_master)]
1162 # What ports do we need to add to the datapath?
1164 # We definitely need the ipdev, and ordinarily we want the
1165 # physical devices too, but for bonds we need the bond as bridge
1167 add_ports = [ipdev, datapath]
1169 add_ports += physdev_names
1171 add_ports += [interface_name(bond_master)]
1173 # What ports do we need to delete?
1175 # - All the ports that we add, to avoid duplication and to drop
1176 # them from another datapath in case they're misassigned.
1178 # - The physical devices, since they will either be in add_ports
1179 # or added to the bonding device (see below).
1181 # - The bond masters for pif. (Ordinarily pif shouldn't have any
1182 # bond masters. If it does then interface-reconfigure is
1183 # implicitly being asked to take them down.)
1184 del_ports = add_ports + physdev_names + bond_masters
1186 # What networks does this datapath carry?
1188 # - The network corresponding to the datapath's PIF.
1190 # - The networks corresponding to any VLANs attached to the
1193 for nwpif in db.get_pifs_by_device({'device': pifrec['device']}):
1194 net = db.get_pif_record(nwpif)['network']
1195 network_uuids += [db.get_network_record(net)['uuid']]
1197 # Bring up bond slaves early, because ovs-vswitchd initially
1198 # enables or disables bond slaves based on whether carrier is
1199 # detected when they are added, and a network device that is down
1200 # always reports "no carrier".
1201 bond_slave_physdev_pifs = []
1202 for slave in bond_slaves:
1203 bond_slave_physdev_pifs += get_physdev_pifs(slave)
1204 for slave_physdev_pif in set(bond_slave_physdev_pifs):
1205 configure_physdev(slave_physdev_pif)
1207 # Now modify the ovs-vswitchd config file.
1209 for port in set(del_ports):
1210 argv += interface_deconfigure_commands(port)
1211 for port in set(add_ports):
1212 argv += ['--add=bridge.%s.port=%s' % (datapath, port)]
1214 argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
1215 argv += ['--add=iface.%s.internal=true' % (ipdev)]
1217 # xapi creates a bridge by the name of the ipdev and requires
1218 # that the IP address will be on it. We need to delete this
1219 # bridge because we need that device to be a member of our
1221 argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev]
1223 # xapi insists that its attempts to create the bridge succeed,
1224 # so force that to happen.
1225 argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
1228 os.unlink("%s/br-%s" % (vswitch_state_dir, bridge))
1231 argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath]
1232 argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid)
1233 for uuid in set(network_uuids)]
1235 argv += configure_bond(bond_master)
1238 # Bring up VLAN slave, plus physical devices other than bond
1239 # slaves (which we brought up earlier).
1241 up_netdev(ipdev_name(vlan_slave))
1242 for physdev_pif in set(physdev_pifs) - set(bond_slave_physdev_pifs):
1243 configure_physdev(physdev_pif)
1245 # Configure network device for local port.
1246 configure_local_port(pif)
1248 # Update /etc/issue (which contains the IP address of the management interface)
1249 os.system("/sbin/update-issue")
1252 # There seems to be a race somewhere: without this sleep, using
1253 # XenCenter to create a bond that becomes the management interface
1254 # fails with "The underlying connection was closed: A connection that
1255 # was expected to be kept alive was closed by the server." on every
1256 # second or third try, even though /var/log/messages doesn't show
1259 # The race is probably present even without vswitch, but bringing up a
1260 # bond without vswitch involves a built-in pause of 10 seconds or more
1261 # to wait for the bond to transition from learning to forwarding state.
1264 def action_down(pif):
1265 rec = db.get_pif_record(pif)
1266 interface = interface_name(pif)
1267 bridge = bridge_name(pif)
1268 ipdev = ipdev_name(pif)
1270 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1271 # files up-to-date, even though we don't use them or need them.
1272 f = unconfigure_pif(pif)
1274 br = open_network_ifcfg(pif)
1275 log("Unlinking stale file %s" % br.path())
1282 log("action_down failed to apply changes: %s" % e.msg)
1287 if rec['VLAN'] != '-1':
1288 # Get rid of the VLAN device itself.
1290 argv += interface_deconfigure_commands(ipdev)
1292 # If the VLAN's slave is attached, stop here.
1293 slave = get_vlan_slave_of_pif(pif)
1294 if db.get_pif_record(slave)['currently_attached']:
1295 log("VLAN slave is currently attached")
1299 # If the VLAN's slave has other VLANs that are attached, stop here.
1300 masters = get_vlan_masters_of_pif(slave)
1302 if m != pif and db.get_pif_record(m)['currently_attached']:
1303 log("VLAN slave has other master %s" % interface_naem(m))
1307 # Otherwise, take down the VLAN's slave too.
1308 log("No more masters, bring down vlan slave %s" % interface_name(slave))
1311 # Stop here if this PIF has attached VLAN masters.
1312 vlan_masters = get_vlan_masters_of_pif(pif)
1313 log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters]))
1314 for m in vlan_masters:
1315 if db.get_pif_record(m)['currently_attached']:
1316 log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m)))
1319 # pif is now either a bond or a physical device which needs to be
1320 # brought down. pif might have changed so re-check all its attributes.
1321 rec = db.get_pif_record(pif)
1322 interface = interface_name(pif)
1323 bridge = bridge_name(pif)
1324 ipdev = ipdev_name(pif)
1327 bond_slaves = get_bond_slaves_of_pif(pif)
1328 log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves]))
1329 for slave in bond_slaves:
1330 slave_interface = interface_name(slave)
1331 log("bring down bond slave %s" % slave_interface)
1332 argv += interface_deconfigure_commands(slave_interface)
1333 down_netdev(slave_interface)
1335 argv += interface_deconfigure_commands(ipdev)
1338 argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)]
1339 argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface]
1342 def action_rewrite(pif):
1343 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1344 # files up-to-date, even though we don't use them or need them.
1345 pifrec = db.get_pif_record(pif)
1346 f = configure_pif(pif)
1347 interface = interface_name(pif)
1348 bridge = bridge_name(pif)
1349 mode = pifrec['ip_configuration_mode']
1351 log("Configuring %s using %s configuration" % (bridge, mode))
1352 br = open_network_ifcfg(pif)
1353 configure_network(pif, br)
1357 log("Configuring %s using %s configuration" % (interface, mode))
1358 configure_network(pif, f)
1364 log("failed to apply changes: %s" % e.msg)
1368 # We have no code of our own to run here.
1371 def main(argv=None):
1372 global output_directory, management_pif
1378 force_interface = None
1379 force_management = False
1387 longops = [ "output-directory=",
1388 "pif=", "pif-uuid=",
1394 "device=", "mode=", "ip=", "netmask=", "gateway=",
1396 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1397 except getopt.GetoptError, msg:
1400 force_rewrite_config = {}
1403 if o == "--output-directory":
1404 output_directory = a
1407 elif o == "--pif-uuid":
1409 elif o == "--session":
1411 elif o == "--force-interface" or o == "--force":
1413 elif o == "--management":
1414 force_management = True
1415 elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1416 force_rewrite_config[o[2:]] = a
1417 elif o == "-h" or o == "--help":
1418 print __doc__ % {'command-name': os.path.basename(argv[0])}
1421 if not debug_mode():
1422 syslog.openlog(os.path.basename(argv[0]))
1423 log("Called as " + str.join(" ", argv))
1425 raise Usage("Required option <action> not present")
1427 raise Usage("Too many arguments")
1430 # backwards compatibility
1431 if action == "rewrite-configuration": action = "rewrite"
1433 if output_directory and ( session or pif ):
1434 raise Usage("--session/--pif cannot be used with --output-directory")
1435 if ( session or pif ) and pif_uuid:
1436 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1437 if ( session and not pif ) or ( not session and pif ):
1438 raise Usage("--session and --pif must be used together.")
1439 if force_interface and ( session or pif or pif_uuid ):
1440 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1441 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1442 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1446 log("Force interface %s %s" % (force_interface, action))
1448 if action == "rewrite":
1449 action_force_rewrite(force_interface, force_rewrite_config)
1451 db = DatabaseCache(cache_file=dbcache_file)
1452 pif = db.get_pif_by_bridge(force_interface)
1453 management_pif = db.get_management_pif()
1457 elif action == "down":
1460 raise Usage("Unknown action %s" % action)
1462 db = DatabaseCache(session_ref=session)
1465 pif = db.get_pif_by_uuid(pif_uuid)
1468 raise Usage("No PIF given")
1470 if force_management:
1471 # pif is going to be the management pif
1472 management_pif = pif
1474 # pif is not going to be the management pif.
1475 # Search DB cache for pif on same host with management=true
1476 pifrec = db.get_pif_record(pif)
1477 management_pif = db.get_management_pif()
1479 log_pif_action(action, pif)
1481 if not check_allowed(pif):
1486 elif action == "down":
1488 elif action == "rewrite":
1491 raise Usage("Unknown action %s" % action)
1494 db.save(dbcache_file)
1497 print >>sys.stderr, err.msg
1498 print >>sys.stderr, "For help use --help."
1506 # The following code allows interface-reconfigure to keep Centos
1507 # network configuration files up-to-date, even though the vswitch
1508 # never uses them. In turn, that means that "rpm -e vswitch" does not
1509 # have to update any configuration files.
1511 def configure_ethtool(oc, f):
1512 # Options for "ethtool -s"
1514 setting_opts = ["autoneg", "speed", "duplex"]
1515 # Options for "ethtool -K"
1517 offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
1519 for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
1520 val = oc["ethtool-" + opt]
1522 if opt in ["speed"]:
1523 if val in ["10", "100", "1000"]:
1524 val = "speed " + val
1526 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
1528 elif opt in ["duplex"]:
1529 if val in ["half", "full"]:
1530 val = "duplex " + val
1532 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
1534 elif opt in ["autoneg"] + offload_opts:
1535 if val in ["true", "on"]:
1537 elif val in ["false", "off"]:
1540 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
1543 if opt in setting_opts:
1544 if val and settings:
1545 settings = settings + " " + val
1548 elif opt in offload_opts:
1550 offload = offload + " " + val
1555 f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
1557 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
1559 def configure_mtu(oc, f):
1560 if not oc.has_key('mtu'):
1564 mtu = int(oc['mtu'])
1565 f.write("MTU=%d\n" % mtu)
1566 except ValueError, x:
1567 log("Invalid value for mtu = %s" % mtu)
1569 def configure_static_routes(interface, oc, f):
1570 """Open a route-<interface> file for static routes.
1572 Opens the static routes configuration file for interface and writes one
1573 line for each route specified in the network's other config "static-routes" value.
1575 interface ( RO): xenbr1
1576 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1578 Then route-xenbr1 should be
1579 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1580 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1582 fname = "route-%s" % interface
1583 if oc.has_key('static-routes'):
1584 # The key is present - extract comma seperates entries
1585 lines = oc['static-routes'].split(',')
1587 # The key is not present, i.e. there are no static routes
1590 child = ConfigurationFile(fname)
1591 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1592 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
1596 network, masklen, gateway = l.split('/')
1597 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1599 f.attach_child(child)
1602 except ValueError, e:
1603 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1605 def __open_ifcfg(interface):
1606 """Open a network interface configuration file.
1608 Opens the configuration file for interface, writes a header and
1609 common options and returns the file object.
1611 fname = "ifcfg-%s" % interface
1612 f = ConfigurationFile(fname)
1614 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1615 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1616 f.write("XEMANAGED=yes\n")
1617 f.write("DEVICE=%s\n" % interface)
1618 f.write("ONBOOT=no\n")
1622 def open_network_ifcfg(pif):
1623 bridge = bridge_name(pif)
1624 interface = interface_name(pif)
1626 return __open_ifcfg(bridge)
1628 return __open_ifcfg(interface)
1631 def open_pif_ifcfg(pif):
1632 pifrec = db.get_pif_record(pif)
1634 log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
1636 f = __open_ifcfg(interface_name(pif))
1638 if pifrec.has_key('other_config'):
1639 configure_ethtool(pifrec['other_config'], f)
1640 configure_mtu(pifrec['other_config'], f)
1644 def configure_network(pif, f):
1645 """Write the configuration file for a network.
1647 Writes configuration derived from the network object into the relevant
1648 ifcfg file. The configuration file is passed in, but if the network is
1649 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1651 This routine may also write ifcfg files of the networks corresponding to other PIFs
1652 in order to maintain consistency.
1655 pif: Opaque_ref of pif
1656 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1659 pifrec = db.get_pif_record(pif)
1660 nw = pifrec['network']
1661 nwrec = db.get_network_record(nw)
1663 bridge = bridge_name(pif)
1664 interface = interface_name(pif)
1670 if nwrec.has_key('other_config'):
1671 configure_ethtool(nwrec['other_config'], f)
1672 configure_mtu(nwrec['other_config'], f)
1673 configure_static_routes(device, nwrec['other_config'], f)
1676 if pifrec.has_key('other_config'):
1677 oc = pifrec['other_config']
1679 if device == bridge:
1680 f.write("TYPE=Bridge\n")
1681 f.write("DELAY=0\n")
1682 f.write("STP=off\n")
1683 f.write("PIFDEV=%s\n" % interface_name(pif))
1685 if pifrec['ip_configuration_mode'] == "DHCP":
1686 f.write("BOOTPROTO=dhcp\n")
1687 f.write("PERSISTENT_DHCLIENT=yes\n")
1688 elif pifrec['ip_configuration_mode'] == "Static":
1689 f.write("BOOTPROTO=none\n")
1690 f.write("NETMASK=%(netmask)s\n" % pifrec)
1691 f.write("IPADDR=%(IP)s\n" % pifrec)
1692 f.write("GATEWAY=%(gateway)s\n" % pifrec)
1693 elif pifrec['ip_configuration_mode'] == "None":
1694 f.write("BOOTPROTO=none\n")
1696 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1698 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1699 ServerList = pifrec['DNS'].split(",")
1700 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1701 if oc and oc.has_key('domain'):
1702 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1704 # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1705 # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1706 # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1708 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1710 # Note: we prune out the bond master pif (if it exists).
1711 # This is because when we are called to bring up an interface with a bond master, it is implicit that
1712 # we should bring down that master.
1713 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1714 not __pif in get_bond_masters_of_pif(pif) ]
1715 other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1718 defaultroute_pif = None
1720 # loop through all the pifs on this host looking for one with
1721 # other-config:peerdns = true, and one with
1722 # other-config:default-route=true
1723 for __pif in pifs_on_host:
1724 __pifrec = db.get_pif_record(__pif)
1725 __oc = __pifrec['other_config']
1726 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1727 if peerdns_pif == None:
1730 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1731 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1732 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1733 if defaultroute_pif == None:
1734 defaultroute_pif = __pif
1736 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1737 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1739 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1740 if peerdns_pif == None:
1741 peerdns_pif = management_pif
1742 if defaultroute_pif == None:
1743 defaultroute_pif = management_pif
1745 # Update all the other network's ifcfg files and ensure consistency
1746 for __pif in other_pifs_on_host:
1747 __f = open_network_ifcfg(__pif)
1748 peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1749 lines = __f.readlines()
1751 if not peerdns_line_wanted in lines:
1752 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1754 if not line.lstrip().startswith('PEERDNS'):
1756 log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1757 __f.write(peerdns_line_wanted)
1762 # There is no need to change this ifcfg file. So don't attach_child.
1765 # ... and for this pif too
1766 f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1769 fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1770 for line in fnetwork.readlines():
1771 if line.lstrip().startswith('GATEWAY') :
1773 fnetwork.write(line)
1774 if defaultroute_pif:
1775 gatewaydev = bridge_name(defaultroute_pif)
1777 gatewaydev = interface_name(defaultroute_pif)
1778 fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1780 f.attach_child(fnetwork)
1785 def configure_physical_interface(pif):
1786 """Write the configuration for a physical interface.
1788 Writes the configuration file for the physical interface described by
1791 Returns the open file handle for the interface configuration file.
1794 pifrec = db.get_pif_record(pif)
1796 f = open_pif_ifcfg(pif)
1798 f.write("TYPE=Ethernet\n")
1799 f.write("HWADDR=%(MAC)s\n" % pifrec)
1803 def configure_bond_interface(pif):
1804 """Write the configuration for a bond interface.
1806 Writes the configuration file for the bond interface described by
1807 the pif object. Handles writing the configuration for the slave
1810 Returns the open file handle for the bond interface configuration
1814 pifrec = db.get_pif_record(pif)
1815 oc = pifrec['other_config']
1816 f = open_pif_ifcfg(pif)
1818 if pifrec['MAC'] != "":
1819 f.write("MACADDR=%s\n" % pifrec['MAC'])
1821 for slave in get_bond_slaves_of_pif(pif):
1822 s = configure_physical_interface(slave)
1823 s.write("MASTER=%(device)s\n" % pifrec)
1824 s.write("SLAVE=yes\n")
1828 # The bond option defaults
1830 "mode": "balance-slb",
1837 # override defaults with values from other-config whose keys being with "bond-"
1838 overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
1839 overrides = map(lambda (key,val): (key[5:], val), overrides)
1840 bond_options.update(overrides)
1842 # write the bond options to ifcfg-bondX
1843 f.write('BONDING_OPTS="')
1844 for (name,val) in bond_options.items():
1845 f.write("%s=%s " % (name,val))
1849 def configure_vlan_interface(pif):
1850 """Write the configuration for a VLAN interface.
1852 Writes the configuration file for the VLAN interface described by
1853 the pif object. Handles writing the configuration for the master
1854 interface if necessary.
1856 Returns the open file handle for the VLAN interface configuration
1860 slave = configure_pif(get_vlan_slave_of_pif(pif))
1863 f = open_pif_ifcfg(pif)
1864 f.write("VLAN=yes\n")
1865 f.attach_child(slave)
1869 def configure_pif(pif):
1870 """Write the configuration for a PIF object.
1872 Writes the configuration file the PIF and all dependent
1873 interfaces (bond slaves and VLAN masters etc).
1875 Returns the open file handle for the interface configuration file.
1878 pifrec = db.get_pif_record(pif)
1880 if pifrec['VLAN'] != '-1':
1881 f = configure_vlan_interface(pif)
1882 elif len(pifrec['bond_master_of']) != 0:
1883 f = configure_bond_interface(pif)
1885 f = configure_physical_interface(pif)
1887 bridge = bridge_name(pif)
1889 f.write("BRIDGE=%s\n" % bridge)
1893 def unconfigure_pif(pif):
1894 """Clear up the files created by configure_pif"""
1895 f = open_pif_ifcfg(pif)
1896 log("Unlinking stale file %s" % f.path())
1900 if __name__ == "__main__":
1906 err = traceback.format_exception(*ex)
1910 if not debug_mode():