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)),
375 PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
376 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
377 ETHTOOL_OTHERCONFIG_ATTRS
379 VLAN_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
380 'tagged_PIF': (str_to_xml,str_from_xml),
381 'untagged_PIF': (str_to_xml,str_from_xml),
384 BOND_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
385 'master': (str_to_xml,str_from_xml),
386 'slaves': (lambda x, p, t, v: strlist_to_xml(x, p, 'slaves', 'slave', v),
387 lambda n: strlist_from_xml(n, 'slaves', 'slave')),
390 NETWORK_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
391 'bridge': (str_to_xml,str_from_xml),
392 'PIFs': (lambda x, p, t, v: strlist_to_xml(x, p, 'PIFs', 'PIF', v),
393 lambda n: strlist_from_xml(n, 'PIFs', 'PIF')),
394 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, NETWORK_OTHERCONFIG_ATTRS),
395 lambda n: otherconfig_from_xml(n, NETWORK_OTHERCONFIG_ATTRS)),
398 NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + ETHTOOL_OTHERCONFIG_ATTRS
400 class DatabaseCache(object):
401 def __read_xensource_inventory(self):
402 filename = "/etc/xensource-inventory"
403 f = open(filename, "r")
404 lines = [x.strip("\n") for x in f.readlines()]
407 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
408 defs = [ (a, b.strip("'")) for (a,b) in defs ]
411 def __pif_on_host(self,pif):
412 return self.__pifs.has_key(pif)
414 def __get_pif_records_from_xapi(self, session, host):
416 for (p,rec) in session.xenapi.PIF.get_all_records().items():
417 if rec['host'] != host:
421 self.__pifs[p][f] = rec[f]
422 self.__pifs[p]['other_config'] = {}
423 for f in PIF_OTHERCONFIG_ATTRS:
424 if not rec['other_config'].has_key(f): continue
425 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
427 def __get_vlan_records_from_xapi(self, session):
429 for v in session.xenapi.VLAN.get_all():
430 rec = session.xenapi.VLAN.get_record(v)
431 if not self.__pif_on_host(rec['untagged_PIF']):
435 self.__vlans[v][f] = rec[f]
437 def __get_bond_records_from_xapi(self, session):
439 for b in session.xenapi.Bond.get_all():
440 rec = session.xenapi.Bond.get_record(b)
441 if not self.__pif_on_host(rec['master']):
445 self.__bonds[b][f] = rec[f]
447 def __get_network_records_from_xapi(self, session):
449 for n in session.xenapi.network.get_all():
450 rec = session.xenapi.network.get_record(n)
451 self.__networks[n] = {}
452 for f in NETWORK_ATTRS:
453 self.__networks[n][f] = rec[f]
454 self.__networks[n]['other_config'] = {}
455 for f in NETWORK_OTHERCONFIG_ATTRS:
456 if not rec['other_config'].has_key(f): continue
457 self.__networks[n]['other_config'][f] = rec['other_config'][f]
459 def __to_xml(self, xml, parent, key, ref, rec, attrs):
460 """Encode a database object as XML"""
461 e = xml.createElement(key)
462 parent.appendChild(e)
464 e.setAttribute('ref', ref)
466 for n,v in rec.items():
471 raise Error("Unknown attribute %s" % n)
472 def __from_xml(self, e, attrs):
473 """Decode a database object from XML"""
474 ref = e.attributes['ref'].value
476 for n in e.childNodes:
477 if n.nodeName in attrs:
478 _,h = attrs[n.nodeName]
479 rec[n.nodeName] = h(n)
482 def __init__(self, session_ref=None, cache_file=None):
483 if session_ref and cache_file:
484 raise Error("can't specify session reference and cache file")
485 if cache_file == None:
486 session = XenAPI.xapi_local()
489 log("No session ref given on command line, logging in.")
490 session.xenapi.login_with_password("root", "")
492 session._session = session_ref
496 inventory = self.__read_xensource_inventory()
497 assert(inventory.has_key('INSTALLATION_UUID'))
498 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
500 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
502 self.__get_pif_records_from_xapi(session, host)
504 self.__get_vlan_records_from_xapi(session)
505 self.__get_bond_records_from_xapi(session)
506 self.__get_network_records_from_xapi(session)
509 session.xenapi.session.logout()
511 log("Loading xapi database cache from %s" % cache_file)
513 xml = parseXML(cache_file)
520 assert(len(xml.childNodes) == 1)
521 toplevel = xml.childNodes[0]
523 assert(toplevel.nodeName == "xenserver-network-configuration")
525 for n in toplevel.childNodes:
526 if n.nodeName == "#text":
528 elif n.nodeName == PIF_XML_TAG:
529 (ref,rec) = self.__from_xml(n, PIF_ATTRS)
530 self.__pifs[ref] = rec
531 elif n.nodeName == BOND_XML_TAG:
532 (ref,rec) = self.__from_xml(n, BOND_ATTRS)
533 self.__bonds[ref] = rec
534 elif n.nodeName == VLAN_XML_TAG:
535 (ref,rec) = self.__from_xml(n, VLAN_ATTRS)
536 self.__vlans[ref] = rec
537 elif n.nodeName == NETWORK_XML_TAG:
538 (ref,rec) = self.__from_xml(n, NETWORK_ATTRS)
539 self.__networks[ref] = rec
541 raise Error("Unknown XML element %s" % n.nodeName)
543 def save(self, cache_file):
545 xml = getDOMImplementation().createDocument(
546 None, "xenserver-network-configuration", None)
547 for (ref,rec) in self.__pifs.items():
548 self.__to_xml(xml, xml.documentElement, PIF_XML_TAG, ref, rec, PIF_ATTRS)
549 for (ref,rec) in self.__bonds.items():
550 self.__to_xml(xml, xml.documentElement, BOND_XML_TAG, ref, rec, BOND_ATTRS)
551 for (ref,rec) in self.__vlans.items():
552 self.__to_xml(xml, xml.documentElement, VLAN_XML_TAG, ref, rec, VLAN_ATTRS)
553 for (ref,rec) in self.__networks.items():
554 self.__to_xml(xml, xml.documentElement, NETWORK_XML_TAG, ref, rec,
557 f = open(cache_file, 'w')
558 f.write(xml.toprettyxml())
561 def get_pif_by_uuid(self, uuid):
562 pifs = map(lambda (ref,rec): ref,
563 filter(lambda (ref,rec): uuid == rec['uuid'],
564 self.__pifs.items()))
566 raise Error("Unknown PIF \"%s\"" % uuid)
568 raise Error("Non-unique PIF \"%s\"" % uuid)
572 def get_pifs_by_device(self, device):
573 return map(lambda (ref,rec): ref,
574 filter(lambda (ref,rec): rec['device'] == device,
575 self.__pifs.items()))
577 def get_pif_by_bridge(self, bridge):
578 networks = map(lambda (ref,rec): ref,
579 filter(lambda (ref,rec): rec['bridge'] == bridge,
580 self.__networks.items()))
581 if len(networks) == 0:
582 raise Error("No matching network \"%s\"")
585 for network in networks:
586 nwrec = self.get_network_record(network)
587 for pif in nwrec['PIFs']:
588 pifrec = self.get_pif_record(pif)
590 raise Error("Multiple PIFs on host for network %s" % (bridge))
593 raise Error("No PIF on host for network %s" % (bridge))
596 def get_pif_record(self, pif):
597 if self.__pifs.has_key(pif):
598 return self.__pifs[pif]
599 raise Error("Unknown PIF \"%s\" (get_pif_record)" % pif)
600 def get_all_pifs(self):
602 def pif_exists(self, pif):
603 return self.__pifs.has_key(pif)
605 def get_management_pif(self):
606 """ Returns the management pif on host
608 all = self.get_all_pifs()
610 pifrec = self.get_pif_record(pif)
611 if pifrec['management']: return pif
614 def get_network_record(self, network):
615 if self.__networks.has_key(network):
616 return self.__networks[network]
617 raise Error("Unknown network \"%s\"" % network)
618 def get_all_networks(self):
619 return self.__networks
621 def get_bond_record(self, bond):
622 if self.__bonds.has_key(bond):
623 return self.__bonds[bond]
627 def get_vlan_record(self, vlan):
628 if self.__vlans.has_key(vlan):
629 return self.__vlans[vlan]
633 def bridge_name(pif):
634 """Return the bridge name associated with pif, or None if network is bridgeless"""
635 pifrec = db.get_pif_record(pif)
636 nwrec = db.get_network_record(pifrec['network'])
639 # TODO: sanity check that nwrec['bridgeless'] != 'true'
640 return nwrec['bridge']
642 # TODO: sanity check that nwrec['bridgeless'] == 'true'
645 def interface_name(pif):
646 """Construct an interface name from the given PIF record."""
648 pifrec = db.get_pif_record(pif)
650 if pifrec['VLAN'] == '-1':
651 return pifrec['device']
653 return "%(device)s.%(VLAN)s" % pifrec
655 def datapath_name(pif):
656 """Return the OpenFlow datapath name associated with pif.
657 For a non-VLAN PIF, the datapath name is the bridge name.
658 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
659 (xapi will create a datapath named with the bridge name even though we won't
664 pifrec = db.get_pif_record(pif)
666 if pifrec['VLAN'] == '-1':
667 return bridge_name(pif)
669 return bridge_name(get_vlan_slave_of_pif(pif))
672 """Return the the name of the network device that carries the
673 IP configuration (if any) associated with pif.
674 The ipdev name is the same as the bridge name.
677 pifrec = db.get_pif_record(pif)
678 return bridge_name(pif)
680 def get_physdev_pifs(pif):
681 """Return the PIFs for the physical network device(s) associated with pif.
682 For a VLAN PIF, this is the VLAN slave's physical device PIF.
683 For a bond master PIF, these are the bond slave PIFs.
684 For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
686 pifrec = db.get_pif_record(pif)
688 if pifrec['VLAN'] != '-1':
689 return get_physdev_pifs(get_vlan_slave_of_pif(pif))
690 elif len(pifrec['bond_master_of']) != 0:
691 return get_bond_slaves_of_pif(pif)
695 def get_physdev_names(pif):
696 """Return the name(s) of the physical network device(s) associated with pif.
697 For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
698 For a bond master PIF, the physical devices are the bond slaves.
699 For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
702 return [db.get_pif_record(phys)['device'] for phys in get_physdev_pifs(pif)]
704 def log_pif_action(action, pif):
705 pifrec = db.get_pif_record(pif)
707 rec['uuid'] = pifrec['uuid']
708 rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
709 rec['action'] = action
710 rec['interface-name'] = interface_name(pif)
711 if action == "rewrite":
712 rec['message'] = "Rewrite PIF %(uuid)s configuration" % rec
714 rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
715 log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % rec)
717 def get_bond_masters_of_pif(pif):
718 """Returns a list of PIFs which are bond masters of this PIF"""
720 pifrec = db.get_pif_record(pif)
722 bso = pifrec['bond_slave_of']
724 # bond-slave-of is currently a single reference but in principle a
725 # PIF could be a member of several bonds which are not
726 # concurrently attached. Be robust to this possibility.
727 if not bso or bso == "OpaqueRef:NULL":
729 elif not type(bso) == list:
732 bondrecs = [db.get_bond_record(bond) for bond in bso]
733 bondrecs = [rec for rec in bondrecs if rec]
735 return [bond['master'] for bond in bondrecs]
737 def get_bond_slaves_of_pif(pif):
738 """Returns a list of PIFs which make up the given bonded pif."""
740 pifrec = db.get_pif_record(pif)
742 bmo = pifrec['bond_master_of']
744 raise Error("Bond-master-of contains too many elements")
749 bondrec = db.get_bond_record(bmo[0])
751 raise Error("No bond record for bond master PIF")
753 return bondrec['slaves']
755 def get_vlan_slave_of_pif(pif):
756 """Find the PIF which is the VLAN slave of pif.
758 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
760 pifrec = db.get_pif_record(pif)
762 vlan = pifrec['VLAN_master_of']
763 if not vlan or vlan == "OpaqueRef:NULL":
764 raise Error("PIF is not a VLAN master")
766 vlanrec = db.get_vlan_record(vlan)
768 raise Error("No VLAN record found for PIF")
770 return vlanrec['tagged_PIF']
772 def get_vlan_masters_of_pif(pif):
773 """Returns a list of PIFs which are VLANs on top of the given pif."""
775 pifrec = db.get_pif_record(pif)
776 vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
777 return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])]
779 def interface_deconfigure_commands(interface):
780 # The use of [!0-9] keeps an interface of 'eth0' from matching
781 # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
783 return ['--del-match=bridge.*.port=%s' % interface,
784 '--del-match=bonding.%s.[!0-9]*' % interface,
785 '--del-match=bonding.*.slave=%s' % interface,
786 '--del-match=vlan.%s.[!0-9]*' % interface,
787 '--del-match=port.%s.[!0-9]*' % interface,
788 '--del-match=iface.%s.[!0-9]*' % interface]
790 def run_command(command):
791 log("Running command: " + ' '.join(command))
792 if os.spawnl(os.P_WAIT, command[0], *command) != 0:
793 log("Command failed: " + ' '.join(command))
797 def rename_netdev(old_name, new_name):
798 log("Changing the name of %s to %s" % (old_name, new_name))
799 run_command(['/sbin/ifconfig', old_name, 'down'])
800 if not run_command(['/sbin/ip', 'link', 'set', old_name,
802 raise Error("Could not rename %s to %s" % (old_name, new_name))
804 # Check whether 'pif' exists and has the correct MAC.
805 # If not, try to find a device with the correct MAC and rename it.
806 # 'already_renamed' is used to avoid infinite recursion.
807 def remap_pif(pif, already_renamed=[]):
808 pifrec = db.get_pif_record(pif)
809 device = pifrec['device']
812 # Is there a network device named 'device' at all?
813 device_exists = interface_exists(device)
815 # Yes. Does it have MAC 'mac'?
816 found_mac = get_netdev_mac(device)
817 if found_mac and mac.lower() == found_mac.lower():
818 # Yes, everything checks out the way we want. Nothing to do.
821 log("No network device %s" % device)
823 # What device has MAC 'mac'?
824 cur_device = get_netdev_by_mac(mac)
826 log("No network device has MAC %s" % mac)
829 # First rename 'device', if it exists, to get it out of the way
830 # for 'cur_device' to replace it.
832 rename_netdev(device, "dev%d" % random.getrandbits(24))
834 # Rename 'cur_device' to 'device'.
835 rename_netdev(cur_device, device)
837 def read_first_line_of_file(name):
840 file = open(name, 'r')
841 return file.readline().rstrip('\n')
846 def down_netdev(interface, deconfigure=True):
847 if not interface_exists(interface):
848 log("down_netdev: interface %s does not exist, ignoring" % interface)
852 pidfile_name = '/var/run/dhclient-%s.pid' % interface
854 os.kill(int(read_first_line_of_file(pidfile_name)), signal.SIGTERM)
858 # Remove dhclient pidfile.
860 os.remove(pidfile_name)
864 run_command(["/sbin/ifconfig", interface, '0.0.0.0'])
866 run_command(["/sbin/ifconfig", interface, 'down'])
868 def up_netdev(interface):
869 run_command(["/sbin/ifconfig", interface, 'up'])
871 def find_distinguished_pifs(pif):
872 """Returns the PIFs on host that own DNS and the default route.
873 The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
874 The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
876 Note: we prune out the bond master pif (if it exists).
877 This is because when we are called to bring up an interface with a bond master, it is implicit that
878 we should bring down that master."""
880 pifrec = db.get_pif_record(pif)
882 pifs = [ __pif for __pif in db.get_all_pifs() if
883 (not __pif in get_bond_masters_of_pif(pif)) ]
886 defaultroute_pif = None
888 # loop through all the pifs on this host looking for one with
889 # other-config:peerdns = true, and one with
890 # other-config:default-route=true
892 __pifrec = db.get_pif_record(__pif)
893 __oc = __pifrec['other_config']
894 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
895 if peerdns_pif == None:
898 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
899 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
900 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
901 if defaultroute_pif == None:
902 defaultroute_pif = __pif
904 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
905 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
907 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
908 if peerdns_pif == None:
909 peerdns_pif = management_pif
910 if defaultroute_pif == None:
911 defaultroute_pif = management_pif
913 return peerdns_pif, defaultroute_pif
915 def run_ethtool(device, oc):
916 # Run "ethtool -s" if there are any settings.
918 if oc.has_key('ethtool-speed'):
919 val = oc['ethtool-speed']
920 if val in ["10", "100", "1000"]:
921 settings += ['speed', val]
923 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
924 if oc.has_key('ethtool-duplex'):
925 val = oc['ethtool-duplex']
926 if val in ["10", "100", "1000"]:
927 settings += ['duplex', 'val']
929 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
930 if oc.has_key('ethtool-autoneg'):
931 val = oc['ethtool-autoneg']
932 if val in ["true", "on"]:
933 settings += ['autoneg', 'on']
934 elif val in ["false", "off"]:
935 settings += ['autoneg', 'off']
937 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
939 run_command(['/sbin/ethtool', '-s', device] + settings)
941 # Run "ethtool -K" if there are any offload settings.
943 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
944 if oc.has_key("ethtool-" + opt):
945 val = oc["ethtool-" + opt]
946 if val in ["true", "on"]:
947 offload += [opt, 'on']
948 elif val in ["false", "off"]:
949 offload += [opt, 'off']
951 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
953 run_command(['/sbin/ethtool', '-K', device] + offload)
956 if oc.has_key('mtu'):
958 int(oc['mtu']) # Check that the value is an integer
959 return ['mtu', oc['mtu']]
960 except ValueError, x:
961 log("Invalid value for mtu = %s" % mtu)
964 def configure_local_port(pif):
965 pifrec = db.get_pif_record(pif)
966 datapath = datapath_name(pif)
967 ipdev = ipdev_name(pif)
969 nw = pifrec['network']
970 nwrec = db.get_network_record(nw)
972 pif_oc = pifrec['other_config']
973 nw_oc = nwrec['other_config']
975 # IP (except DHCP) and MTU.
976 ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up']
978 if pifrec['ip_configuration_mode'] == "DHCP":
980 elif pifrec['ip_configuration_mode'] == "Static":
981 ifconfig_argv += [pifrec['IP']]
982 ifconfig_argv += ['netmask', pifrec['netmask']]
983 gateway = pifrec['gateway']
984 elif pifrec['ip_configuration_mode'] == "None":
988 raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode'])
989 ifconfig_argv += mtu_setting(nw_oc)
990 run_command(ifconfig_argv)
992 (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif)
995 if peerdns_pif == pif:
996 f = ConfigurationFile('resolv.conf', "/etc")
997 if pif_oc.has_key('domain'):
998 f.write("search %s\n" % pif_oc['domain'])
999 for dns in pifrec['DNS'].split(","):
1000 f.write("nameserver %s\n" % dns)
1006 if defaultroute_pif == pif and gateway != '':
1007 run_command(['/sbin/ip', 'route', 'replace', 'default',
1008 'via', gateway, 'dev', ipdev])
1009 if nw_oc.has_key('static-routes'):
1010 for line in nw_oc['static-routes'].split(','):
1011 network, masklen, gateway = line.split('/')
1012 run_command(['/sbin/ip', 'route', 'add',
1013 '%s/%s' % (network, masklen), 'via', gateway,
1017 run_ethtool(ipdev, nw_oc)
1020 if pifrec['ip_configuration_mode'] == "DHCP":
1022 print "Determining IP information for %s..." % ipdev,
1023 argv = ['/sbin/dhclient', '-q',
1024 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev,
1025 '-pf', '/var/run/dhclient-%s.pid' % ipdev,
1027 if run_command(argv):
1032 def configure_physdev(pif):
1033 pifrec = db.get_pif_record(pif)
1034 device = pifrec['device']
1035 oc = pifrec['other_config']
1037 run_command(['/sbin/ifconfig', device, 'up'] + mtu_setting(oc))
1038 run_ethtool(device, oc)
1040 def modify_config(commands):
1041 run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer',
1042 '-F', '/etc/ovs-vswitchd.conf']
1043 + commands + ['-c'])
1044 run_command(['/sbin/service', 'vswitch', 'reload'])
1046 def is_bond_pif(pif):
1047 pifrec = db.get_pif_record(pif)
1048 return len(pifrec['bond_master_of']) != 0
1050 def configure_bond(pif):
1051 pifrec = db.get_pif_record(pif)
1052 interface = interface_name(pif)
1053 ipdev = ipdev_name(pif)
1054 datapath = datapath_name(pif)
1055 physdev_names = get_physdev_names(pif)
1057 argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
1058 argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
1059 for slave in physdev_names]
1060 argv += ['--add=bonding.%s.fake-iface=true' % interface]
1062 if pifrec['MAC'] != "":
1063 argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
1067 "mode": "balance-slb",
1073 # override defaults with values from other-config whose keys
1074 # being with "bond-"
1075 oc = pifrec['other_config']
1076 overrides = filter(lambda (key,val):
1077 key.startswith("bond-"), oc.items())
1078 overrides = map(lambda (key,val): (key[5:], val), overrides)
1079 bond_options.update(overrides)
1080 for (name,val) in bond_options.items():
1081 argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
1085 pifrec = db.get_pif_record(pif)
1087 bridge = bridge_name(pif)
1088 interface = interface_name(pif)
1089 ipdev = ipdev_name(pif)
1090 datapath = datapath_name(pif)
1091 physdev_pifs = get_physdev_pifs(pif)
1092 physdev_names = get_physdev_names(pif)
1094 if pifrec['VLAN'] != '-1':
1095 vlan_slave = get_vlan_slave_of_pif(pif)
1096 if vlan_slave and is_bond_pif(vlan_slave):
1097 bond_master = vlan_slave
1098 elif is_bond_pif(pif):
1103 bond_slaves = get_bond_slaves_of_pif(bond_master)
1106 bond_masters = get_bond_masters_of_pif(pif)
1108 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1109 # files up-to-date, even though we don't use them or need them.
1110 f = configure_pif(pif)
1111 mode = pifrec['ip_configuration_mode']
1113 log("Configuring %s using %s configuration" % (bridge, mode))
1114 br = open_network_ifcfg(pif)
1115 configure_network(pif, br)
1119 log("Configuring %s using %s configuration" % (interface, mode))
1120 configure_network(pif, f)
1122 for master in bond_masters:
1123 master_bridge = bridge_name(master)
1124 removed = unconfigure_pif(master)
1125 f.attach_child(removed)
1127 removed = open_network_ifcfg(master)
1128 log("Unlinking stale file %s" % removed.path())
1130 f.attach_child(removed)
1132 # /etc/xensource/scripts/vif needs to know where to add VIFs.
1134 if not os.path.exists(vswitch_state_dir):
1135 os.mkdir(vswitch_state_dir)
1136 br = ConfigurationFile("br-%s" % bridge, vswitch_state_dir)
1137 br.write("VLAN_SLAVE=%s\n" % datapath)
1138 br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
1142 # Update all configuration files (both ours and Centos's).
1146 # Check the MAC address of each network device and remap if
1147 # necessary to make names match our expectations.
1148 for physdev_pif in physdev_pifs:
1149 remap_pif(physdev_pif)
1151 # "ifconfig down" the network device and delete its IP address, etc.
1153 for physdev_name in physdev_names:
1154 down_netdev(physdev_name)
1156 # If we are bringing up a bond, remove IP addresses from the
1157 # slaves (because we are implicitly being asked to take them down).
1159 # Conversely, if we are bringing up an interface that has bond
1160 # masters, remove IP addresses from the bond master (because we
1161 # are implicitly being asked to take it down).
1162 for bond_pif in bond_slaves + bond_masters:
1163 run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0'])
1165 # Remove all keys related to pif and any bond masters linked to PIF.
1166 del_ports = [ipdev] + physdev_names + bond_masters
1167 if vlan_slave and bond_master:
1168 del_ports += [interface_name(bond_master)]
1170 # What ports do we need to add to the datapath?
1172 # We definitely need the ipdev, and ordinarily we want the
1173 # physical devices too, but for bonds we need the bond as bridge
1175 add_ports = [ipdev, datapath]
1177 add_ports += physdev_names
1179 add_ports += [interface_name(bond_master)]
1181 # What ports do we need to delete?
1183 # - All the ports that we add, to avoid duplication and to drop
1184 # them from another datapath in case they're misassigned.
1186 # - The physical devices, since they will either be in add_ports
1187 # or added to the bonding device (see below).
1189 # - The bond masters for pif. (Ordinarily pif shouldn't have any
1190 # bond masters. If it does then interface-reconfigure is
1191 # implicitly being asked to take them down.)
1192 del_ports = add_ports + physdev_names + bond_masters
1194 # What networks does this datapath carry?
1196 # - The network corresponding to the datapath's PIF.
1198 # - The networks corresponding to any VLANs attached to the
1201 for nwpif in db.get_pifs_by_device({'device': pifrec['device']}):
1202 net = db.get_pif_record(nwpif)['network']
1203 network_uuids += [db.get_network_record(net)['uuid']]
1205 # Bring up bond slaves early, because ovs-vswitchd initially
1206 # enables or disables bond slaves based on whether carrier is
1207 # detected when they are added, and a network device that is down
1208 # always reports "no carrier".
1209 bond_slave_physdev_pifs = []
1210 for slave in bond_slaves:
1211 bond_slave_physdev_pifs += get_physdev_pifs(slave)
1212 for slave_physdev_pif in set(bond_slave_physdev_pifs):
1213 configure_physdev(slave_physdev_pif)
1215 # Now modify the ovs-vswitchd config file.
1217 for port in set(del_ports):
1218 argv += interface_deconfigure_commands(port)
1219 for port in set(add_ports):
1220 argv += ['--add=bridge.%s.port=%s' % (datapath, port)]
1222 argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
1223 argv += ['--add=iface.%s.internal=true' % (ipdev)]
1225 # xapi creates a bridge by the name of the ipdev and requires
1226 # that the IP address will be on it. We need to delete this
1227 # bridge because we need that device to be a member of our
1229 argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev]
1231 # xapi insists that its attempts to create the bridge succeed,
1232 # so force that to happen.
1233 argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
1236 os.unlink("%s/br-%s" % (vswitch_state_dir, bridge))
1239 argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath]
1240 argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid)
1241 for uuid in set(network_uuids)]
1243 argv += configure_bond(bond_master)
1246 # Bring up VLAN slave, plus physical devices other than bond
1247 # slaves (which we brought up earlier).
1249 up_netdev(ipdev_name(vlan_slave))
1250 for physdev_pif in set(physdev_pifs) - set(bond_slave_physdev_pifs):
1251 configure_physdev(physdev_pif)
1253 # Configure network device for local port.
1254 configure_local_port(pif)
1256 # Update /etc/issue (which contains the IP address of the management interface)
1257 os.system("/sbin/update-issue")
1260 # There seems to be a race somewhere: without this sleep, using
1261 # XenCenter to create a bond that becomes the management interface
1262 # fails with "The underlying connection was closed: A connection that
1263 # was expected to be kept alive was closed by the server." on every
1264 # second or third try, even though /var/log/messages doesn't show
1267 # The race is probably present even without vswitch, but bringing up a
1268 # bond without vswitch involves a built-in pause of 10 seconds or more
1269 # to wait for the bond to transition from learning to forwarding state.
1272 def action_down(pif):
1273 rec = db.get_pif_record(pif)
1274 interface = interface_name(pif)
1275 bridge = bridge_name(pif)
1276 ipdev = ipdev_name(pif)
1278 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1279 # files up-to-date, even though we don't use them or need them.
1280 f = unconfigure_pif(pif)
1282 br = open_network_ifcfg(pif)
1283 log("Unlinking stale file %s" % br.path())
1290 log("action_down failed to apply changes: %s" % e.msg)
1295 if rec['VLAN'] != '-1':
1296 # Get rid of the VLAN device itself.
1298 argv += interface_deconfigure_commands(ipdev)
1300 # If the VLAN's slave is attached, stop here.
1301 slave = get_vlan_slave_of_pif(pif)
1302 if db.get_pif_record(slave)['currently_attached']:
1303 log("VLAN slave is currently attached")
1307 # If the VLAN's slave has other VLANs that are attached, stop here.
1308 masters = get_vlan_masters_of_pif(slave)
1310 if m != pif and db.get_pif_record(m)['currently_attached']:
1311 log("VLAN slave has other master %s" % interface_naem(m))
1315 # Otherwise, take down the VLAN's slave too.
1316 log("No more masters, bring down vlan slave %s" % interface_name(slave))
1319 # Stop here if this PIF has attached VLAN masters.
1320 vlan_masters = get_vlan_masters_of_pif(pif)
1321 log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters]))
1322 for m in vlan_masters:
1323 if db.get_pif_record(m)['currently_attached']:
1324 log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m)))
1327 # pif is now either a bond or a physical device which needs to be
1328 # brought down. pif might have changed so re-check all its attributes.
1329 rec = db.get_pif_record(pif)
1330 interface = interface_name(pif)
1331 bridge = bridge_name(pif)
1332 ipdev = ipdev_name(pif)
1335 bond_slaves = get_bond_slaves_of_pif(pif)
1336 log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves]))
1337 for slave in bond_slaves:
1338 slave_interface = interface_name(slave)
1339 log("bring down bond slave %s" % slave_interface)
1340 argv += interface_deconfigure_commands(slave_interface)
1341 down_netdev(slave_interface)
1343 argv += interface_deconfigure_commands(ipdev)
1346 argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)]
1347 argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface]
1350 def action_rewrite(pif):
1351 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1352 # files up-to-date, even though we don't use them or need them.
1353 pifrec = db.get_pif_record(pif)
1354 f = configure_pif(pif)
1355 interface = interface_name(pif)
1356 bridge = bridge_name(pif)
1357 mode = pifrec['ip_configuration_mode']
1359 log("Configuring %s using %s configuration" % (bridge, mode))
1360 br = open_network_ifcfg(pif)
1361 configure_network(pif, br)
1365 log("Configuring %s using %s configuration" % (interface, mode))
1366 configure_network(pif, f)
1372 log("failed to apply changes: %s" % e.msg)
1376 # We have no code of our own to run here.
1379 def main(argv=None):
1380 global output_directory, management_pif
1386 force_interface = None
1387 force_management = False
1395 longops = [ "output-directory=",
1396 "pif=", "pif-uuid=",
1402 "device=", "mode=", "ip=", "netmask=", "gateway=",
1404 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1405 except getopt.GetoptError, msg:
1408 force_rewrite_config = {}
1411 if o == "--output-directory":
1412 output_directory = a
1415 elif o == "--pif-uuid":
1417 elif o == "--session":
1419 elif o == "--force-interface" or o == "--force":
1421 elif o == "--management":
1422 force_management = True
1423 elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1424 force_rewrite_config[o[2:]] = a
1425 elif o == "-h" or o == "--help":
1426 print __doc__ % {'command-name': os.path.basename(argv[0])}
1429 if not debug_mode():
1430 syslog.openlog(os.path.basename(argv[0]))
1431 log("Called as " + str.join(" ", argv))
1433 raise Usage("Required option <action> not present")
1435 raise Usage("Too many arguments")
1438 # backwards compatibility
1439 if action == "rewrite-configuration": action = "rewrite"
1441 if output_directory and ( session or pif ):
1442 raise Usage("--session/--pif cannot be used with --output-directory")
1443 if ( session or pif ) and pif_uuid:
1444 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1445 if ( session and not pif ) or ( not session and pif ):
1446 raise Usage("--session and --pif must be used together.")
1447 if force_interface and ( session or pif or pif_uuid ):
1448 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1449 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1450 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1454 log("Force interface %s %s" % (force_interface, action))
1456 if action == "rewrite":
1457 action_force_rewrite(force_interface, force_rewrite_config)
1459 db = DatabaseCache(cache_file=dbcache_file)
1460 pif = db.get_pif_by_bridge(force_interface)
1461 management_pif = db.get_management_pif()
1465 elif action == "down":
1468 raise Usage("Unknown action %s" % action)
1470 db = DatabaseCache(session_ref=session)
1473 pif = db.get_pif_by_uuid(pif_uuid)
1476 raise Usage("No PIF given")
1478 if force_management:
1479 # pif is going to be the management pif
1480 management_pif = pif
1482 # pif is not going to be the management pif.
1483 # Search DB cache for pif on same host with management=true
1484 pifrec = db.get_pif_record(pif)
1485 management_pif = db.get_management_pif()
1487 log_pif_action(action, pif)
1489 if not check_allowed(pif):
1494 elif action == "down":
1496 elif action == "rewrite":
1499 raise Usage("Unknown action %s" % action)
1502 pifrec = db.get_pif_record(pif)
1503 db.save(dbcache_file)
1506 print >>sys.stderr, err.msg
1507 print >>sys.stderr, "For help use --help."
1515 # The following code allows interface-reconfigure to keep Centos
1516 # network configuration files up-to-date, even though the vswitch
1517 # never uses them. In turn, that means that "rpm -e vswitch" does not
1518 # have to update any configuration files.
1520 def configure_ethtool(oc, f):
1521 # Options for "ethtool -s"
1523 setting_opts = ["autoneg", "speed", "duplex"]
1524 # Options for "ethtool -K"
1526 offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
1528 for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
1529 val = oc["ethtool-" + opt]
1531 if opt in ["speed"]:
1532 if val in ["10", "100", "1000"]:
1533 val = "speed " + val
1535 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
1537 elif opt in ["duplex"]:
1538 if val in ["half", "full"]:
1539 val = "duplex " + val
1541 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
1543 elif opt in ["autoneg"] + offload_opts:
1544 if val in ["true", "on"]:
1546 elif val in ["false", "off"]:
1549 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
1552 if opt in setting_opts:
1553 if val and settings:
1554 settings = settings + " " + val
1557 elif opt in offload_opts:
1559 offload = offload + " " + val
1564 f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
1566 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
1568 def configure_mtu(oc, f):
1569 if not oc.has_key('mtu'):
1573 mtu = int(oc['mtu'])
1574 f.write("MTU=%d\n" % mtu)
1575 except ValueError, x:
1576 log("Invalid value for mtu = %s" % mtu)
1578 def configure_static_routes(interface, oc, f):
1579 """Open a route-<interface> file for static routes.
1581 Opens the static routes configuration file for interface and writes one
1582 line for each route specified in the network's other config "static-routes" value.
1584 interface ( RO): xenbr1
1585 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1587 Then route-xenbr1 should be
1588 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1589 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1591 fname = "route-%s" % interface
1592 if oc.has_key('static-routes'):
1593 # The key is present - extract comma seperates entries
1594 lines = oc['static-routes'].split(',')
1596 # The key is not present, i.e. there are no static routes
1599 child = ConfigurationFile(fname)
1600 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1601 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
1605 network, masklen, gateway = l.split('/')
1606 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1608 f.attach_child(child)
1611 except ValueError, e:
1612 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1614 def __open_ifcfg(interface):
1615 """Open a network interface configuration file.
1617 Opens the configuration file for interface, writes a header and
1618 common options and returns the file object.
1620 fname = "ifcfg-%s" % interface
1621 f = ConfigurationFile(fname)
1623 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1624 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1625 f.write("XEMANAGED=yes\n")
1626 f.write("DEVICE=%s\n" % interface)
1627 f.write("ONBOOT=no\n")
1631 def open_network_ifcfg(pif):
1632 bridge = bridge_name(pif)
1633 interface = interface_name(pif)
1635 return __open_ifcfg(bridge)
1637 return __open_ifcfg(interface)
1640 def open_pif_ifcfg(pif):
1641 pifrec = db.get_pif_record(pif)
1643 log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
1645 f = __open_ifcfg(interface_name(pif))
1647 if pifrec.has_key('other_config'):
1648 configure_ethtool(pifrec['other_config'], f)
1649 configure_mtu(pifrec['other_config'], f)
1653 def configure_network(pif, f):
1654 """Write the configuration file for a network.
1656 Writes configuration derived from the network object into the relevant
1657 ifcfg file. The configuration file is passed in, but if the network is
1658 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1660 This routine may also write ifcfg files of the networks corresponding to other PIFs
1661 in order to maintain consistency.
1664 pif: Opaque_ref of pif
1665 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1668 pifrec = db.get_pif_record(pif)
1669 nw = pifrec['network']
1670 nwrec = db.get_network_record(nw)
1672 bridge = bridge_name(pif)
1673 interface = interface_name(pif)
1679 if nwrec.has_key('other_config'):
1680 configure_ethtool(nwrec['other_config'], f)
1681 configure_mtu(nwrec['other_config'], f)
1682 configure_static_routes(device, nwrec['other_config'], f)
1685 if pifrec.has_key('other_config'):
1686 oc = pifrec['other_config']
1688 if device == bridge:
1689 f.write("TYPE=Bridge\n")
1690 f.write("DELAY=0\n")
1691 f.write("STP=off\n")
1692 f.write("PIFDEV=%s\n" % interface_name(pif))
1694 if pifrec['ip_configuration_mode'] == "DHCP":
1695 f.write("BOOTPROTO=dhcp\n")
1696 f.write("PERSISTENT_DHCLIENT=yes\n")
1697 elif pifrec['ip_configuration_mode'] == "Static":
1698 f.write("BOOTPROTO=none\n")
1699 f.write("NETMASK=%(netmask)s\n" % pifrec)
1700 f.write("IPADDR=%(IP)s\n" % pifrec)
1701 f.write("GATEWAY=%(gateway)s\n" % pifrec)
1702 elif pifrec['ip_configuration_mode'] == "None":
1703 f.write("BOOTPROTO=none\n")
1705 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1707 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1708 ServerList = pifrec['DNS'].split(",")
1709 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1710 if oc and oc.has_key('domain'):
1711 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1713 # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1714 # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1715 # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1717 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1719 # Note: we prune out the bond master pif (if it exists).
1720 # This is because when we are called to bring up an interface with a bond master, it is implicit that
1721 # we should bring down that master.
1722 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1723 not __pif in get_bond_masters_of_pif(pif) ]
1724 other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1727 defaultroute_pif = None
1729 # loop through all the pifs on this host looking for one with
1730 # other-config:peerdns = true, and one with
1731 # other-config:default-route=true
1732 for __pif in pifs_on_host:
1733 __pifrec = db.get_pif_record(__pif)
1734 __oc = __pifrec['other_config']
1735 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1736 if peerdns_pif == None:
1739 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1740 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1741 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1742 if defaultroute_pif == None:
1743 defaultroute_pif = __pif
1745 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1746 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1748 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1749 if peerdns_pif == None:
1750 peerdns_pif = management_pif
1751 if defaultroute_pif == None:
1752 defaultroute_pif = management_pif
1754 # Update all the other network's ifcfg files and ensure consistency
1755 for __pif in other_pifs_on_host:
1756 __f = open_network_ifcfg(__pif)
1757 peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1758 lines = __f.readlines()
1760 if not peerdns_line_wanted in lines:
1761 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1763 if not line.lstrip().startswith('PEERDNS'):
1765 log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1766 __f.write(peerdns_line_wanted)
1771 # There is no need to change this ifcfg file. So don't attach_child.
1774 # ... and for this pif too
1775 f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1778 fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1779 for line in fnetwork.readlines():
1780 if line.lstrip().startswith('GATEWAY') :
1782 fnetwork.write(line)
1783 if defaultroute_pif:
1784 gatewaydev = bridge_name(defaultroute_pif)
1786 gatewaydev = interface_name(defaultroute_pif)
1787 fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1789 f.attach_child(fnetwork)
1794 def configure_physical_interface(pif):
1795 """Write the configuration for a physical interface.
1797 Writes the configuration file for the physical interface described by
1800 Returns the open file handle for the interface configuration file.
1803 pifrec = db.get_pif_record(pif)
1805 f = open_pif_ifcfg(pif)
1807 f.write("TYPE=Ethernet\n")
1808 f.write("HWADDR=%(MAC)s\n" % pifrec)
1812 def configure_bond_interface(pif):
1813 """Write the configuration for a bond interface.
1815 Writes the configuration file for the bond interface described by
1816 the pif object. Handles writing the configuration for the slave
1819 Returns the open file handle for the bond interface configuration
1823 pifrec = db.get_pif_record(pif)
1824 oc = pifrec['other_config']
1825 f = open_pif_ifcfg(pif)
1827 if pifrec['MAC'] != "":
1828 f.write("MACADDR=%s\n" % pifrec['MAC'])
1830 for slave in get_bond_slaves_of_pif(pif):
1831 s = configure_physical_interface(slave)
1832 s.write("MASTER=%(device)s\n" % pifrec)
1833 s.write("SLAVE=yes\n")
1837 # The bond option defaults
1839 "mode": "balance-slb",
1846 # override defaults with values from other-config whose keys being with "bond-"
1847 overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
1848 overrides = map(lambda (key,val): (key[5:], val), overrides)
1849 bond_options.update(overrides)
1851 # write the bond options to ifcfg-bondX
1852 f.write('BONDING_OPTS="')
1853 for (name,val) in bond_options.items():
1854 f.write("%s=%s " % (name,val))
1858 def configure_vlan_interface(pif):
1859 """Write the configuration for a VLAN interface.
1861 Writes the configuration file for the VLAN interface described by
1862 the pif object. Handles writing the configuration for the master
1863 interface if necessary.
1865 Returns the open file handle for the VLAN interface configuration
1869 slave = configure_pif(get_vlan_slave_of_pif(pif))
1872 f = open_pif_ifcfg(pif)
1873 f.write("VLAN=yes\n")
1874 f.attach_child(slave)
1878 def configure_pif(pif):
1879 """Write the configuration for a PIF object.
1881 Writes the configuration file the PIF and all dependent
1882 interfaces (bond slaves and VLAN masters etc).
1884 Returns the open file handle for the interface configuration file.
1887 pifrec = db.get_pif_record(pif)
1889 if pifrec['VLAN'] != '-1':
1890 f = configure_vlan_interface(pif)
1891 elif len(pifrec['bond_master_of']) != 0:
1892 f = configure_bond_interface(pif)
1894 f = configure_physical_interface(pif)
1896 bridge = bridge_name(pif)
1898 f.write("BRIDGE=%s\n" % bridge)
1902 def unconfigure_pif(pif):
1903 """Clear up the files created by configure_pif"""
1904 f = open_pif_ifcfg(pif)
1905 log("Unlinking stale file %s" % f.path())
1909 if __name__ == "__main__":
1915 err = traceback.format_exception(*ex)
1919 if not debug_mode():