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)
41 # XXX: --force-interface=all down
43 # XXX: --force-interface rewrite
45 # XXX: Sometimes this leaves "orphaned" datapaths, e.g. a datapath whose
46 # only port is the local port. Should delete those.
48 # XXX: This can leave crud in ovs-vswitchd.conf in this scenario:
49 # - Create bond in XenCenter.
50 # - Create VLAN on bond in XenCenter.
51 # - Attempt to delete bond in XenCenter (this will fail because there
52 # is a VLAN on the bond, although the error may not be reported
53 # until the next step)
54 # - Delete VLAN in XenCenter.
55 # - Delete bond in XenCenter.
56 # At this point there will still be some configuration data for the bond
57 # or the VLAN in ovs-vswitchd.conf.
60 import os, sys, getopt, time, signal
66 from xml.dom.minidom import getDOMImplementation
67 from xml.dom.minidom import parse as parseXML
69 output_directory = None
74 vswitch_state_dir = "/var/lib/openvswitch/"
75 dbcache_file = vswitch_state_dir + "dbcache"
77 class Usage(Exception):
78 def __init__(self, msg):
79 Exception.__init__(self)
82 class Error(Exception):
83 def __init__(self, msg):
84 Exception.__init__(self)
87 class ConfigurationFile(object):
88 """Write a file, tracking old and new versions.
90 Supports writing a new version of a file and applying and
91 reverting those changes.
94 __STATE = {"OPEN":"OPEN",
95 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
96 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
98 def __init__(self, fname, path="/etc/sysconfig/network-scripts"):
100 self.__state = self.__STATE['OPEN']
105 dirname = output_directory
109 self.__path = os.path.join(dirname, fname)
110 self.__oldpath = os.path.join(dirname, "." + fname + ".xapi-old")
111 self.__newpath = os.path.join(dirname, "." + fname + ".xapi-new")
112 self.__unlink = False
114 self.__f = open(self.__newpath, "w")
116 def attach_child(self, child):
117 self.__children.append(child)
124 return open(self.path()).readlines()
128 def write(self, args):
129 if self.__state != self.__STATE['OPEN']:
130 raise Error("Attempt to write to file in state %s" % self.__state)
134 if self.__state != self.__STATE['OPEN']:
135 raise Error("Attempt to unlink file in state %s" % self.__state)
138 self.__state = self.__STATE['NOT-APPLIED']
141 if self.__state != self.__STATE['OPEN']:
142 raise Error("Attempt to close file in state %s" % self.__state)
145 self.__state = self.__STATE['NOT-APPLIED']
148 if self.__state != self.__STATE['NOT-APPLIED']:
149 raise Error("Attempt to compare file in state %s" % self.__state)
154 if self.__state != self.__STATE['NOT-APPLIED']:
155 raise Error("Attempt to apply configuration from state %s" % self.__state)
157 for child in self.__children:
160 log("Applying changes to %s configuration" % self.__fname)
162 # Remove previous backup.
163 if os.access(self.__oldpath, os.F_OK):
164 os.unlink(self.__oldpath)
166 # Save current configuration.
167 if os.access(self.__path, os.F_OK):
168 os.link(self.__path, self.__oldpath)
169 os.unlink(self.__path)
171 # Apply new configuration.
172 assert(os.path.exists(self.__newpath))
173 if not self.__unlink:
174 os.link(self.__newpath, self.__path)
176 pass # implicit unlink of original file
178 # Remove temporary file.
179 os.unlink(self.__newpath)
181 self.__state = self.__STATE['APPLIED']
184 if self.__state != self.__STATE['APPLIED']:
185 raise Error("Attempt to revert configuration from state %s" % self.__state)
187 for child in self.__children:
190 log("Reverting changes to %s configuration" % self.__fname)
192 # Remove existing new configuration
193 if os.access(self.__newpath, os.F_OK):
194 os.unlink(self.__newpath)
196 # Revert new configuration.
197 if os.access(self.__path, os.F_OK):
198 os.link(self.__path, self.__newpath)
199 os.unlink(self.__path)
201 # Revert to old configuration.
202 if os.access(self.__oldpath, os.F_OK):
203 os.link(self.__oldpath, self.__path)
204 os.unlink(self.__oldpath)
206 # Leave .*.xapi-new as an aid to debugging.
208 self.__state = self.__STATE['REVERTED']
211 if self.__state != self.__STATE['APPLIED']:
212 raise Error("Attempt to commit configuration from state %s" % self.__state)
214 for child in self.__children:
217 log("Committing changes to %s configuration" % self.__fname)
219 if os.access(self.__oldpath, os.F_OK):
220 os.unlink(self.__oldpath)
221 if os.access(self.__newpath, os.F_OK):
222 os.unlink(self.__newpath)
224 self.__state = self.__STATE['COMMITTED']
227 return output_directory is not None
231 print >>sys.stderr, s
235 def check_allowed(pif):
236 pifrec = db.get_pif_record(pif)
238 f = open("/proc/ardence")
239 macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
241 if len(macline) == 1:
242 p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
243 if p.match(macline[0]):
244 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
250 def interface_exists(i):
251 return os.path.exists("/sys/class/net/" + i)
253 def get_netdev_mac(device):
255 return read_first_line_of_file("/sys/class/net/%s/address" % device)
257 # Probably no such device.
260 def get_netdev_tx_queue_len(device):
262 return int(read_first_line_of_file("/sys/class/net/%s/tx_queue_len"
265 # Probably no such device.
268 def get_netdev_by_mac(mac):
269 for device in os.listdir("/sys/class/net"):
270 dev_mac = get_netdev_mac(device)
271 if (dev_mac and mac.lower() == dev_mac.lower() and
272 get_netdev_tx_queue_len(device)):
277 # Helper functions for encoding/decoding database attributes to/from XML.
279 def str_to_xml(xml, parent, tag, val):
280 e = xml.createElement(tag)
281 parent.appendChild(e)
282 v = xml.createTextNode(val)
285 def getText(nodelist):
287 for node in nodelist:
288 if node.nodeType == node.TEXT_NODE:
291 return getText(n.childNodes).strip()
294 def bool_to_xml(xml, parent, tag, val):
296 str_to_xml(xml, parent, tag, "True")
298 str_to_xml(xml, parent, tag, "False")
299 def bool_from_xml(n):
306 raise Error("Unknown boolean value %s" % s);
308 def strlist_to_xml(xml, parent, ltag, itag, val):
309 e = xml.createElement(ltag)
310 parent.appendChild(e)
312 c = xml.createElement(itag)
314 cv = xml.createTextNode(v)
316 def strlist_from_xml(n, ltag, itag):
318 for n in n.childNodes:
319 if n.nodeName == itag:
320 ret.append(str_from_xml(n))
323 def otherconfig_to_xml(xml, parent, val, attrs):
324 otherconfig = xml.createElement("other_config")
325 parent.appendChild(otherconfig)
326 for n,v in val.items():
328 raise Error("Unknown other-config attribute: %s" % n)
329 str_to_xml(xml, otherconfig, n, v)
330 def otherconfig_from_xml(n, attrs):
332 for n in n.childNodes:
333 if n.nodeName in attrs:
334 ret[n.nodeName] = str_from_xml(n)
338 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
340 # Each object is defined by a dictionary mapping an attribute name in
341 # the xapi database to a tuple containing two items:
342 # - a function which takes this attribute and encodes it as XML.
343 # - a function which takes XML and decocdes it into a value.
345 # other-config attributes are specified as a simple array of strings
348 VLAN_XML_TAG = "vlan"
349 BOND_XML_TAG = "bond"
350 NETWORK_XML_TAG = "network"
352 ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
354 PIF_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
355 'management': (bool_to_xml,bool_from_xml),
356 'network': (str_to_xml,str_from_xml),
357 'device': (str_to_xml,str_from_xml),
358 'bond_master_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
359 lambda n: strlist_from_xml(n, 'bond_master_of', 'slave')),
360 'bond_slave_of': (str_to_xml,str_from_xml),
361 'VLAN': (str_to_xml,str_from_xml),
362 'VLAN_master_of': (str_to_xml,str_from_xml),
363 'VLAN_slave_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
364 lambda n: strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
365 'ip_configuration_mode': (str_to_xml,str_from_xml),
366 'IP': (str_to_xml,str_from_xml),
367 'netmask': (str_to_xml,str_from_xml),
368 'gateway': (str_to_xml,str_from_xml),
369 'DNS': (str_to_xml,str_from_xml),
370 'MAC': (str_to_xml,str_from_xml),
371 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, PIF_OTHERCONFIG_ATTRS),
372 lambda n: otherconfig_from_xml(n, PIF_OTHERCONFIG_ATTRS)),
374 # Special case: We write the current value
375 # PIF.currently-attached to the cache but since it will
376 # not be valid when we come to use the cache later
377 # (i.e. after a reboot) we always read it as False.
378 'currently_attached': (bool_to_xml, lambda n: False),
381 PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
382 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
383 ETHTOOL_OTHERCONFIG_ATTRS
385 VLAN_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
386 'tagged_PIF': (str_to_xml,str_from_xml),
387 'untagged_PIF': (str_to_xml,str_from_xml),
390 BOND_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
391 'master': (str_to_xml,str_from_xml),
392 'slaves': (lambda x, p, t, v: strlist_to_xml(x, p, 'slaves', 'slave', v),
393 lambda n: strlist_from_xml(n, 'slaves', 'slave')),
396 NETWORK_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
397 'bridge': (str_to_xml,str_from_xml),
398 'PIFs': (lambda x, p, t, v: strlist_to_xml(x, p, 'PIFs', 'PIF', v),
399 lambda n: strlist_from_xml(n, 'PIFs', 'PIF')),
400 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, NETWORK_OTHERCONFIG_ATTRS),
401 lambda n: otherconfig_from_xml(n, NETWORK_OTHERCONFIG_ATTRS)),
404 NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + ETHTOOL_OTHERCONFIG_ATTRS
406 class DatabaseCache(object):
407 def __read_xensource_inventory(self):
408 filename = "/etc/xensource-inventory"
409 f = open(filename, "r")
410 lines = [x.strip("\n") for x in f.readlines()]
413 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
414 defs = [ (a, b.strip("'")) for (a,b) in defs ]
417 def __pif_on_host(self,pif):
418 return self.__pifs.has_key(pif)
420 def __get_pif_records_from_xapi(self, session, host):
422 for (p,rec) in session.xenapi.PIF.get_all_records().items():
423 if rec['host'] != host:
427 self.__pifs[p][f] = rec[f]
428 self.__pifs[p]['other_config'] = {}
429 for f in PIF_OTHERCONFIG_ATTRS:
430 if not rec['other_config'].has_key(f): continue
431 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
433 def __get_vlan_records_from_xapi(self, session):
435 for v in session.xenapi.VLAN.get_all():
436 rec = session.xenapi.VLAN.get_record(v)
437 if not self.__pif_on_host(rec['untagged_PIF']):
441 self.__vlans[v][f] = rec[f]
443 def __get_bond_records_from_xapi(self, session):
445 for b in session.xenapi.Bond.get_all():
446 rec = session.xenapi.Bond.get_record(b)
447 if not self.__pif_on_host(rec['master']):
451 self.__bonds[b][f] = rec[f]
453 def __get_network_records_from_xapi(self, session):
455 for n in session.xenapi.network.get_all():
456 rec = session.xenapi.network.get_record(n)
457 self.__networks[n] = {}
458 for f in NETWORK_ATTRS:
460 # drop PIFs on other hosts
461 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
463 self.__networks[n][f] = rec[f]
464 self.__networks[n]['other_config'] = {}
465 for f in NETWORK_OTHERCONFIG_ATTRS:
466 if not rec['other_config'].has_key(f): continue
467 self.__networks[n]['other_config'][f] = rec['other_config'][f]
469 def __to_xml(self, xml, parent, key, ref, rec, attrs):
470 """Encode a database object as XML"""
471 e = xml.createElement(key)
472 parent.appendChild(e)
474 e.setAttribute('ref', ref)
476 for n,v in rec.items():
481 raise Error("Unknown attribute %s" % n)
482 def __from_xml(self, e, attrs):
483 """Decode a database object from XML"""
484 ref = e.attributes['ref'].value
486 for n in e.childNodes:
487 if n.nodeName in attrs:
488 _,h = attrs[n.nodeName]
489 rec[n.nodeName] = h(n)
492 def __init__(self, session_ref=None, cache_file=None):
493 if session_ref and cache_file:
494 raise Error("can't specify session reference and cache file")
495 if cache_file == None:
496 session = XenAPI.xapi_local()
499 log("No session ref given on command line, logging in.")
500 session.xenapi.login_with_password("root", "")
502 session._session = session_ref
506 inventory = self.__read_xensource_inventory()
507 assert(inventory.has_key('INSTALLATION_UUID'))
508 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
510 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
512 self.__get_pif_records_from_xapi(session, host)
514 self.__get_vlan_records_from_xapi(session)
515 self.__get_bond_records_from_xapi(session)
516 self.__get_network_records_from_xapi(session)
519 session.xenapi.session.logout()
521 log("Loading xapi database cache from %s" % cache_file)
523 xml = parseXML(cache_file)
530 assert(len(xml.childNodes) == 1)
531 toplevel = xml.childNodes[0]
533 assert(toplevel.nodeName == "xenserver-network-configuration")
535 for n in toplevel.childNodes:
536 if n.nodeName == "#text":
538 elif n.nodeName == PIF_XML_TAG:
539 (ref,rec) = self.__from_xml(n, PIF_ATTRS)
540 self.__pifs[ref] = rec
541 elif n.nodeName == BOND_XML_TAG:
542 (ref,rec) = self.__from_xml(n, BOND_ATTRS)
543 self.__bonds[ref] = rec
544 elif n.nodeName == VLAN_XML_TAG:
545 (ref,rec) = self.__from_xml(n, VLAN_ATTRS)
546 self.__vlans[ref] = rec
547 elif n.nodeName == NETWORK_XML_TAG:
548 (ref,rec) = self.__from_xml(n, NETWORK_ATTRS)
549 self.__networks[ref] = rec
551 raise Error("Unknown XML element %s" % n.nodeName)
553 def save(self, cache_file):
555 xml = getDOMImplementation().createDocument(
556 None, "xenserver-network-configuration", None)
557 for (ref,rec) in self.__pifs.items():
558 self.__to_xml(xml, xml.documentElement, PIF_XML_TAG, ref, rec, PIF_ATTRS)
559 for (ref,rec) in self.__bonds.items():
560 self.__to_xml(xml, xml.documentElement, BOND_XML_TAG, ref, rec, BOND_ATTRS)
561 for (ref,rec) in self.__vlans.items():
562 self.__to_xml(xml, xml.documentElement, VLAN_XML_TAG, ref, rec, VLAN_ATTRS)
563 for (ref,rec) in self.__networks.items():
564 self.__to_xml(xml, xml.documentElement, NETWORK_XML_TAG, ref, rec,
567 f = open(cache_file, 'w')
568 f.write(xml.toprettyxml())
571 def get_pif_by_uuid(self, uuid):
572 pifs = map(lambda (ref,rec): ref,
573 filter(lambda (ref,rec): uuid == rec['uuid'],
574 self.__pifs.items()))
576 raise Error("Unknown PIF \"%s\"" % uuid)
578 raise Error("Non-unique PIF \"%s\"" % uuid)
582 def get_pifs_by_device(self, device):
583 return map(lambda (ref,rec): ref,
584 filter(lambda (ref,rec): rec['device'] == device,
585 self.__pifs.items()))
587 def get_pif_by_bridge(self, bridge):
588 networks = map(lambda (ref,rec): ref,
589 filter(lambda (ref,rec): rec['bridge'] == bridge,
590 self.__networks.items()))
591 if len(networks) == 0:
592 raise Error("No matching network \"%s\"")
595 for network in networks:
596 nwrec = self.get_network_record(network)
597 for pif in nwrec['PIFs']:
598 pifrec = self.get_pif_record(pif)
600 raise Error("Multiple PIFs on host for network %s" % (bridge))
603 raise Error("No PIF on host for network %s" % (bridge))
606 def get_pif_record(self, pif):
607 if self.__pifs.has_key(pif):
608 return self.__pifs[pif]
609 raise Error("Unknown PIF \"%s\" (get_pif_record)" % pif)
610 def get_all_pifs(self):
612 def pif_exists(self, pif):
613 return self.__pifs.has_key(pif)
615 def get_management_pif(self):
616 """ Returns the management pif on host
618 all = self.get_all_pifs()
620 pifrec = self.get_pif_record(pif)
621 if pifrec['management']: return pif
624 def get_network_record(self, network):
625 if self.__networks.has_key(network):
626 return self.__networks[network]
627 raise Error("Unknown network \"%s\"" % network)
628 def get_all_networks(self):
629 return self.__networks
631 def get_bond_record(self, bond):
632 if self.__bonds.has_key(bond):
633 return self.__bonds[bond]
637 def get_vlan_record(self, vlan):
638 if self.__vlans.has_key(vlan):
639 return self.__vlans[vlan]
643 def bridge_name(pif):
644 """Return the bridge name associated with pif, or None if network is bridgeless"""
645 pifrec = db.get_pif_record(pif)
646 nwrec = db.get_network_record(pifrec['network'])
649 # TODO: sanity check that nwrec['bridgeless'] != 'true'
650 return nwrec['bridge']
652 # TODO: sanity check that nwrec['bridgeless'] == 'true'
655 def interface_name(pif):
656 """Construct an interface name from the given PIF record."""
658 pifrec = db.get_pif_record(pif)
660 if pifrec['VLAN'] == '-1':
661 return pifrec['device']
663 return "%(device)s.%(VLAN)s" % pifrec
665 def datapath_name(pif):
666 """Return the OpenFlow datapath name associated with pif.
667 For a non-VLAN PIF, the datapath name is the bridge name.
668 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
669 (xapi will create a datapath named with the bridge name even though we won't
674 pifrec = db.get_pif_record(pif)
676 if pifrec['VLAN'] == '-1':
677 return bridge_name(pif)
679 return bridge_name(get_vlan_slave_of_pif(pif))
682 """Return the the name of the network device that carries the
683 IP configuration (if any) associated with pif.
684 The ipdev name is the same as the bridge name.
687 pifrec = db.get_pif_record(pif)
688 return bridge_name(pif)
690 def get_physdev_pifs(pif):
691 """Return the PIFs for the physical network device(s) associated with pif.
692 For a VLAN PIF, this is the VLAN slave's physical device PIF.
693 For a bond master PIF, these are the bond slave PIFs.
694 For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
696 pifrec = db.get_pif_record(pif)
698 if pifrec['VLAN'] != '-1':
699 return get_physdev_pifs(get_vlan_slave_of_pif(pif))
700 elif len(pifrec['bond_master_of']) != 0:
701 return get_bond_slaves_of_pif(pif)
705 def get_physdev_names(pif):
706 """Return the name(s) of the physical network device(s) associated with pif.
707 For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
708 For a bond master PIF, the physical devices are the bond slaves.
709 For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
712 return [db.get_pif_record(phys)['device'] for phys in get_physdev_pifs(pif)]
714 def log_pif_action(action, pif):
715 pifrec = db.get_pif_record(pif)
717 rec['uuid'] = pifrec['uuid']
718 rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
719 rec['action'] = action
720 rec['interface-name'] = interface_name(pif)
721 if action == "rewrite":
722 rec['message'] = "Rewrite PIF %(uuid)s configuration" % rec
724 rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
725 log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % rec)
727 def get_bond_masters_of_pif(pif):
728 """Returns a list of PIFs which are bond masters of this PIF"""
730 pifrec = db.get_pif_record(pif)
732 bso = pifrec['bond_slave_of']
734 # bond-slave-of is currently a single reference but in principle a
735 # PIF could be a member of several bonds which are not
736 # concurrently attached. Be robust to this possibility.
737 if not bso or bso == "OpaqueRef:NULL":
739 elif not type(bso) == list:
742 bondrecs = [db.get_bond_record(bond) for bond in bso]
743 bondrecs = [rec for rec in bondrecs if rec]
745 return [bond['master'] for bond in bondrecs]
747 def get_bond_slaves_of_pif(pif):
748 """Returns a list of PIFs which make up the given bonded pif."""
750 pifrec = db.get_pif_record(pif)
752 bmo = pifrec['bond_master_of']
754 raise Error("Bond-master-of contains too many elements")
759 bondrec = db.get_bond_record(bmo[0])
761 raise Error("No bond record for bond master PIF")
763 return bondrec['slaves']
765 def get_vlan_slave_of_pif(pif):
766 """Find the PIF which is the VLAN slave of pif.
768 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
770 pifrec = db.get_pif_record(pif)
772 vlan = pifrec['VLAN_master_of']
773 if not vlan or vlan == "OpaqueRef:NULL":
774 raise Error("PIF is not a VLAN master")
776 vlanrec = db.get_vlan_record(vlan)
778 raise Error("No VLAN record found for PIF")
780 return vlanrec['tagged_PIF']
782 def get_vlan_masters_of_pif(pif):
783 """Returns a list of PIFs which are VLANs on top of the given pif."""
785 pifrec = db.get_pif_record(pif)
786 vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
787 return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])]
789 def interface_deconfigure_commands(interface):
790 # The use of [!0-9] keeps an interface of 'eth0' from matching
791 # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
793 return ['--del-match=bridge.*.port=%s' % interface,
794 '--del-match=bonding.%s.[!0-9]*' % interface,
795 '--del-match=bonding.*.slave=%s' % interface,
796 '--del-match=vlan.%s.[!0-9]*' % interface,
797 '--del-match=port.%s.[!0-9]*' % interface,
798 '--del-match=iface.%s.[!0-9]*' % interface]
800 def run_command(command):
801 log("Running command: " + ' '.join(command))
802 if os.spawnl(os.P_WAIT, command[0], *command) != 0:
803 log("Command failed: " + ' '.join(command))
807 def rename_netdev(old_name, new_name):
808 log("Changing the name of %s to %s" % (old_name, new_name))
809 run_command(['/sbin/ifconfig', old_name, 'down'])
810 if not run_command(['/sbin/ip', 'link', 'set', old_name,
812 raise Error("Could not rename %s to %s" % (old_name, new_name))
814 # Check whether 'pif' exists and has the correct MAC.
815 # If not, try to find a device with the correct MAC and rename it.
816 # 'already_renamed' is used to avoid infinite recursion.
817 def remap_pif(pif, already_renamed=[]):
818 pifrec = db.get_pif_record(pif)
819 device = pifrec['device']
822 # Is there a network device named 'device' at all?
823 device_exists = interface_exists(device)
825 # Yes. Does it have MAC 'mac'?
826 found_mac = get_netdev_mac(device)
827 if found_mac and mac.lower() == found_mac.lower():
828 # Yes, everything checks out the way we want. Nothing to do.
831 log("No network device %s" % device)
833 # What device has MAC 'mac'?
834 cur_device = get_netdev_by_mac(mac)
836 log("No network device has MAC %s" % mac)
839 # First rename 'device', if it exists, to get it out of the way
840 # for 'cur_device' to replace it.
842 rename_netdev(device, "dev%d" % random.getrandbits(24))
844 # Rename 'cur_device' to 'device'.
845 rename_netdev(cur_device, device)
847 def read_first_line_of_file(name):
850 file = open(name, 'r')
851 return file.readline().rstrip('\n')
856 def down_netdev(interface, deconfigure=True):
857 if not interface_exists(interface):
858 log("down_netdev: interface %s does not exist, ignoring" % interface)
862 pidfile_name = '/var/run/dhclient-%s.pid' % interface
864 os.kill(int(read_first_line_of_file(pidfile_name)), signal.SIGTERM)
868 # Remove dhclient pidfile.
870 os.remove(pidfile_name)
874 run_command(["/sbin/ifconfig", interface, '0.0.0.0'])
876 run_command(["/sbin/ifconfig", interface, 'down'])
878 def up_netdev(interface):
879 run_command(["/sbin/ifconfig", interface, 'up'])
881 def find_distinguished_pifs(pif):
882 """Returns the PIFs on host that own DNS and the default route.
883 The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
884 The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
886 Note: we prune out the bond master pif (if it exists).
887 This is because when we are called to bring up an interface with a bond master, it is implicit that
888 we should bring down that master."""
890 pifrec = db.get_pif_record(pif)
892 pifs = [ __pif for __pif in db.get_all_pifs() if
893 (not __pif in get_bond_masters_of_pif(pif)) ]
896 defaultroute_pif = None
898 # loop through all the pifs on this host looking for one with
899 # other-config:peerdns = true, and one with
900 # other-config:default-route=true
902 __pifrec = db.get_pif_record(__pif)
903 __oc = __pifrec['other_config']
904 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
905 if peerdns_pif == None:
908 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
909 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
910 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
911 if defaultroute_pif == None:
912 defaultroute_pif = __pif
914 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
915 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
917 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
918 if peerdns_pif == None:
919 peerdns_pif = management_pif
920 if defaultroute_pif == None:
921 defaultroute_pif = management_pif
923 return peerdns_pif, defaultroute_pif
925 def run_ethtool(device, oc):
926 # Run "ethtool -s" if there are any settings.
928 if oc.has_key('ethtool-speed'):
929 val = oc['ethtool-speed']
930 if val in ["10", "100", "1000"]:
931 settings += ['speed', val]
933 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
934 if oc.has_key('ethtool-duplex'):
935 val = oc['ethtool-duplex']
936 if val in ["10", "100", "1000"]:
937 settings += ['duplex', 'val']
939 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
940 if oc.has_key('ethtool-autoneg'):
941 val = oc['ethtool-autoneg']
942 if val in ["true", "on"]:
943 settings += ['autoneg', 'on']
944 elif val in ["false", "off"]:
945 settings += ['autoneg', 'off']
947 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
949 run_command(['/sbin/ethtool', '-s', device] + settings)
951 # Run "ethtool -K" if there are any offload settings.
953 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
954 if oc.has_key("ethtool-" + opt):
955 val = oc["ethtool-" + opt]
956 if val in ["true", "on"]:
957 offload += [opt, 'on']
958 elif val in ["false", "off"]:
959 offload += [opt, 'off']
961 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
963 run_command(['/sbin/ethtool', '-K', device] + offload)
966 if oc.has_key('mtu'):
968 int(oc['mtu']) # Check that the value is an integer
969 return ['mtu', oc['mtu']]
970 except ValueError, x:
971 log("Invalid value for mtu = %s" % mtu)
974 def configure_local_port(pif):
975 pifrec = db.get_pif_record(pif)
976 datapath = datapath_name(pif)
977 ipdev = ipdev_name(pif)
979 nw = pifrec['network']
980 nwrec = db.get_network_record(nw)
982 pif_oc = pifrec['other_config']
983 nw_oc = nwrec['other_config']
985 # IP (except DHCP) and MTU.
986 ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up']
988 if pifrec['ip_configuration_mode'] == "DHCP":
990 elif pifrec['ip_configuration_mode'] == "Static":
991 ifconfig_argv += [pifrec['IP']]
992 ifconfig_argv += ['netmask', pifrec['netmask']]
993 gateway = pifrec['gateway']
994 elif pifrec['ip_configuration_mode'] == "None":
998 raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode'])
999 ifconfig_argv += mtu_setting(nw_oc)
1000 run_command(ifconfig_argv)
1002 (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif)
1005 if peerdns_pif == pif:
1006 f = ConfigurationFile('resolv.conf', "/etc")
1007 if pif_oc.has_key('domain'):
1008 f.write("search %s\n" % pif_oc['domain'])
1009 for dns in pifrec['DNS'].split(","):
1010 f.write("nameserver %s\n" % dns)
1016 if defaultroute_pif == pif and gateway != '':
1017 run_command(['/sbin/ip', 'route', 'replace', 'default',
1018 'via', gateway, 'dev', ipdev])
1019 if nw_oc.has_key('static-routes'):
1020 for line in nw_oc['static-routes'].split(','):
1021 network, masklen, gateway = line.split('/')
1022 run_command(['/sbin/ip', 'route', 'add',
1023 '%s/%s' % (network, masklen), 'via', gateway,
1027 run_ethtool(ipdev, nw_oc)
1030 if pifrec['ip_configuration_mode'] == "DHCP":
1032 print "Determining IP information for %s..." % ipdev,
1033 argv = ['/sbin/dhclient', '-q',
1034 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev,
1035 '-pf', '/var/run/dhclient-%s.pid' % ipdev,
1037 if run_command(argv):
1042 def configure_physdev(pif):
1043 pifrec = db.get_pif_record(pif)
1044 device = pifrec['device']
1045 oc = pifrec['other_config']
1047 run_command(['/sbin/ifconfig', device, 'up'] + mtu_setting(oc))
1048 run_ethtool(device, oc)
1050 def modify_config(commands):
1051 run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer',
1052 '-F', '/etc/ovs-vswitchd.conf']
1053 + commands + ['-c'])
1054 run_command(['/sbin/service', 'vswitch', 'reload'])
1056 def is_bond_pif(pif):
1057 pifrec = db.get_pif_record(pif)
1058 return len(pifrec['bond_master_of']) != 0
1060 def configure_bond(pif):
1061 pifrec = db.get_pif_record(pif)
1062 interface = interface_name(pif)
1063 ipdev = ipdev_name(pif)
1064 datapath = datapath_name(pif)
1065 physdev_names = get_physdev_names(pif)
1067 argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
1068 argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
1069 for slave in physdev_names]
1070 argv += ['--add=bonding.%s.fake-iface=true' % interface]
1072 if pifrec['MAC'] != "":
1073 argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
1077 "mode": "balance-slb",
1083 # override defaults with values from other-config whose keys
1084 # being with "bond-"
1085 oc = pifrec['other_config']
1086 overrides = filter(lambda (key,val):
1087 key.startswith("bond-"), oc.items())
1088 overrides = map(lambda (key,val): (key[5:], val), overrides)
1089 bond_options.update(overrides)
1090 for (name,val) in bond_options.items():
1091 argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
1095 pifrec = db.get_pif_record(pif)
1097 bridge = bridge_name(pif)
1098 interface = interface_name(pif)
1099 ipdev = ipdev_name(pif)
1100 datapath = datapath_name(pif)
1101 physdev_pifs = get_physdev_pifs(pif)
1102 physdev_names = get_physdev_names(pif)
1104 if pifrec['VLAN'] != '-1':
1105 vlan_slave = get_vlan_slave_of_pif(pif)
1106 if vlan_slave and is_bond_pif(vlan_slave):
1107 bond_master = vlan_slave
1108 elif is_bond_pif(pif):
1113 bond_slaves = get_bond_slaves_of_pif(bond_master)
1116 bond_masters = get_bond_masters_of_pif(pif)
1118 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1119 # files up-to-date, even though we don't use them or need them.
1120 f = configure_pif(pif)
1121 mode = pifrec['ip_configuration_mode']
1123 log("Configuring %s using %s configuration" % (bridge, mode))
1124 br = open_network_ifcfg(pif)
1125 configure_network(pif, br)
1129 log("Configuring %s using %s configuration" % (interface, mode))
1130 configure_network(pif, f)
1132 for master in bond_masters:
1133 master_bridge = bridge_name(master)
1134 removed = unconfigure_pif(master)
1135 f.attach_child(removed)
1137 removed = open_network_ifcfg(master)
1138 log("Unlinking stale file %s" % removed.path())
1140 f.attach_child(removed)
1142 # /etc/xensource/scripts/vif needs to know where to add VIFs.
1144 if not os.path.exists(vswitch_state_dir):
1145 os.mkdir(vswitch_state_dir)
1146 br = ConfigurationFile("br-%s" % bridge, vswitch_state_dir)
1147 br.write("VLAN_SLAVE=%s\n" % datapath)
1148 br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
1152 # Update all configuration files (both ours and Centos's).
1156 # Check the MAC address of each network device and remap if
1157 # necessary to make names match our expectations.
1158 for physdev_pif in physdev_pifs:
1159 remap_pif(physdev_pif)
1161 # "ifconfig down" the network device and delete its IP address, etc.
1163 for physdev_name in physdev_names:
1164 down_netdev(physdev_name)
1166 # If we are bringing up a bond, remove IP addresses from the
1167 # slaves (because we are implicitly being asked to take them down).
1169 # Conversely, if we are bringing up an interface that has bond
1170 # masters, remove IP addresses from the bond master (because we
1171 # are implicitly being asked to take it down).
1172 for bond_pif in bond_slaves + bond_masters:
1173 run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0'])
1175 # Remove all keys related to pif and any bond masters linked to PIF.
1176 del_ports = [ipdev] + physdev_names + bond_masters
1177 if vlan_slave and bond_master:
1178 del_ports += [interface_name(bond_master)]
1180 # What ports do we need to add to the datapath?
1182 # We definitely need the ipdev, and ordinarily we want the
1183 # physical devices too, but for bonds we need the bond as bridge
1185 add_ports = [ipdev, datapath]
1187 add_ports += physdev_names
1189 add_ports += [interface_name(bond_master)]
1191 # What ports do we need to delete?
1193 # - All the ports that we add, to avoid duplication and to drop
1194 # them from another datapath in case they're misassigned.
1196 # - The physical devices, since they will either be in add_ports
1197 # or added to the bonding device (see below).
1199 # - The bond masters for pif. (Ordinarily pif shouldn't have any
1200 # bond masters. If it does then interface-reconfigure is
1201 # implicitly being asked to take them down.)
1202 del_ports = add_ports + physdev_names + bond_masters
1204 # What networks does this datapath carry?
1206 # - The network corresponding to the datapath's PIF.
1208 # - The networks corresponding to any VLANs attached to the
1211 for nwpif in db.get_pifs_by_device({'device': pifrec['device']}):
1212 net = db.get_pif_record(nwpif)['network']
1213 network_uuids += [db.get_network_record(net)['uuid']]
1215 # Bring up bond slaves early, because ovs-vswitchd initially
1216 # enables or disables bond slaves based on whether carrier is
1217 # detected when they are added, and a network device that is down
1218 # always reports "no carrier".
1219 bond_slave_physdev_pifs = []
1220 for slave in bond_slaves:
1221 bond_slave_physdev_pifs += get_physdev_pifs(slave)
1222 for slave_physdev_pif in set(bond_slave_physdev_pifs):
1223 configure_physdev(slave_physdev_pif)
1225 # Now modify the ovs-vswitchd config file.
1227 for port in set(del_ports):
1228 argv += interface_deconfigure_commands(port)
1229 for port in set(add_ports):
1230 argv += ['--add=bridge.%s.port=%s' % (datapath, port)]
1232 argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
1233 argv += ['--add=iface.%s.internal=true' % (ipdev)]
1235 # xapi creates a bridge by the name of the ipdev and requires
1236 # that the IP address will be on it. We need to delete this
1237 # bridge because we need that device to be a member of our
1239 argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev]
1241 # xapi insists that its attempts to create the bridge succeed,
1242 # so force that to happen.
1243 argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
1246 os.unlink("%s/br-%s" % (vswitch_state_dir, bridge))
1249 argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath]
1250 argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid)
1251 for uuid in set(network_uuids)]
1253 argv += configure_bond(bond_master)
1256 # Bring up VLAN slave, plus physical devices other than bond
1257 # slaves (which we brought up earlier).
1259 up_netdev(ipdev_name(vlan_slave))
1260 for physdev_pif in set(physdev_pifs) - set(bond_slave_physdev_pifs):
1261 configure_physdev(physdev_pif)
1263 # Configure network device for local port.
1264 configure_local_port(pif)
1266 # Update /etc/issue (which contains the IP address of the management interface)
1267 os.system("/sbin/update-issue")
1270 # There seems to be a race somewhere: without this sleep, using
1271 # XenCenter to create a bond that becomes the management interface
1272 # fails with "The underlying connection was closed: A connection that
1273 # was expected to be kept alive was closed by the server." on every
1274 # second or third try, even though /var/log/messages doesn't show
1277 # The race is probably present even without vswitch, but bringing up a
1278 # bond without vswitch involves a built-in pause of 10 seconds or more
1279 # to wait for the bond to transition from learning to forwarding state.
1282 def action_down(pif):
1283 rec = db.get_pif_record(pif)
1284 interface = interface_name(pif)
1285 bridge = bridge_name(pif)
1286 ipdev = ipdev_name(pif)
1288 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1289 # files up-to-date, even though we don't use them or need them.
1290 f = unconfigure_pif(pif)
1292 br = open_network_ifcfg(pif)
1293 log("Unlinking stale file %s" % br.path())
1300 log("action_down failed to apply changes: %s" % e.msg)
1305 if rec['VLAN'] != '-1':
1306 # Get rid of the VLAN device itself.
1308 argv += interface_deconfigure_commands(ipdev)
1310 # If the VLAN's slave is attached, stop here.
1311 slave = get_vlan_slave_of_pif(pif)
1312 if db.get_pif_record(slave)['currently_attached']:
1313 log("VLAN slave is currently attached")
1317 # If the VLAN's slave has other VLANs that are attached, stop here.
1318 masters = get_vlan_masters_of_pif(slave)
1320 if m != pif and db.get_pif_record(m)['currently_attached']:
1321 log("VLAN slave has other master %s" % interface_naem(m))
1325 # Otherwise, take down the VLAN's slave too.
1326 log("No more masters, bring down vlan slave %s" % interface_name(slave))
1329 # Stop here if this PIF has attached VLAN masters.
1330 vlan_masters = get_vlan_masters_of_pif(pif)
1331 log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters]))
1332 for m in vlan_masters:
1333 if db.get_pif_record(m)['currently_attached']:
1334 log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m)))
1337 # pif is now either a bond or a physical device which needs to be
1338 # brought down. pif might have changed so re-check all its attributes.
1339 rec = db.get_pif_record(pif)
1340 interface = interface_name(pif)
1341 bridge = bridge_name(pif)
1342 ipdev = ipdev_name(pif)
1345 bond_slaves = get_bond_slaves_of_pif(pif)
1346 log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves]))
1347 for slave in bond_slaves:
1348 slave_interface = interface_name(slave)
1349 log("bring down bond slave %s" % slave_interface)
1350 argv += interface_deconfigure_commands(slave_interface)
1351 down_netdev(slave_interface)
1353 argv += interface_deconfigure_commands(ipdev)
1356 argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)]
1357 argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface]
1360 def action_rewrite(pif):
1361 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1362 # files up-to-date, even though we don't use them or need them.
1363 pifrec = db.get_pif_record(pif)
1364 f = configure_pif(pif)
1365 interface = interface_name(pif)
1366 bridge = bridge_name(pif)
1367 mode = pifrec['ip_configuration_mode']
1369 log("Configuring %s using %s configuration" % (bridge, mode))
1370 br = open_network_ifcfg(pif)
1371 configure_network(pif, br)
1375 log("Configuring %s using %s configuration" % (interface, mode))
1376 configure_network(pif, f)
1382 log("failed to apply changes: %s" % e.msg)
1386 # We have no code of our own to run here.
1389 def main(argv=None):
1390 global output_directory, management_pif
1396 force_interface = None
1397 force_management = False
1405 longops = [ "output-directory=",
1406 "pif=", "pif-uuid=",
1412 "device=", "mode=", "ip=", "netmask=", "gateway=",
1414 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1415 except getopt.GetoptError, msg:
1418 force_rewrite_config = {}
1421 if o == "--output-directory":
1422 output_directory = a
1425 elif o == "--pif-uuid":
1427 elif o == "--session":
1429 elif o == "--force-interface" or o == "--force":
1431 elif o == "--management":
1432 force_management = True
1433 elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1434 force_rewrite_config[o[2:]] = a
1435 elif o == "-h" or o == "--help":
1436 print __doc__ % {'command-name': os.path.basename(argv[0])}
1439 if not debug_mode():
1440 syslog.openlog(os.path.basename(argv[0]))
1441 log("Called as " + str.join(" ", argv))
1443 raise Usage("Required option <action> not present")
1445 raise Usage("Too many arguments")
1448 # backwards compatibility
1449 if action == "rewrite-configuration": action = "rewrite"
1451 if output_directory and ( session or pif ):
1452 raise Usage("--session/--pif cannot be used with --output-directory")
1453 if ( session or pif ) and pif_uuid:
1454 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1455 if ( session and not pif ) or ( not session and pif ):
1456 raise Usage("--session and --pif must be used together.")
1457 if force_interface and ( session or pif or pif_uuid ):
1458 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1459 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1460 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1464 log("Force interface %s %s" % (force_interface, action))
1466 if action == "rewrite":
1467 action_force_rewrite(force_interface, force_rewrite_config)
1469 db = DatabaseCache(cache_file=dbcache_file)
1470 pif = db.get_pif_by_bridge(force_interface)
1471 management_pif = db.get_management_pif()
1475 elif action == "down":
1478 raise Usage("Unknown action %s" % action)
1480 db = DatabaseCache(session_ref=session)
1483 pif = db.get_pif_by_uuid(pif_uuid)
1486 raise Usage("No PIF given")
1488 if force_management:
1489 # pif is going to be the management pif
1490 management_pif = pif
1492 # pif is not going to be the management pif.
1493 # Search DB cache for pif on same host with management=true
1494 pifrec = db.get_pif_record(pif)
1495 management_pif = db.get_management_pif()
1497 log_pif_action(action, pif)
1499 if not check_allowed(pif):
1504 elif action == "down":
1506 elif action == "rewrite":
1509 raise Usage("Unknown action %s" % action)
1512 pifrec = db.get_pif_record(pif)
1513 db.save(dbcache_file)
1516 print >>sys.stderr, err.msg
1517 print >>sys.stderr, "For help use --help."
1525 # The following code allows interface-reconfigure to keep Centos
1526 # network configuration files up-to-date, even though the vswitch
1527 # never uses them. In turn, that means that "rpm -e vswitch" does not
1528 # have to update any configuration files.
1530 def configure_ethtool(oc, f):
1531 # Options for "ethtool -s"
1533 setting_opts = ["autoneg", "speed", "duplex"]
1534 # Options for "ethtool -K"
1536 offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
1538 for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
1539 val = oc["ethtool-" + opt]
1541 if opt in ["speed"]:
1542 if val in ["10", "100", "1000"]:
1543 val = "speed " + val
1545 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
1547 elif opt in ["duplex"]:
1548 if val in ["half", "full"]:
1549 val = "duplex " + val
1551 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
1553 elif opt in ["autoneg"] + offload_opts:
1554 if val in ["true", "on"]:
1556 elif val in ["false", "off"]:
1559 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
1562 if opt in setting_opts:
1563 if val and settings:
1564 settings = settings + " " + val
1567 elif opt in offload_opts:
1569 offload = offload + " " + val
1574 f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
1576 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
1578 def configure_mtu(oc, f):
1579 if not oc.has_key('mtu'):
1583 mtu = int(oc['mtu'])
1584 f.write("MTU=%d\n" % mtu)
1585 except ValueError, x:
1586 log("Invalid value for mtu = %s" % mtu)
1588 def configure_static_routes(interface, oc, f):
1589 """Open a route-<interface> file for static routes.
1591 Opens the static routes configuration file for interface and writes one
1592 line for each route specified in the network's other config "static-routes" value.
1594 interface ( RO): xenbr1
1595 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1597 Then route-xenbr1 should be
1598 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1599 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1601 fname = "route-%s" % interface
1602 if oc.has_key('static-routes'):
1603 # The key is present - extract comma seperates entries
1604 lines = oc['static-routes'].split(',')
1606 # The key is not present, i.e. there are no static routes
1609 child = ConfigurationFile(fname)
1610 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1611 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
1615 network, masklen, gateway = l.split('/')
1616 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1618 f.attach_child(child)
1621 except ValueError, e:
1622 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1624 def __open_ifcfg(interface):
1625 """Open a network interface configuration file.
1627 Opens the configuration file for interface, writes a header and
1628 common options and returns the file object.
1630 fname = "ifcfg-%s" % interface
1631 f = ConfigurationFile(fname)
1633 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1634 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1635 f.write("XEMANAGED=yes\n")
1636 f.write("DEVICE=%s\n" % interface)
1637 f.write("ONBOOT=no\n")
1641 def open_network_ifcfg(pif):
1642 bridge = bridge_name(pif)
1643 interface = interface_name(pif)
1645 return __open_ifcfg(bridge)
1647 return __open_ifcfg(interface)
1650 def open_pif_ifcfg(pif):
1651 pifrec = db.get_pif_record(pif)
1653 log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
1655 f = __open_ifcfg(interface_name(pif))
1657 if pifrec.has_key('other_config'):
1658 configure_ethtool(pifrec['other_config'], f)
1659 configure_mtu(pifrec['other_config'], f)
1663 def configure_network(pif, f):
1664 """Write the configuration file for a network.
1666 Writes configuration derived from the network object into the relevant
1667 ifcfg file. The configuration file is passed in, but if the network is
1668 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1670 This routine may also write ifcfg files of the networks corresponding to other PIFs
1671 in order to maintain consistency.
1674 pif: Opaque_ref of pif
1675 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1678 pifrec = db.get_pif_record(pif)
1679 nw = pifrec['network']
1680 nwrec = db.get_network_record(nw)
1682 bridge = bridge_name(pif)
1683 interface = interface_name(pif)
1689 if nwrec.has_key('other_config'):
1690 configure_ethtool(nwrec['other_config'], f)
1691 configure_mtu(nwrec['other_config'], f)
1692 configure_static_routes(device, nwrec['other_config'], f)
1695 if pifrec.has_key('other_config'):
1696 oc = pifrec['other_config']
1698 if device == bridge:
1699 f.write("TYPE=Bridge\n")
1700 f.write("DELAY=0\n")
1701 f.write("STP=off\n")
1702 f.write("PIFDEV=%s\n" % interface_name(pif))
1704 if pifrec['ip_configuration_mode'] == "DHCP":
1705 f.write("BOOTPROTO=dhcp\n")
1706 f.write("PERSISTENT_DHCLIENT=yes\n")
1707 elif pifrec['ip_configuration_mode'] == "Static":
1708 f.write("BOOTPROTO=none\n")
1709 f.write("NETMASK=%(netmask)s\n" % pifrec)
1710 f.write("IPADDR=%(IP)s\n" % pifrec)
1711 f.write("GATEWAY=%(gateway)s\n" % pifrec)
1712 elif pifrec['ip_configuration_mode'] == "None":
1713 f.write("BOOTPROTO=none\n")
1715 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1717 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1718 ServerList = pifrec['DNS'].split(",")
1719 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1720 if oc and oc.has_key('domain'):
1721 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1723 # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1724 # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1725 # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1727 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1729 # Note: we prune out the bond master pif (if it exists).
1730 # This is because when we are called to bring up an interface with a bond master, it is implicit that
1731 # we should bring down that master.
1732 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1733 not __pif in get_bond_masters_of_pif(pif) ]
1734 other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1737 defaultroute_pif = None
1739 # loop through all the pifs on this host looking for one with
1740 # other-config:peerdns = true, and one with
1741 # other-config:default-route=true
1742 for __pif in pifs_on_host:
1743 __pifrec = db.get_pif_record(__pif)
1744 __oc = __pifrec['other_config']
1745 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1746 if peerdns_pif == None:
1749 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1750 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1751 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1752 if defaultroute_pif == None:
1753 defaultroute_pif = __pif
1755 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1756 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1758 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1759 if peerdns_pif == None:
1760 peerdns_pif = management_pif
1761 if defaultroute_pif == None:
1762 defaultroute_pif = management_pif
1764 # Update all the other network's ifcfg files and ensure consistency
1765 for __pif in other_pifs_on_host:
1766 __f = open_network_ifcfg(__pif)
1767 peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1768 lines = __f.readlines()
1770 if not peerdns_line_wanted in lines:
1771 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1773 if not line.lstrip().startswith('PEERDNS'):
1775 log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1776 __f.write(peerdns_line_wanted)
1781 # There is no need to change this ifcfg file. So don't attach_child.
1784 # ... and for this pif too
1785 f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1788 fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1789 for line in fnetwork.readlines():
1790 if line.lstrip().startswith('GATEWAY') :
1792 fnetwork.write(line)
1793 if defaultroute_pif:
1794 gatewaydev = bridge_name(defaultroute_pif)
1796 gatewaydev = interface_name(defaultroute_pif)
1797 fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1799 f.attach_child(fnetwork)
1804 def configure_physical_interface(pif):
1805 """Write the configuration for a physical interface.
1807 Writes the configuration file for the physical interface described by
1810 Returns the open file handle for the interface configuration file.
1813 pifrec = db.get_pif_record(pif)
1815 f = open_pif_ifcfg(pif)
1817 f.write("TYPE=Ethernet\n")
1818 f.write("HWADDR=%(MAC)s\n" % pifrec)
1822 def configure_bond_interface(pif):
1823 """Write the configuration for a bond interface.
1825 Writes the configuration file for the bond interface described by
1826 the pif object. Handles writing the configuration for the slave
1829 Returns the open file handle for the bond interface configuration
1833 pifrec = db.get_pif_record(pif)
1834 oc = pifrec['other_config']
1835 f = open_pif_ifcfg(pif)
1837 if pifrec['MAC'] != "":
1838 f.write("MACADDR=%s\n" % pifrec['MAC'])
1840 for slave in get_bond_slaves_of_pif(pif):
1841 s = configure_physical_interface(slave)
1842 s.write("MASTER=%(device)s\n" % pifrec)
1843 s.write("SLAVE=yes\n")
1847 # The bond option defaults
1849 "mode": "balance-slb",
1856 # override defaults with values from other-config whose keys being with "bond-"
1857 overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
1858 overrides = map(lambda (key,val): (key[5:], val), overrides)
1859 bond_options.update(overrides)
1861 # write the bond options to ifcfg-bondX
1862 f.write('BONDING_OPTS="')
1863 for (name,val) in bond_options.items():
1864 f.write("%s=%s " % (name,val))
1868 def configure_vlan_interface(pif):
1869 """Write the configuration for a VLAN interface.
1871 Writes the configuration file for the VLAN interface described by
1872 the pif object. Handles writing the configuration for the master
1873 interface if necessary.
1875 Returns the open file handle for the VLAN interface configuration
1879 slave = configure_pif(get_vlan_slave_of_pif(pif))
1882 f = open_pif_ifcfg(pif)
1883 f.write("VLAN=yes\n")
1884 f.attach_child(slave)
1888 def configure_pif(pif):
1889 """Write the configuration for a PIF object.
1891 Writes the configuration file the PIF and all dependent
1892 interfaces (bond slaves and VLAN masters etc).
1894 Returns the open file handle for the interface configuration file.
1897 pifrec = db.get_pif_record(pif)
1899 if pifrec['VLAN'] != '-1':
1900 f = configure_vlan_interface(pif)
1901 elif len(pifrec['bond_master_of']) != 0:
1902 f = configure_bond_interface(pif)
1904 f = configure_physical_interface(pif)
1906 bridge = bridge_name(pif)
1908 f.write("BRIDGE=%s\n" % bridge)
1912 def unconfigure_pif(pif):
1913 """Clear up the files created by configure_pif"""
1914 f = open_pif_ifcfg(pif)
1915 log("Unlinking stale file %s" % f.path())
1919 if __name__ == "__main__":
1925 err = traceback.format_exception(*ex)
1929 if not debug_mode():