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 dbcache_file = "/etc/ovs-vswitch.dbcache"
75 vswitch_config_dir = "/etc/openvswitch"
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):
270 for device in os.listdir("/sys/class/net"):
271 dev_mac = get_netdev_mac(device)
272 if dev_mac and mac.lower() == dev_mac.lower():
273 if get_netdev_tx_queue_len(device):
276 # Probably a datapath internal port.
281 # Helper functions for encoding/decoding database attributes to/from XML.
283 def str_to_xml(xml, parent, tag, val):
284 e = xml.createElement(tag)
285 parent.appendChild(e)
286 v = xml.createTextNode(val)
289 def getText(nodelist):
291 for node in nodelist:
292 if node.nodeType == node.TEXT_NODE:
295 return getText(n.childNodes).strip()
298 def bool_to_xml(xml, parent, tag, val):
300 str_to_xml(xml, parent, tag, "True")
302 str_to_xml(xml, parent, tag, "False")
303 def bool_from_xml(n):
310 raise Error("Unknown boolean value %s" % s);
312 def strlist_to_xml(xml, parent, ltag, itag, val):
313 e = xml.createElement(ltag)
314 parent.appendChild(e)
316 c = xml.createElement(itag)
318 cv = xml.createTextNode(v)
320 def strlist_from_xml(n, ltag, itag):
322 for n in n.childNodes:
323 if n.nodeName == itag:
324 ret.append(str_from_xml(n))
327 def otherconfig_to_xml(xml, parent, val, attrs):
328 otherconfig = xml.createElement("other_config")
329 parent.appendChild(otherconfig)
330 for n,v in val.items():
332 raise Error("Unknown other-config attribute: %s" % n)
333 str_to_xml(xml, otherconfig, n, v)
334 def otherconfig_from_xml(n, attrs):
336 for n in n.childNodes:
337 if n.nodeName in attrs:
338 ret[n.nodeName] = str_from_xml(n)
342 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
344 # Each object is defined by a dictionary mapping an attribute name in
345 # the xapi database to a tuple containing two items:
346 # - a function which takes this attribute and encodes it as XML.
347 # - a function which takes XML and decocdes it into a value.
349 # other-config attributes are specified as a simple array of strings
352 VLAN_XML_TAG = "vlan"
353 BOND_XML_TAG = "bond"
354 NETWORK_XML_TAG = "network"
356 ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
358 PIF_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
359 'management': (bool_to_xml,bool_from_xml),
360 'network': (str_to_xml,str_from_xml),
361 'device': (str_to_xml,str_from_xml),
362 'bond_master_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
363 lambda n: strlist_from_xml(n, 'bond_master_of', 'slave')),
364 'bond_slave_of': (str_to_xml,str_from_xml),
365 'VLAN': (str_to_xml,str_from_xml),
366 'VLAN_master_of': (str_to_xml,str_from_xml),
367 'VLAN_slave_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
368 lambda n: strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
369 'ip_configuration_mode': (str_to_xml,str_from_xml),
370 'IP': (str_to_xml,str_from_xml),
371 'netmask': (str_to_xml,str_from_xml),
372 'gateway': (str_to_xml,str_from_xml),
373 'DNS': (str_to_xml,str_from_xml),
374 'MAC': (str_to_xml,str_from_xml),
375 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, PIF_OTHERCONFIG_ATTRS),
376 lambda n: otherconfig_from_xml(n, PIF_OTHERCONFIG_ATTRS)),
379 PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
380 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
381 ETHTOOL_OTHERCONFIG_ATTRS
383 VLAN_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
384 'tagged_PIF': (str_to_xml,str_from_xml),
385 'untagged_PIF': (str_to_xml,str_from_xml),
388 BOND_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
389 'master': (str_to_xml,str_from_xml),
390 'slaves': (lambda x, p, t, v: strlist_to_xml(x, p, 'slaves', 'slave', v),
391 lambda n: strlist_from_xml(n, 'slaves', 'slave')),
394 NETWORK_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
395 'bridge': (str_to_xml,str_from_xml),
396 'PIFs': (lambda x, p, t, v: strlist_to_xml(x, p, 'PIFs', 'PIF', v),
397 lambda n: strlist_from_xml(n, 'PIFs', 'PIF')),
398 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, NETWORK_OTHERCONFIG_ATTRS),
399 lambda n: otherconfig_from_xml(n, NETWORK_OTHERCONFIG_ATTRS)),
402 NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + ETHTOOL_OTHERCONFIG_ATTRS
404 class DatabaseCache(object):
405 def __read_xensource_inventory(self):
406 filename = "/etc/xensource-inventory"
407 f = open(filename, "r")
408 lines = [x.strip("\n") for x in f.readlines()]
411 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
412 defs = [ (a, b.strip("'")) for (a,b) in defs ]
415 def __pif_on_host(self,pif):
416 return self.__pifs.has_key(pif)
418 def __get_pif_records_from_xapi(self, session, host):
420 for (p,rec) in session.xenapi.PIF.get_all_records().items():
421 if rec['host'] != host:
425 self.__pifs[p][f] = rec[f]
426 self.__pifs[p]['other_config'] = {}
427 for f in PIF_OTHERCONFIG_ATTRS:
428 if not rec['other_config'].has_key(f): continue
429 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
431 def __get_vlan_records_from_xapi(self, session):
433 for v in session.xenapi.VLAN.get_all():
434 rec = session.xenapi.VLAN.get_record(v)
435 if not self.__pif_on_host(rec['untagged_PIF']):
439 self.__vlans[v][f] = rec[f]
441 def __get_bond_records_from_xapi(self, session):
443 for b in session.xenapi.Bond.get_all():
444 rec = session.xenapi.Bond.get_record(b)
445 if not self.__pif_on_host(rec['master']):
449 self.__bonds[b][f] = rec[f]
451 def __get_network_records_from_xapi(self, session):
453 for n in session.xenapi.network.get_all():
454 rec = session.xenapi.network.get_record(n)
455 self.__networks[n] = {}
456 for f in NETWORK_ATTRS:
457 self.__networks[n][f] = rec[f]
458 self.__networks[n]['other_config'] = {}
459 for f in NETWORK_OTHERCONFIG_ATTRS:
460 if not rec['other_config'].has_key(f): continue
461 self.__networks[n]['other_config'][f] = rec['other_config'][f]
463 def __to_xml(self, xml, parent, key, ref, rec, attrs):
464 """Encode a database object as XML"""
465 e = xml.createElement(key)
466 parent.appendChild(e)
468 e.setAttribute('ref', ref)
470 for n,v in rec.items():
475 raise Error("Unknown attribute %s" % n)
476 def __from_xml(self, e, attrs):
477 """Decode a database object from XML"""
478 ref = e.attributes['ref'].value
480 for n in e.childNodes:
481 if n.nodeName in attrs:
482 _,h = attrs[n.nodeName]
483 rec[n.nodeName] = h(n)
486 def __init__(self, session_ref=None, cache_file=None):
487 if session_ref and cache_file:
488 raise Error("can't specify session reference and cache file")
489 if cache_file == None:
490 session = XenAPI.xapi_local()
493 log("No session ref given on command line, logging in.")
494 session.xenapi.login_with_password("root", "")
496 session._session = session_ref
500 inventory = self.__read_xensource_inventory()
501 assert(inventory.has_key('INSTALLATION_UUID'))
502 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
504 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
506 self.__get_pif_records_from_xapi(session, host)
508 self.__get_vlan_records_from_xapi(session)
509 self.__get_bond_records_from_xapi(session)
510 self.__get_network_records_from_xapi(session)
513 session.xenapi.session.logout()
515 log("Loading xapi database cache from %s" % cache_file)
517 xml = parseXML(cache_file)
524 assert(len(xml.childNodes) == 1)
525 toplevel = xml.childNodes[0]
527 assert(toplevel.nodeName == "xenserver-network-configuration")
529 for n in toplevel.childNodes:
530 if n.nodeName == "#text":
532 elif n.nodeName == PIF_XML_TAG:
533 (ref,rec) = self.__from_xml(n, PIF_ATTRS)
534 self.__pifs[ref] = rec
535 elif n.nodeName == BOND_XML_TAG:
536 (ref,rec) = self.__from_xml(n, BOND_ATTRS)
537 self.__bonds[ref] = rec
538 elif n.nodeName == VLAN_XML_TAG:
539 (ref,rec) = self.__from_xml(n, VLAN_ATTRS)
540 self.__vlans[ref] = rec
541 elif n.nodeName == NETWORK_XML_TAG:
542 (ref,rec) = self.__from_xml(n, NETWORK_ATTRS)
543 self.__networks[ref] = rec
545 raise Error("Unknown XML element %s" % n.nodeName)
547 def save(self, cache_file):
549 xml = getDOMImplementation().createDocument(
550 None, "xenserver-network-configuration", None)
551 for (ref,rec) in self.__pifs.items():
552 self.__to_xml(xml, xml.documentElement, PIF_XML_TAG, ref, rec, PIF_ATTRS)
553 for (ref,rec) in self.__bonds.items():
554 self.__to_xml(xml, xml.documentElement, BOND_XML_TAG, ref, rec, BOND_ATTRS)
555 for (ref,rec) in self.__vlans.items():
556 self.__to_xml(xml, xml.documentElement, VLAN_XML_TAG, ref, rec, VLAN_ATTRS)
557 for (ref,rec) in self.__networks.items():
558 self.__to_xml(xml, xml.documentElement, NETWORK_XML_TAG, ref, rec,
561 f = open(cache_file, 'w')
562 f.write(xml.toprettyxml())
565 def get_pif_by_uuid(self, uuid):
566 pifs = map(lambda (ref,rec): ref,
567 filter(lambda (ref,rec): uuid == rec['uuid'],
568 self.__pifs.items()))
570 raise Error("Unknown PIF \"%s\"" % uuid)
572 raise Error("Non-unique PIF \"%s\"" % uuid)
576 def get_pifs_by_device(self, device):
577 return map(lambda (ref,rec): ref,
578 filter(lambda (ref,rec): rec['device'] == device,
579 self.__pifs.items()))
581 def get_pif_by_bridge(self, bridge):
582 networks = map(lambda (ref,rec): ref,
583 filter(lambda (ref,rec): rec['bridge'] == bridge,
584 self.__networks.items()))
585 if len(networks) == 0:
586 raise Error("No matching network \"%s\"")
589 for network in networks:
590 nwrec = self.get_network_record(network)
591 for pif in nwrec['PIFs']:
592 pifrec = self.get_pif_record(pif)
594 raise Error("Multiple PIFs on host for network %s" % (bridge))
597 raise Error("No PIF on host for network %s" % (bridge))
600 def get_pif_record(self, pif):
601 if self.__pifs.has_key(pif):
602 return self.__pifs[pif]
603 raise Error("Unknown PIF \"%s\" (get_pif_record)" % pif)
604 def get_all_pifs(self):
606 def pif_exists(self, pif):
607 return self.__pifs.has_key(pif)
609 def get_management_pif(self):
610 """ Returns the management pif on host
612 all = self.get_all_pifs()
614 pifrec = self.get_pif_record(pif)
615 if pifrec['management']: return pif
618 def get_network_record(self, network):
619 if self.__networks.has_key(network):
620 return self.__networks[network]
621 raise Error("Unknown network \"%s\"" % network)
622 def get_all_networks(self):
623 return self.__networks
625 def get_bond_record(self, bond):
626 if self.__bonds.has_key(bond):
627 return self.__bonds[bond]
631 def get_vlan_record(self, vlan):
632 if self.__vlans.has_key(vlan):
633 return self.__vlans[vlan]
637 def bridge_name(pif):
638 """Return the bridge name associated with pif, or None if network is bridgeless"""
639 pifrec = db.get_pif_record(pif)
640 nwrec = db.get_network_record(pifrec['network'])
643 # TODO: sanity check that nwrec['bridgeless'] != 'true'
644 return nwrec['bridge']
646 # TODO: sanity check that nwrec['bridgeless'] == 'true'
649 def interface_name(pif):
650 """Construct an interface name from the given PIF record."""
652 pifrec = db.get_pif_record(pif)
654 if pifrec['VLAN'] == '-1':
655 return pifrec['device']
657 return "%(device)s.%(VLAN)s" % pifrec
659 def datapath_name(pif):
660 """Return the OpenFlow datapath name associated with pif.
661 For a non-VLAN PIF, the datapath name is the bridge name.
662 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
663 (xapi will create a datapath named with the bridge name even though we won't
668 pifrec = db.get_pif_record(pif)
670 if pifrec['VLAN'] == '-1':
671 return bridge_name(pif)
673 return bridge_name(get_vlan_slave_of_pif(pif))
676 """Return the the name of the network device that carries the
677 IP configuration (if any) associated with pif.
678 The ipdev name is the same as the bridge name.
681 pifrec = db.get_pif_record(pif)
682 return bridge_name(pif)
684 def get_physdev_pifs(pif):
685 """Return the PIFs for the physical network device(s) associated with pif.
686 For a VLAN PIF, this is the VLAN slave's physical device PIF.
687 For a bond master PIF, these are the bond slave PIFs.
688 For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
690 pifrec = db.get_pif_record(pif)
692 if pifrec['VLAN'] != '-1':
693 return [get_vlan_slave_of_pif(pif)]
694 elif len(pifrec['bond_master_of']) != 0:
695 return get_bond_slaves_of_pif(pif)
699 def get_physdev_names(pif):
700 """Return the name(s) of the physical network device(s) associated with pif.
701 For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
702 For a bond master PIF, the physical devices are the bond slaves.
703 For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
706 return [db.get_pif_record(phys)['device'] for phys in get_physdev_pifs(pif)]
708 def log_pif_action(action, pif):
709 pifrec = db.get_pif_record(pif)
711 rec['uuid'] = pifrec['uuid']
712 rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
713 rec['action'] = action
714 rec['interface-name'] = interface_name(pif)
715 if action == "rewrite":
716 rec['message'] = "Rewrite PIF %(uuid)s configuration" % rec
718 rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
719 log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % rec)
721 def get_bond_masters_of_pif(pif):
722 """Returns a list of PIFs which are bond masters of this PIF"""
724 pifrec = db.get_pif_record(pif)
726 bso = pifrec['bond_slave_of']
728 # bond-slave-of is currently a single reference but in principle a
729 # PIF could be a member of several bonds which are not
730 # concurrently attached. Be robust to this possibility.
731 if not bso or bso == "OpaqueRef:NULL":
733 elif not type(bso) == list:
736 bondrecs = [db.get_bond_record(bond) for bond in bso]
737 bondrecs = [rec for rec in bondrecs if rec]
739 return [bond['master'] for bond in bondrecs]
741 def get_bond_slaves_of_pif(pif):
742 """Returns a list of PIFs which make up the given bonded pif."""
744 pifrec = db.get_pif_record(pif)
746 bmo = pifrec['bond_master_of']
748 raise Error("Bond-master-of contains too many elements")
753 bondrec = db.get_bond_record(bmo[0])
755 raise Error("No bond record for bond master PIF")
757 return bondrec['slaves']
759 def get_vlan_slave_of_pif(pif):
760 """Find the PIF which is the VLAN slave of pif.
762 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
764 pifrec = db.get_pif_record(pif)
766 vlan = pifrec['VLAN_master_of']
767 if not vlan or vlan == "OpaqueRef:NULL":
768 raise Error("PIF is not a VLAN master")
770 vlanrec = db.get_vlan_record(vlan)
772 raise Error("No VLAN record found for PIF")
774 return vlanrec['tagged_PIF']
776 def get_vlan_masters_of_pif(pif):
777 """Returns a list of PIFs which are VLANs on top of the given pif."""
779 pifrec = db.get_pif_record(pif)
780 vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
781 return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])]
783 def interface_deconfigure_commands(interface):
784 # The use of [!0-9] keeps an interface of 'eth0' from matching
785 # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
787 return ['--del-match=bridge.*.port=%s' % interface,
788 '--del-match=bonding.%s.[!0-9]*' % interface,
789 '--del-match=bonding.*.slave=%s' % interface,
790 '--del-match=vlan.%s.[!0-9]*' % interface,
791 '--del-match=port.%s.[!0-9]*' % interface,
792 '--del-match=iface.%s.[!0-9]*' % interface]
794 def run_command(command):
795 log("Running command: " + ' '.join(command))
796 if os.spawnl(os.P_WAIT, command[0], *command) != 0:
797 log("Command failed: " + ' '.join(command))
801 def rename_netdev(old_name, new_name):
802 log("Changing the name of %s to %s" % (old_name, new_name))
803 run_command(['/sbin/ifconfig', old_name, 'down'])
804 if not run_command(['/sbin/ip', 'link', 'set', old_name,
806 raise Error("Could not rename %s to %s" % (old_name, new_name))
808 # Check whether 'pif' exists and has the correct MAC.
809 # If not, try to find a device with the correct MAC and rename it.
810 # 'already_renamed' is used to avoid infinite recursion.
811 def remap_pif(pif, already_renamed=[]):
812 pifrec = db.get_pif_record(pif)
813 device = pifrec['device']
816 # Is there a network device named 'device' at all?
817 device_exists = interface_exists(device)
819 # Yes. Does it have MAC 'mac'?
820 found_mac = get_netdev_mac(device)
821 if found_mac and mac.lower() == found_mac.lower():
822 # Yes, everything checks out the way we want. Nothing to do.
825 log("No network device %s" % device)
827 # What device has MAC 'mac'?
828 cur_device = get_netdev_by_mac(mac)
830 log("No network device has MAC %s" % mac)
833 # First rename 'device', if it exists, to get it out of the way
834 # for 'cur_device' to replace it.
836 rename_netdev(device, "dev%d" % random.getrandbits(24))
838 # Rename 'cur_device' to 'device'.
839 rename_netdev(cur_device, device)
841 def read_first_line_of_file(name):
844 file = open(name, 'r')
845 return file.readline().rstrip('\n')
850 def down_netdev(interface, deconfigure=True):
851 if not interface_exists(interface):
852 log("down_netdev: interface %s does not exist, ignoring" % interface)
856 pidfile_name = '/var/run/dhclient-%s.pid' % interface
858 os.kill(int(read_first_line_of_file(pidfile_name)), signal.SIGTERM)
862 # Remove dhclient pidfile.
864 os.remove(pidfile_name)
868 run_command(["/sbin/ifconfig", interface, '0.0.0.0'])
870 run_command(["/sbin/ifconfig", interface, 'down'])
872 def up_netdev(interface):
873 run_command(["/sbin/ifconfig", interface, 'up'])
875 def find_distinguished_pifs(pif):
876 """Returns the PIFs on host that own DNS and the default route.
877 The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
878 The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
880 Note: we prune out the bond master pif (if it exists).
881 This is because when we are called to bring up an interface with a bond master, it is implicit that
882 we should bring down that master."""
884 pifrec = db.get_pif_record(pif)
886 pifs = [ __pif for __pif in db.get_all_pifs() if
887 (not __pif in get_bond_masters_of_pif(pif)) ]
890 defaultroute_pif = None
892 # loop through all the pifs on this host looking for one with
893 # other-config:peerdns = true, and one with
894 # other-config:default-route=true
896 __pifrec = db.get_pif_record(__pif)
897 __oc = __pifrec['other_config']
898 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
899 if peerdns_pif == None:
902 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
903 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
904 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
905 if defaultroute_pif == None:
906 defaultroute_pif = __pif
908 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
909 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
911 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
912 if peerdns_pif == None:
913 peerdns_pif = management_pif
914 if defaultroute_pif == None:
915 defaultroute_pif = management_pif
917 return peerdns_pif, defaultroute_pif
919 def run_ethtool(device, oc):
920 # Run "ethtool -s" if there are any settings.
922 if oc.has_key('ethtool-speed'):
923 val = oc['ethtool-speed']
924 if val in ["10", "100", "1000"]:
925 settings += ['speed', val]
927 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
928 if oc.has_key('ethtool-duplex'):
929 val = oc['ethtool-duplex']
930 if val in ["10", "100", "1000"]:
931 settings += ['duplex', 'val']
933 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
934 if oc.has_key('ethtool-autoneg'):
935 val = oc['ethtool-autoneg']
936 if val in ["true", "on"]:
937 settings += ['autoneg', 'on']
938 elif val in ["false", "off"]:
939 settings += ['autoneg', 'off']
941 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
943 run_command(['/sbin/ethtool', '-s', device] + settings)
945 # Run "ethtool -K" if there are any offload settings.
947 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
948 if oc.has_key("ethtool-" + opt):
949 val = oc["ethtool-" + opt]
950 if val in ["true", "on"]:
951 offload += [opt, 'on']
952 elif val in ["false", "off"]:
953 offload += [opt, 'off']
955 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
957 run_command(['/sbin/ethtool', '-K', device] + offload)
960 if oc.has_key('mtu'):
962 int(oc['mtu']) # Check that the value is an integer
963 return ['mtu', oc['mtu']]
964 except ValueError, x:
965 log("Invalid value for mtu = %s" % mtu)
968 def configure_local_port(pif):
969 pifrec = db.get_pif_record(pif)
970 datapath = datapath_name(pif)
971 ipdev = ipdev_name(pif)
973 nw = pifrec['network']
974 nwrec = db.get_network_record(nw)
976 pif_oc = pifrec['other_config']
977 nw_oc = nwrec['other_config']
979 # IP (except DHCP) and MTU.
980 ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up']
982 if pifrec['ip_configuration_mode'] == "DHCP":
984 elif pifrec['ip_configuration_mode'] == "Static":
985 ifconfig_argv += [pifrec['IP']]
986 ifconfig_argv += ['netmask', pifrec['netmask']]
987 gateway = pifrec['gateway']
988 elif pifrec['ip_configuration_mode'] == "None":
992 raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode'])
993 ifconfig_argv += mtu_setting(nw_oc)
994 run_command(ifconfig_argv)
996 (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif)
999 if peerdns_pif == pif:
1000 f = ConfigurationFile('resolv.conf', "/etc")
1001 if pif_oc.has_key('domain'):
1002 f.write("search %s\n" % pif_oc['domain'])
1003 for dns in pifrec['DNS'].split(","):
1004 f.write("nameserver %s\n" % dns)
1010 if defaultroute_pif == pif and gateway != '':
1011 run_command(['/sbin/ip', 'route', 'replace', 'default',
1012 'via', gateway, 'dev', ipdev])
1013 if nw_oc.has_key('static-routes'):
1014 for line in nw_oc['static-routes'].split(','):
1015 network, masklen, gateway = line.split('/')
1016 run_command(['/sbin/ip', 'route', 'add',
1017 '%s/%s' % (network, masklen), 'via', gateway,
1021 run_ethtool(ipdev, nw_oc)
1024 if pifrec['ip_configuration_mode'] == "DHCP":
1026 print "Determining IP information for %s..." % ipdev,
1027 argv = ['/sbin/dhclient', '-q',
1028 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev,
1029 '-pf', '/var/run/dhclient-%s.pid' % ipdev,
1031 if run_command(argv):
1036 def configure_physdev(pif):
1037 pifrec = db.get_pif_record(pif)
1038 device = pifrec['device']
1039 oc = pifrec['other_config']
1041 run_command(['/sbin/ifconfig', device, 'up'] + mtu_setting(oc))
1042 run_ethtool(device, oc)
1044 def modify_config(commands):
1045 run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer',
1046 '-F', '/etc/ovs-vswitchd.conf']
1047 + commands + ['-c'])
1048 run_command(['/sbin/service', 'vswitch', 'reload'])
1050 def is_bond_pif(pif):
1051 pifrec = db.get_pif_record(pif)
1052 return len(pifrec['bond_master_of']) != 0
1054 def configure_bond(pif):
1055 pifrec = db.get_pif_record(pif)
1056 interface = interface_name(pif)
1057 ipdev = ipdev_name(pif)
1058 datapath = datapath_name(pif)
1059 physdev_names = get_physdev_names(pif)
1061 argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
1062 argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
1063 for slave in physdev_names]
1064 argv += ['--add=bonding.%s.fake-iface=true' % interface]
1066 if pifrec['MAC'] != "":
1067 argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
1071 "mode": "balance-slb",
1077 # override defaults with values from other-config whose keys
1078 # being with "bond-"
1079 oc = pifrec['other_config']
1080 overrides = filter(lambda (key,val):
1081 key.startswith("bond-"), oc.items())
1082 overrides = map(lambda (key,val): (key[5:], val), overrides)
1083 bond_options.update(overrides)
1084 for (name,val) in bond_options.items():
1085 argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
1089 pifrec = db.get_pif_record(pif)
1091 bridge = bridge_name(pif)
1092 interface = interface_name(pif)
1093 ipdev = ipdev_name(pif)
1094 datapath = datapath_name(pif)
1095 physdev_pifs = get_physdev_pifs(pif)
1096 physdev_names = get_physdev_names(pif)
1098 if pifrec['VLAN'] != '-1':
1099 vlan_slave = get_vlan_slave_of_pif(pif)
1100 if vlan_slave and is_bond_pif(vlan_slave):
1101 bond_master = vlan_slave
1102 elif is_bond_pif(pif):
1107 bond_slaves = get_bond_slaves_of_pif(bond_master)
1110 bond_masters = get_bond_masters_of_pif(pif)
1112 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1113 # files up-to-date, even though we don't use them or need them.
1114 f = configure_pif(pif)
1115 mode = pifrec['ip_configuration_mode']
1117 log("Configuring %s using %s configuration" % (bridge, mode))
1118 br = open_network_ifcfg(pif)
1119 configure_network(pif, br)
1123 log("Configuring %s using %s configuration" % (interface, mode))
1124 configure_network(pif, f)
1126 for master in bond_masters:
1127 master_bridge = bridge_name(master)
1128 removed = unconfigure_pif(master)
1129 f.attach_child(removed)
1131 removed = open_network_ifcfg(master)
1132 log("Unlinking stale file %s" % removed.path())
1134 f.attach_child(removed)
1136 # /etc/xensource/scripts/vif needs to know where to add VIFs.
1138 if not os.path.exists(vswitch_config_dir):
1139 os.mkdir(vswitch_config_dir)
1140 br = ConfigurationFile("br-%s" % bridge, vswitch_config_dir)
1141 br.write("VLAN_SLAVE=%s\n" % datapath)
1142 br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
1146 # Update all configuration files (both ours and Centos's).
1150 # Check the MAC address of each network device and remap if
1151 # necessary to make names match our expectations.
1152 for physdev_pif in physdev_pifs:
1153 remap_pif(physdev_pif)
1155 # "ifconfig down" the network device and delete its IP address, etc.
1157 for physdev_name in physdev_names:
1158 down_netdev(physdev_name)
1160 # If we are bringing up a bond, remove IP addresses from the
1161 # slaves (because we are implicitly being asked to take them down).
1163 # Conversely, if we are bringing up an interface that has bond
1164 # masters, remove IP addresses from the bond master (because we
1165 # are implicitly being asked to take it down).
1166 for bond_pif in bond_slaves + bond_masters:
1167 run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0'])
1169 # Remove all keys related to pif and any bond masters linked to PIF.
1170 del_ports = [ipdev] + physdev_names + bond_masters
1171 if vlan_slave and bond_master:
1172 del_ports += [interface_name(bond_master)]
1174 # What ports do we need to add to the datapath?
1176 # We definitely need the ipdev, and ordinarily we want the
1177 # physical devices too, but for bonds we need the bond as bridge
1179 add_ports = [ipdev, datapath]
1181 add_ports += physdev_names
1183 add_ports += [interface_name(bond_master)]
1185 # What ports do we need to delete?
1187 # - All the ports that we add, to avoid duplication and to drop
1188 # them from another datapath in case they're misassigned.
1190 # - The physical devices, since they will either be in add_ports
1191 # or added to the bonding device (see below).
1193 # - The bond masters for pif. (Ordinarily pif shouldn't have any
1194 # bond masters. If it does then interface-reconfigure is
1195 # implicitly being asked to take them down.)
1196 del_ports = add_ports + physdev_names + bond_masters
1198 # What networks does this datapath carry?
1200 # - The network corresponding to the datapath's PIF.
1202 # - The networks corresponding to any VLANs attached to the
1205 for nwpif in db.get_pifs_by_device({'device': pifrec['device']}):
1206 net = db.get_pif_record(nwpif)['network']
1207 network_uuids += [db.get_network_record(net)['uuid']]
1209 # Bring up bond slaves early, because ovs-vswitchd initially
1210 # enables or disables bond slaves based on whether carrier is
1211 # detected when they are added, and a network device that is down
1212 # always reports "no carrier".
1213 bond_slave_physdev_pifs = []
1214 for slave in bond_slaves:
1215 bond_slave_physdev_pifs += get_physdev_pifs(slave)
1216 for slave_physdev_pif in set(bond_slave_physdev_pifs):
1217 configure_physdev(slave_physdev_pif)
1219 # Now modify the ovs-vswitchd config file.
1221 for port in set(del_ports):
1222 argv += interface_deconfigure_commands(port)
1223 for port in set(add_ports):
1224 argv += ['--add=bridge.%s.port=%s' % (datapath, port)]
1226 argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
1227 argv += ['--add=iface.%s.internal=true' % (ipdev)]
1229 # xapi creates a bridge by the name of the ipdev and requires
1230 # that the IP address will be on it. We need to delete this
1231 # bridge because we need that device to be a member of our
1233 argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev]
1235 # xapi insists that its attempts to create the bridge succeed,
1236 # so force that to happen.
1237 argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
1240 os.unlink("%s/br-%s" % (vswitch_config_dir, bridge))
1243 argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath]
1244 argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid)
1245 for uuid in set(network_uuids)]
1247 argv += configure_bond(bond_master)
1250 # Bring up VLAN slave, plus physical devices other than bond
1251 # slaves (which we brought up earlier).
1253 up_netdev(ipdev_name(vlan_slave))
1254 for physdev_pif in set(physdev_pifs) - set(bond_slave_physdev_pifs):
1255 configure_physdev(physdev_pif)
1257 # Configure network device for local port.
1258 configure_local_port(pif)
1260 # Update /etc/issue (which contains the IP address of the management interface)
1261 os.system("/sbin/update-issue")
1264 # There seems to be a race somewhere: without this sleep, using
1265 # XenCenter to create a bond that becomes the management interface
1266 # fails with "The underlying connection was closed: A connection that
1267 # was expected to be kept alive was closed by the server." on every
1268 # second or third try, even though /var/log/messages doesn't show
1271 # The race is probably present even without vswitch, but bringing up a
1272 # bond without vswitch involves a built-in pause of 10 seconds or more
1273 # to wait for the bond to transition from learning to forwarding state.
1276 def action_down(pif):
1277 rec = db.get_pif_record(pif)
1278 interface = interface_name(pif)
1279 bridge = bridge_name(pif)
1280 ipdev = ipdev_name(pif)
1282 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1283 # files up-to-date, even though we don't use them or need them.
1284 f = unconfigure_pif(pif)
1286 br = open_network_ifcfg(pif)
1287 log("Unlinking stale file %s" % br.path())
1294 log("action_down failed to apply changes: %s" % e.msg)
1299 if rec['VLAN'] != '-1':
1300 # Get rid of the VLAN device itself.
1302 argv += interface_deconfigure_commands(ipdev)
1304 # If the VLAN's slave is attached, stop here.
1305 slave = get_vlan_slave_of_pif(pif)
1306 if db.get_pif_record(slave)['currently_attached']:
1307 log("VLAN slave is currently attached")
1311 # If the VLAN's slave has other VLANs that are attached, stop here.
1312 masters = get_vlan_masters_of_pif(slave)
1314 if m != pif and db.get_pif_record(m)['currently_attached']:
1315 log("VLAN slave has other master %s" % interface_naem(m))
1319 # Otherwise, take down the VLAN's slave too.
1320 log("No more masters, bring down vlan slave %s" % interface_name(slave))
1323 # Stop here if this PIF has attached VLAN masters.
1324 vlan_masters = get_vlan_masters_of_pif(pif)
1325 log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters]))
1326 for m in vlan_masters:
1327 if db.get_pif_record(m)['currently_attached']:
1328 log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m)))
1331 # pif is now either a bond or a physical device which needs to be
1332 # brought down. pif might have changed so re-check all its attributes.
1333 rec = db.get_pif_record(pif)
1334 interface = interface_name(pif)
1335 bridge = bridge_name(pif)
1336 ipdev = ipdev_name(pif)
1339 bond_slaves = get_bond_slaves_of_pif(pif)
1340 log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves]))
1341 for slave in bond_slaves:
1342 slave_interface = interface_name(slave)
1343 log("bring down bond slave %s" % slave_interface)
1344 argv += interface_deconfigure_commands(slave_interface)
1345 down_netdev(slave_interface)
1347 argv += interface_deconfigure_commands(ipdev)
1350 argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)]
1351 argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface]
1354 def action_rewrite(pif):
1355 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1356 # files up-to-date, even though we don't use them or need them.
1357 pifrec = db.get_pif_record(pif)
1358 f = configure_pif(pif)
1359 interface = interface_name(pif)
1360 bridge = bridge_name(pif)
1361 mode = pifrec['ip_configuration_mode']
1363 log("Configuring %s using %s configuration" % (bridge, mode))
1364 br = open_network_ifcfg(pif)
1365 configure_network(pif, br)
1369 log("Configuring %s using %s configuration" % (interface, mode))
1370 configure_network(pif, f)
1376 log("failed to apply changes: %s" % e.msg)
1380 # We have no code of our own to run here.
1383 def main(argv=None):
1384 global output_directory, management_pif
1390 force_interface = None
1391 force_management = False
1399 longops = [ "output-directory=",
1400 "pif=", "pif-uuid=",
1406 "device=", "mode=", "ip=", "netmask=", "gateway=",
1408 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1409 except getopt.GetoptError, msg:
1412 force_rewrite_config = {}
1415 if o == "--output-directory":
1416 output_directory = a
1419 elif o == "--pif-uuid":
1421 elif o == "--session":
1423 elif o == "--force-interface" or o == "--force":
1425 elif o == "--management":
1426 force_management = True
1427 elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1428 force_rewrite_config[o[2:]] = a
1429 elif o == "-h" or o == "--help":
1430 print __doc__ % {'command-name': os.path.basename(argv[0])}
1433 if not debug_mode():
1434 syslog.openlog(os.path.basename(argv[0]))
1435 log("Called as " + str.join(" ", argv))
1437 raise Usage("Required option <action> not present")
1439 raise Usage("Too many arguments")
1442 # backwards compatibility
1443 if action == "rewrite-configuration": action = "rewrite"
1445 if output_directory and ( session or pif ):
1446 raise Usage("--session/--pif cannot be used with --output-directory")
1447 if ( session or pif ) and pif_uuid:
1448 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1449 if ( session and not pif ) or ( not session and pif ):
1450 raise Usage("--session and --pif must be used together.")
1451 if force_interface and ( session or pif or pif_uuid ):
1452 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1453 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1454 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1458 log("Force interface %s %s" % (force_interface, action))
1460 if action == "rewrite":
1461 action_force_rewrite(force_interface, force_rewrite_config)
1463 db = DatabaseCache(cache_file=dbcache_file)
1464 pif = db.get_pif_by_bridge(force_interface)
1465 management_pif = db.get_management_pif()
1469 elif action == "down":
1472 raise Usage("Unknown action %s" % action)
1474 db = DatabaseCache(session_ref=session)
1477 pif = db.get_pif_by_uuid(pif_uuid)
1480 raise Usage("No PIF given")
1482 if force_management:
1483 # pif is going to be the management pif
1484 management_pif = pif
1486 # pif is not going to be the management pif.
1487 # Search DB cache for pif on same host with management=true
1488 pifrec = db.get_pif_record(pif)
1489 management_pif = db.get_management_pif()
1491 log_pif_action(action, pif)
1493 if not check_allowed(pif):
1498 elif action == "down":
1500 elif action == "rewrite":
1503 raise Usage("Unknown action %s" % action)
1506 pifrec = db.get_pif_record(pif)
1507 db.save(dbcache_file)
1510 print >>sys.stderr, err.msg
1511 print >>sys.stderr, "For help use --help."
1519 # The following code allows interface-reconfigure to keep Centos
1520 # network configuration files up-to-date, even though the vswitch
1521 # never uses them. In turn, that means that "rpm -e vswitch" does not
1522 # have to update any configuration files.
1524 def configure_ethtool(oc, f):
1525 # Options for "ethtool -s"
1527 setting_opts = ["autoneg", "speed", "duplex"]
1528 # Options for "ethtool -K"
1530 offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
1532 for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
1533 val = oc["ethtool-" + opt]
1535 if opt in ["speed"]:
1536 if val in ["10", "100", "1000"]:
1537 val = "speed " + val
1539 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
1541 elif opt in ["duplex"]:
1542 if val in ["half", "full"]:
1543 val = "duplex " + val
1545 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
1547 elif opt in ["autoneg"] + offload_opts:
1548 if val in ["true", "on"]:
1550 elif val in ["false", "off"]:
1553 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
1556 if opt in setting_opts:
1557 if val and settings:
1558 settings = settings + " " + val
1561 elif opt in offload_opts:
1563 offload = offload + " " + val
1568 f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
1570 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
1572 def configure_mtu(oc, f):
1573 if not oc.has_key('mtu'):
1577 mtu = int(oc['mtu'])
1578 f.write("MTU=%d\n" % mtu)
1579 except ValueError, x:
1580 log("Invalid value for mtu = %s" % mtu)
1582 def configure_static_routes(interface, oc, f):
1583 """Open a route-<interface> file for static routes.
1585 Opens the static routes configuration file for interface and writes one
1586 line for each route specified in the network's other config "static-routes" value.
1588 interface ( RO): xenbr1
1589 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1591 Then route-xenbr1 should be
1592 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1593 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1595 fname = "route-%s" % interface
1596 if oc.has_key('static-routes'):
1597 # The key is present - extract comma seperates entries
1598 lines = oc['static-routes'].split(',')
1600 # The key is not present, i.e. there are no static routes
1603 child = ConfigurationFile(fname)
1604 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1605 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
1609 network, masklen, gateway = l.split('/')
1610 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1612 f.attach_child(child)
1615 except ValueError, e:
1616 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1618 def __open_ifcfg(interface):
1619 """Open a network interface configuration file.
1621 Opens the configuration file for interface, writes a header and
1622 common options and returns the file object.
1624 fname = "ifcfg-%s" % interface
1625 f = ConfigurationFile(fname)
1627 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1628 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1629 f.write("XEMANAGED=yes\n")
1630 f.write("DEVICE=%s\n" % interface)
1631 f.write("ONBOOT=no\n")
1635 def open_network_ifcfg(pif):
1636 bridge = bridge_name(pif)
1637 interface = interface_name(pif)
1639 return __open_ifcfg(bridge)
1641 return __open_ifcfg(interface)
1644 def open_pif_ifcfg(pif):
1645 pifrec = db.get_pif_record(pif)
1647 log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
1649 f = __open_ifcfg(interface_name(pif))
1651 if pifrec.has_key('other_config'):
1652 configure_ethtool(pifrec['other_config'], f)
1653 configure_mtu(pifrec['other_config'], f)
1657 def configure_network(pif, f):
1658 """Write the configuration file for a network.
1660 Writes configuration derived from the network object into the relevant
1661 ifcfg file. The configuration file is passed in, but if the network is
1662 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1664 This routine may also write ifcfg files of the networks corresponding to other PIFs
1665 in order to maintain consistency.
1668 pif: Opaque_ref of pif
1669 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1672 pifrec = db.get_pif_record(pif)
1673 nw = pifrec['network']
1674 nwrec = db.get_network_record(nw)
1676 bridge = bridge_name(pif)
1677 interface = interface_name(pif)
1683 if nwrec.has_key('other_config'):
1684 configure_ethtool(nwrec['other_config'], f)
1685 configure_mtu(nwrec['other_config'], f)
1686 configure_static_routes(device, nwrec['other_config'], f)
1689 if pifrec.has_key('other_config'):
1690 oc = pifrec['other_config']
1692 if device == bridge:
1693 f.write("TYPE=Bridge\n")
1694 f.write("DELAY=0\n")
1695 f.write("STP=off\n")
1696 f.write("PIFDEV=%s\n" % interface_name(pif))
1698 if pifrec['ip_configuration_mode'] == "DHCP":
1699 f.write("BOOTPROTO=dhcp\n")
1700 f.write("PERSISTENT_DHCLIENT=yes\n")
1701 elif pifrec['ip_configuration_mode'] == "Static":
1702 f.write("BOOTPROTO=none\n")
1703 f.write("NETMASK=%(netmask)s\n" % pifrec)
1704 f.write("IPADDR=%(IP)s\n" % pifrec)
1705 f.write("GATEWAY=%(gateway)s\n" % pifrec)
1706 elif pifrec['ip_configuration_mode'] == "None":
1707 f.write("BOOTPROTO=none\n")
1709 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1711 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1712 ServerList = pifrec['DNS'].split(",")
1713 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1714 if oc and oc.has_key('domain'):
1715 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1717 # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1718 # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1719 # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1721 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1723 # Note: we prune out the bond master pif (if it exists).
1724 # This is because when we are called to bring up an interface with a bond master, it is implicit that
1725 # we should bring down that master.
1726 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1727 not __pif in get_bond_masters_of_pif(pif) ]
1728 other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1731 defaultroute_pif = None
1733 # loop through all the pifs on this host looking for one with
1734 # other-config:peerdns = true, and one with
1735 # other-config:default-route=true
1736 for __pif in pifs_on_host:
1737 __pifrec = db.get_pif_record(__pif)
1738 __oc = __pifrec['other_config']
1739 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1740 if peerdns_pif == None:
1743 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1744 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1745 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1746 if defaultroute_pif == None:
1747 defaultroute_pif = __pif
1749 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1750 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1752 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1753 if peerdns_pif == None:
1754 peerdns_pif = management_pif
1755 if defaultroute_pif == None:
1756 defaultroute_pif = management_pif
1758 # Update all the other network's ifcfg files and ensure consistency
1759 for __pif in other_pifs_on_host:
1760 __f = open_network_ifcfg(__pif)
1761 peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1762 lines = __f.readlines()
1764 if not peerdns_line_wanted in lines:
1765 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1767 if not line.lstrip().startswith('PEERDNS'):
1769 log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1770 __f.write(peerdns_line_wanted)
1775 # There is no need to change this ifcfg file. So don't attach_child.
1778 # ... and for this pif too
1779 f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1782 fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1783 for line in fnetwork.readlines():
1784 if line.lstrip().startswith('GATEWAY') :
1786 fnetwork.write(line)
1787 if defaultroute_pif:
1788 gatewaydev = bridge_name(defaultroute_pif)
1790 gatewaydev = interface_name(defaultroute_pif)
1791 fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1793 f.attach_child(fnetwork)
1798 def configure_physical_interface(pif):
1799 """Write the configuration for a physical interface.
1801 Writes the configuration file for the physical interface described by
1804 Returns the open file handle for the interface configuration file.
1807 pifrec = db.get_pif_record(pif)
1809 f = open_pif_ifcfg(pif)
1811 f.write("TYPE=Ethernet\n")
1812 f.write("HWADDR=%(MAC)s\n" % pifrec)
1816 def configure_bond_interface(pif):
1817 """Write the configuration for a bond interface.
1819 Writes the configuration file for the bond interface described by
1820 the pif object. Handles writing the configuration for the slave
1823 Returns the open file handle for the bond interface configuration
1827 pifrec = db.get_pif_record(pif)
1828 oc = pifrec['other_config']
1829 f = open_pif_ifcfg(pif)
1831 if pifrec['MAC'] != "":
1832 f.write("MACADDR=%s\n" % pifrec['MAC'])
1834 for slave in get_bond_slaves_of_pif(pif):
1835 s = configure_physical_interface(slave)
1836 s.write("MASTER=%(device)s\n" % pifrec)
1837 s.write("SLAVE=yes\n")
1841 # The bond option defaults
1843 "mode": "balance-slb",
1850 # override defaults with values from other-config whose keys being with "bond-"
1851 overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
1852 overrides = map(lambda (key,val): (key[5:], val), overrides)
1853 bond_options.update(overrides)
1855 # write the bond options to ifcfg-bondX
1856 f.write('BONDING_OPTS="')
1857 for (name,val) in bond_options.items():
1858 f.write("%s=%s " % (name,val))
1862 def configure_vlan_interface(pif):
1863 """Write the configuration for a VLAN interface.
1865 Writes the configuration file for the VLAN interface described by
1866 the pif object. Handles writing the configuration for the master
1867 interface if necessary.
1869 Returns the open file handle for the VLAN interface configuration
1873 slave = configure_pif(get_vlan_slave_of_pif(pif))
1876 f = open_pif_ifcfg(pif)
1877 f.write("VLAN=yes\n")
1878 f.attach_child(slave)
1882 def configure_pif(pif):
1883 """Write the configuration for a PIF object.
1885 Writes the configuration file the PIF and all dependent
1886 interfaces (bond slaves and VLAN masters etc).
1888 Returns the open file handle for the interface configuration file.
1891 pifrec = db.get_pif_record(pif)
1893 if pifrec['VLAN'] != '-1':
1894 f = configure_vlan_interface(pif)
1895 elif len(pifrec['bond_master_of']) != 0:
1896 f = configure_bond_interface(pif)
1898 f = configure_physical_interface(pif)
1900 bridge = bridge_name(pif)
1902 f.write("BRIDGE=%s\n" % bridge)
1906 def unconfigure_pif(pif):
1907 """Clear up the files created by configure_pif"""
1908 f = open_pif_ifcfg(pif)
1909 log("Unlinking stale file %s" % f.path())
1913 if __name__ == "__main__":
1919 err = traceback.format_exception(*ex)
1923 if not debug_mode():