3 # Copyright (c) 2008,2009 Citrix Systems, Inc. All rights reserved.
4 # Copyright (c) 2009 Nicira Networks.
8 %(command-name)s --session <SESSION-REF> --pif <PIF-REF> [up|down|rewrite]
9 %(command-name)s --force <BRIDGE> [up|down|rewrite <CONFIG>]
10 %(command-name)s --force all down
13 <CONFIG> = --device=<INTERFACE> --mode=dhcp
14 <CONFIG> = --device=<INTERFACE> --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
17 --session A session reference to use to access the xapi DB
18 --pif A PIF reference.
19 --force-interface An interface name. Mutually exclusive with --session/--pif.
21 Either both --session and --pif or just --pif-uuid.
23 <ACTION> is either "up" or "down" or "rewrite"
27 # Undocumented parameters for test & dev:
29 # --output-directory=<DIR> Write configuration to <DIR>. Also disables actually
30 # raising/lowering the interfaces
31 # --pif-uuid A PIF UUID, use instead of --session/--pif.
36 # 1. Every pif belongs to exactly one network
37 # 2. Every network has zero or one pifs
38 # 3. A network may have an associated bridge, allowing vifs to be attached
39 # 4. A network may be bridgeless (there's no point having a bridge over a storage pif)
41 # XXX: --force-interface=all down
43 # XXX: --force-interface rewrite
45 # XXX: Sometimes this leaves "orphaned" datapaths, e.g. a datapath whose
46 # only port is the local port. Should delete those.
48 # XXX: This can leave crud in ovs-vswitchd.conf in this scenario:
49 # - Create bond in XenCenter.
50 # - Create VLAN on bond in XenCenter.
51 # - Attempt to delete bond in XenCenter (this will fail because there
52 # is a VLAN on the bond, although the error may not be reported
53 # until the next step)
54 # - Delete VLAN in XenCenter.
55 # - Delete bond in XenCenter.
56 # At this point there will still be some configuration data for the bond
57 # or the VLAN in ovs-vswitchd.conf.
60 import os, sys, getopt, time, signal
66 from xml.dom.minidom import getDOMImplementation
67 from xml.dom.minidom import parse as parseXML
69 output_directory = None
74 vswitch_state_dir = "/var/lib/openvswitch/"
75 dbcache_file = vswitch_state_dir + "dbcache"
77 class Usage(Exception):
78 def __init__(self, msg):
79 Exception.__init__(self)
82 class Error(Exception):
83 def __init__(self, msg):
84 Exception.__init__(self)
87 class ConfigurationFile(object):
88 """Write a file, tracking old and new versions.
90 Supports writing a new version of a file and applying and
91 reverting those changes.
94 __STATE = {"OPEN":"OPEN",
95 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
96 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
98 def __init__(self, fname, path="/etc/sysconfig/network-scripts"):
100 self.__state = self.__STATE['OPEN']
105 dirname = output_directory
109 self.__path = os.path.join(dirname, fname)
110 self.__oldpath = os.path.join(dirname, "." + fname + ".xapi-old")
111 self.__newpath = os.path.join(dirname, "." + fname + ".xapi-new")
112 self.__unlink = False
114 self.__f = open(self.__newpath, "w")
116 def attach_child(self, child):
117 self.__children.append(child)
124 return open(self.path()).readlines()
128 def write(self, args):
129 if self.__state != self.__STATE['OPEN']:
130 raise Error("Attempt to write to file in state %s" % self.__state)
134 if self.__state != self.__STATE['OPEN']:
135 raise Error("Attempt to unlink file in state %s" % self.__state)
138 self.__state = self.__STATE['NOT-APPLIED']
141 if self.__state != self.__STATE['OPEN']:
142 raise Error("Attempt to close file in state %s" % self.__state)
145 self.__state = self.__STATE['NOT-APPLIED']
148 if self.__state != self.__STATE['NOT-APPLIED']:
149 raise Error("Attempt to compare file in state %s" % self.__state)
154 if self.__state != self.__STATE['NOT-APPLIED']:
155 raise Error("Attempt to apply configuration from state %s" % self.__state)
157 for child in self.__children:
160 log("Applying changes to %s configuration" % self.__fname)
162 # Remove previous backup.
163 if os.access(self.__oldpath, os.F_OK):
164 os.unlink(self.__oldpath)
166 # Save current configuration.
167 if os.access(self.__path, os.F_OK):
168 os.link(self.__path, self.__oldpath)
169 os.unlink(self.__path)
171 # Apply new configuration.
172 assert(os.path.exists(self.__newpath))
173 if not self.__unlink:
174 os.link(self.__newpath, self.__path)
176 pass # implicit unlink of original file
178 # Remove temporary file.
179 os.unlink(self.__newpath)
181 self.__state = self.__STATE['APPLIED']
184 if self.__state != self.__STATE['APPLIED']:
185 raise Error("Attempt to revert configuration from state %s" % self.__state)
187 for child in self.__children:
190 log("Reverting changes to %s configuration" % self.__fname)
192 # Remove existing new configuration
193 if os.access(self.__newpath, os.F_OK):
194 os.unlink(self.__newpath)
196 # Revert new configuration.
197 if os.access(self.__path, os.F_OK):
198 os.link(self.__path, self.__newpath)
199 os.unlink(self.__path)
201 # Revert to old configuration.
202 if os.access(self.__oldpath, os.F_OK):
203 os.link(self.__oldpath, self.__path)
204 os.unlink(self.__oldpath)
206 # Leave .*.xapi-new as an aid to debugging.
208 self.__state = self.__STATE['REVERTED']
211 if self.__state != self.__STATE['APPLIED']:
212 raise Error("Attempt to commit configuration from state %s" % self.__state)
214 for child in self.__children:
217 log("Committing changes to %s configuration" % self.__fname)
219 if os.access(self.__oldpath, os.F_OK):
220 os.unlink(self.__oldpath)
221 if os.access(self.__newpath, os.F_OK):
222 os.unlink(self.__newpath)
224 self.__state = self.__STATE['COMMITTED']
227 return output_directory is not None
231 print >>sys.stderr, s
235 def check_allowed(pif):
236 pifrec = db.get_pif_record(pif)
238 f = open("/proc/ardence")
239 macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
241 if len(macline) == 1:
242 p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
243 if p.match(macline[0]):
244 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
250 def interface_exists(i):
251 return os.path.exists("/sys/class/net/" + i)
253 def get_netdev_mac(device):
255 return read_first_line_of_file("/sys/class/net/%s/address" % device)
257 # Probably no such device.
260 def get_netdev_tx_queue_len(device):
262 return int(read_first_line_of_file("/sys/class/net/%s/tx_queue_len"
265 # Probably no such device.
268 def get_netdev_by_mac(mac):
269 for device in os.listdir("/sys/class/net"):
270 dev_mac = get_netdev_mac(device)
271 if (dev_mac and mac.lower() == dev_mac.lower() and
272 get_netdev_tx_queue_len(device)):
277 # Helper functions for encoding/decoding database attributes to/from XML.
279 def str_to_xml(xml, parent, tag, val):
280 e = xml.createElement(tag)
281 parent.appendChild(e)
282 v = xml.createTextNode(val)
285 def getText(nodelist):
287 for node in nodelist:
288 if node.nodeType == node.TEXT_NODE:
291 return getText(n.childNodes).strip()
294 def bool_to_xml(xml, parent, tag, val):
296 str_to_xml(xml, parent, tag, "True")
298 str_to_xml(xml, parent, tag, "False")
299 def bool_from_xml(n):
306 raise Error("Unknown boolean value %s" % s);
308 def strlist_to_xml(xml, parent, ltag, itag, val):
309 e = xml.createElement(ltag)
310 parent.appendChild(e)
312 c = xml.createElement(itag)
314 cv = xml.createTextNode(v)
316 def strlist_from_xml(n, ltag, itag):
318 for n in n.childNodes:
319 if n.nodeName == itag:
320 ret.append(str_from_xml(n))
323 def otherconfig_to_xml(xml, parent, val, attrs):
324 otherconfig = xml.createElement("other_config")
325 parent.appendChild(otherconfig)
326 for n,v in val.items():
328 raise Error("Unknown other-config attribute: %s" % n)
329 str_to_xml(xml, otherconfig, n, v)
330 def otherconfig_from_xml(n, attrs):
332 for n in n.childNodes:
333 if n.nodeName in attrs:
334 ret[n.nodeName] = str_from_xml(n)
338 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
340 # Each object is defined by a dictionary mapping an attribute name in
341 # the xapi database to a tuple containing two items:
342 # - a function which takes this attribute and encodes it as XML.
343 # - a function which takes XML and decocdes it into a value.
345 # other-config attributes are specified as a simple array of strings
348 VLAN_XML_TAG = "vlan"
349 BOND_XML_TAG = "bond"
350 NETWORK_XML_TAG = "network"
352 ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
354 PIF_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
355 'management': (bool_to_xml,bool_from_xml),
356 'network': (str_to_xml,str_from_xml),
357 'device': (str_to_xml,str_from_xml),
358 'bond_master_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
359 lambda n: strlist_from_xml(n, 'bond_master_of', 'slave')),
360 'bond_slave_of': (str_to_xml,str_from_xml),
361 'VLAN': (str_to_xml,str_from_xml),
362 'VLAN_master_of': (str_to_xml,str_from_xml),
363 'VLAN_slave_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
364 lambda n: strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
365 'ip_configuration_mode': (str_to_xml,str_from_xml),
366 'IP': (str_to_xml,str_from_xml),
367 'netmask': (str_to_xml,str_from_xml),
368 'gateway': (str_to_xml,str_from_xml),
369 'DNS': (str_to_xml,str_from_xml),
370 'MAC': (str_to_xml,str_from_xml),
371 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, PIF_OTHERCONFIG_ATTRS),
372 lambda n: otherconfig_from_xml(n, PIF_OTHERCONFIG_ATTRS)),
374 # Special case: We write the current value
375 # PIF.currently-attached to the cache but since it will
376 # not be valid when we come to use the cache later
377 # (i.e. after a reboot) we always read it as False.
378 'currently_attached': (bool_to_xml, lambda n: False),
381 PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
382 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
383 ETHTOOL_OTHERCONFIG_ATTRS
385 VLAN_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
386 'tagged_PIF': (str_to_xml,str_from_xml),
387 'untagged_PIF': (str_to_xml,str_from_xml),
390 BOND_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
391 'master': (str_to_xml,str_from_xml),
392 'slaves': (lambda x, p, t, v: strlist_to_xml(x, p, 'slaves', 'slave', v),
393 lambda n: strlist_from_xml(n, 'slaves', 'slave')),
396 NETWORK_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
397 'bridge': (str_to_xml,str_from_xml),
398 'PIFs': (lambda x, p, t, v: strlist_to_xml(x, p, 'PIFs', 'PIF', v),
399 lambda n: strlist_from_xml(n, 'PIFs', 'PIF')),
400 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, NETWORK_OTHERCONFIG_ATTRS),
401 lambda n: otherconfig_from_xml(n, NETWORK_OTHERCONFIG_ATTRS)),
404 NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + ETHTOOL_OTHERCONFIG_ATTRS
406 class DatabaseCache(object):
407 def __read_xensource_inventory(self):
408 filename = "/etc/xensource-inventory"
409 f = open(filename, "r")
410 lines = [x.strip("\n") for x in f.readlines()]
413 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
414 defs = [ (a, b.strip("'")) for (a,b) in defs ]
417 def __pif_on_host(self,pif):
418 return self.__pifs.has_key(pif)
420 def __get_pif_records_from_xapi(self, session, host):
422 for (p,rec) in session.xenapi.PIF.get_all_records().items():
423 if rec['host'] != host:
427 self.__pifs[p][f] = rec[f]
428 self.__pifs[p]['other_config'] = {}
429 for f in PIF_OTHERCONFIG_ATTRS:
430 if not rec['other_config'].has_key(f): continue
431 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
433 def __get_vlan_records_from_xapi(self, session):
435 for v in session.xenapi.VLAN.get_all():
436 rec = session.xenapi.VLAN.get_record(v)
437 if not self.__pif_on_host(rec['untagged_PIF']):
441 self.__vlans[v][f] = rec[f]
443 def __get_bond_records_from_xapi(self, session):
445 for b in session.xenapi.Bond.get_all():
446 rec = session.xenapi.Bond.get_record(b)
447 if not self.__pif_on_host(rec['master']):
451 self.__bonds[b][f] = rec[f]
453 def __get_network_records_from_xapi(self, session):
455 for n in session.xenapi.network.get_all():
456 rec = session.xenapi.network.get_record(n)
457 self.__networks[n] = {}
458 for f in NETWORK_ATTRS:
459 self.__networks[n][f] = rec[f]
460 self.__networks[n]['other_config'] = {}
461 for f in NETWORK_OTHERCONFIG_ATTRS:
462 if not rec['other_config'].has_key(f): continue
463 self.__networks[n]['other_config'][f] = rec['other_config'][f]
465 def __to_xml(self, xml, parent, key, ref, rec, attrs):
466 """Encode a database object as XML"""
467 e = xml.createElement(key)
468 parent.appendChild(e)
470 e.setAttribute('ref', ref)
472 for n,v in rec.items():
477 raise Error("Unknown attribute %s" % n)
478 def __from_xml(self, e, attrs):
479 """Decode a database object from XML"""
480 ref = e.attributes['ref'].value
482 for n in e.childNodes:
483 if n.nodeName in attrs:
484 _,h = attrs[n.nodeName]
485 rec[n.nodeName] = h(n)
488 def __init__(self, session_ref=None, cache_file=None):
489 if session_ref and cache_file:
490 raise Error("can't specify session reference and cache file")
491 if cache_file == None:
492 session = XenAPI.xapi_local()
495 log("No session ref given on command line, logging in.")
496 session.xenapi.login_with_password("root", "")
498 session._session = session_ref
502 inventory = self.__read_xensource_inventory()
503 assert(inventory.has_key('INSTALLATION_UUID'))
504 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
506 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
508 self.__get_pif_records_from_xapi(session, host)
510 self.__get_vlan_records_from_xapi(session)
511 self.__get_bond_records_from_xapi(session)
512 self.__get_network_records_from_xapi(session)
515 session.xenapi.session.logout()
517 log("Loading xapi database cache from %s" % cache_file)
519 xml = parseXML(cache_file)
526 assert(len(xml.childNodes) == 1)
527 toplevel = xml.childNodes[0]
529 assert(toplevel.nodeName == "xenserver-network-configuration")
531 for n in toplevel.childNodes:
532 if n.nodeName == "#text":
534 elif n.nodeName == PIF_XML_TAG:
535 (ref,rec) = self.__from_xml(n, PIF_ATTRS)
536 self.__pifs[ref] = rec
537 elif n.nodeName == BOND_XML_TAG:
538 (ref,rec) = self.__from_xml(n, BOND_ATTRS)
539 self.__bonds[ref] = rec
540 elif n.nodeName == VLAN_XML_TAG:
541 (ref,rec) = self.__from_xml(n, VLAN_ATTRS)
542 self.__vlans[ref] = rec
543 elif n.nodeName == NETWORK_XML_TAG:
544 (ref,rec) = self.__from_xml(n, NETWORK_ATTRS)
545 self.__networks[ref] = rec
547 raise Error("Unknown XML element %s" % n.nodeName)
549 def save(self, cache_file):
551 xml = getDOMImplementation().createDocument(
552 None, "xenserver-network-configuration", None)
553 for (ref,rec) in self.__pifs.items():
554 self.__to_xml(xml, xml.documentElement, PIF_XML_TAG, ref, rec, PIF_ATTRS)
555 for (ref,rec) in self.__bonds.items():
556 self.__to_xml(xml, xml.documentElement, BOND_XML_TAG, ref, rec, BOND_ATTRS)
557 for (ref,rec) in self.__vlans.items():
558 self.__to_xml(xml, xml.documentElement, VLAN_XML_TAG, ref, rec, VLAN_ATTRS)
559 for (ref,rec) in self.__networks.items():
560 self.__to_xml(xml, xml.documentElement, NETWORK_XML_TAG, ref, rec,
563 f = open(cache_file, 'w')
564 f.write(xml.toprettyxml())
567 def get_pif_by_uuid(self, uuid):
568 pifs = map(lambda (ref,rec): ref,
569 filter(lambda (ref,rec): uuid == rec['uuid'],
570 self.__pifs.items()))
572 raise Error("Unknown PIF \"%s\"" % uuid)
574 raise Error("Non-unique PIF \"%s\"" % uuid)
578 def get_pifs_by_device(self, device):
579 return map(lambda (ref,rec): ref,
580 filter(lambda (ref,rec): rec['device'] == device,
581 self.__pifs.items()))
583 def get_pif_by_bridge(self, bridge):
584 networks = map(lambda (ref,rec): ref,
585 filter(lambda (ref,rec): rec['bridge'] == bridge,
586 self.__networks.items()))
587 if len(networks) == 0:
588 raise Error("No matching network \"%s\"")
591 for network in networks:
592 nwrec = self.get_network_record(network)
593 for pif in nwrec['PIFs']:
594 pifrec = self.get_pif_record(pif)
596 raise Error("Multiple PIFs on host for network %s" % (bridge))
599 raise Error("No PIF on host for network %s" % (bridge))
602 def get_pif_record(self, pif):
603 if self.__pifs.has_key(pif):
604 return self.__pifs[pif]
605 raise Error("Unknown PIF \"%s\" (get_pif_record)" % pif)
606 def get_all_pifs(self):
608 def pif_exists(self, pif):
609 return self.__pifs.has_key(pif)
611 def get_management_pif(self):
612 """ Returns the management pif on host
614 all = self.get_all_pifs()
616 pifrec = self.get_pif_record(pif)
617 if pifrec['management']: return pif
620 def get_network_record(self, network):
621 if self.__networks.has_key(network):
622 return self.__networks[network]
623 raise Error("Unknown network \"%s\"" % network)
624 def get_all_networks(self):
625 return self.__networks
627 def get_bond_record(self, bond):
628 if self.__bonds.has_key(bond):
629 return self.__bonds[bond]
633 def get_vlan_record(self, vlan):
634 if self.__vlans.has_key(vlan):
635 return self.__vlans[vlan]
639 def bridge_name(pif):
640 """Return the bridge name associated with pif, or None if network is bridgeless"""
641 pifrec = db.get_pif_record(pif)
642 nwrec = db.get_network_record(pifrec['network'])
645 # TODO: sanity check that nwrec['bridgeless'] != 'true'
646 return nwrec['bridge']
648 # TODO: sanity check that nwrec['bridgeless'] == 'true'
651 def interface_name(pif):
652 """Construct an interface name from the given PIF record."""
654 pifrec = db.get_pif_record(pif)
656 if pifrec['VLAN'] == '-1':
657 return pifrec['device']
659 return "%(device)s.%(VLAN)s" % pifrec
661 def datapath_name(pif):
662 """Return the OpenFlow datapath name associated with pif.
663 For a non-VLAN PIF, the datapath name is the bridge name.
664 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
665 (xapi will create a datapath named with the bridge name even though we won't
670 pifrec = db.get_pif_record(pif)
672 if pifrec['VLAN'] == '-1':
673 return bridge_name(pif)
675 return bridge_name(get_vlan_slave_of_pif(pif))
678 """Return the the name of the network device that carries the
679 IP configuration (if any) associated with pif.
680 The ipdev name is the same as the bridge name.
683 pifrec = db.get_pif_record(pif)
684 return bridge_name(pif)
686 def get_physdev_pifs(pif):
687 """Return the PIFs for the physical network device(s) associated with pif.
688 For a VLAN PIF, this is the VLAN slave's physical device PIF.
689 For a bond master PIF, these are the bond slave PIFs.
690 For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
692 pifrec = db.get_pif_record(pif)
694 if pifrec['VLAN'] != '-1':
695 return get_physdev_pifs(get_vlan_slave_of_pif(pif))
696 elif len(pifrec['bond_master_of']) != 0:
697 return get_bond_slaves_of_pif(pif)
701 def get_physdev_names(pif):
702 """Return the name(s) of the physical network device(s) associated with pif.
703 For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
704 For a bond master PIF, the physical devices are the bond slaves.
705 For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
708 return [db.get_pif_record(phys)['device'] for phys in get_physdev_pifs(pif)]
710 def log_pif_action(action, pif):
711 pifrec = db.get_pif_record(pif)
713 rec['uuid'] = pifrec['uuid']
714 rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
715 rec['action'] = action
716 rec['interface-name'] = interface_name(pif)
717 if action == "rewrite":
718 rec['message'] = "Rewrite PIF %(uuid)s configuration" % rec
720 rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
721 log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % rec)
723 def get_bond_masters_of_pif(pif):
724 """Returns a list of PIFs which are bond masters of this PIF"""
726 pifrec = db.get_pif_record(pif)
728 bso = pifrec['bond_slave_of']
730 # bond-slave-of is currently a single reference but in principle a
731 # PIF could be a member of several bonds which are not
732 # concurrently attached. Be robust to this possibility.
733 if not bso or bso == "OpaqueRef:NULL":
735 elif not type(bso) == list:
738 bondrecs = [db.get_bond_record(bond) for bond in bso]
739 bondrecs = [rec for rec in bondrecs if rec]
741 return [bond['master'] for bond in bondrecs]
743 def get_bond_slaves_of_pif(pif):
744 """Returns a list of PIFs which make up the given bonded pif."""
746 pifrec = db.get_pif_record(pif)
748 bmo = pifrec['bond_master_of']
750 raise Error("Bond-master-of contains too many elements")
755 bondrec = db.get_bond_record(bmo[0])
757 raise Error("No bond record for bond master PIF")
759 return bondrec['slaves']
761 def get_vlan_slave_of_pif(pif):
762 """Find the PIF which is the VLAN slave of pif.
764 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
766 pifrec = db.get_pif_record(pif)
768 vlan = pifrec['VLAN_master_of']
769 if not vlan or vlan == "OpaqueRef:NULL":
770 raise Error("PIF is not a VLAN master")
772 vlanrec = db.get_vlan_record(vlan)
774 raise Error("No VLAN record found for PIF")
776 return vlanrec['tagged_PIF']
778 def get_vlan_masters_of_pif(pif):
779 """Returns a list of PIFs which are VLANs on top of the given pif."""
781 pifrec = db.get_pif_record(pif)
782 vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
783 return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])]
785 def interface_deconfigure_commands(interface):
786 # The use of [!0-9] keeps an interface of 'eth0' from matching
787 # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
789 return ['--del-match=bridge.*.port=%s' % interface,
790 '--del-match=bonding.%s.[!0-9]*' % interface,
791 '--del-match=bonding.*.slave=%s' % interface,
792 '--del-match=vlan.%s.[!0-9]*' % interface,
793 '--del-match=port.%s.[!0-9]*' % interface,
794 '--del-match=iface.%s.[!0-9]*' % interface]
796 def run_command(command):
797 log("Running command: " + ' '.join(command))
798 if os.spawnl(os.P_WAIT, command[0], *command) != 0:
799 log("Command failed: " + ' '.join(command))
803 def rename_netdev(old_name, new_name):
804 log("Changing the name of %s to %s" % (old_name, new_name))
805 run_command(['/sbin/ifconfig', old_name, 'down'])
806 if not run_command(['/sbin/ip', 'link', 'set', old_name,
808 raise Error("Could not rename %s to %s" % (old_name, new_name))
810 # Check whether 'pif' exists and has the correct MAC.
811 # If not, try to find a device with the correct MAC and rename it.
812 # 'already_renamed' is used to avoid infinite recursion.
813 def remap_pif(pif, already_renamed=[]):
814 pifrec = db.get_pif_record(pif)
815 device = pifrec['device']
818 # Is there a network device named 'device' at all?
819 device_exists = interface_exists(device)
821 # Yes. Does it have MAC 'mac'?
822 found_mac = get_netdev_mac(device)
823 if found_mac and mac.lower() == found_mac.lower():
824 # Yes, everything checks out the way we want. Nothing to do.
827 log("No network device %s" % device)
829 # What device has MAC 'mac'?
830 cur_device = get_netdev_by_mac(mac)
832 log("No network device has MAC %s" % mac)
835 # First rename 'device', if it exists, to get it out of the way
836 # for 'cur_device' to replace it.
838 rename_netdev(device, "dev%d" % random.getrandbits(24))
840 # Rename 'cur_device' to 'device'.
841 rename_netdev(cur_device, device)
843 def read_first_line_of_file(name):
846 file = open(name, 'r')
847 return file.readline().rstrip('\n')
852 def down_netdev(interface, deconfigure=True):
853 if not interface_exists(interface):
854 log("down_netdev: interface %s does not exist, ignoring" % interface)
858 pidfile_name = '/var/run/dhclient-%s.pid' % interface
860 os.kill(int(read_first_line_of_file(pidfile_name)), signal.SIGTERM)
864 # Remove dhclient pidfile.
866 os.remove(pidfile_name)
870 run_command(["/sbin/ifconfig", interface, '0.0.0.0'])
872 run_command(["/sbin/ifconfig", interface, 'down'])
874 def up_netdev(interface):
875 run_command(["/sbin/ifconfig", interface, 'up'])
877 def find_distinguished_pifs(pif):
878 """Returns the PIFs on host that own DNS and the default route.
879 The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
880 The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
882 Note: we prune out the bond master pif (if it exists).
883 This is because when we are called to bring up an interface with a bond master, it is implicit that
884 we should bring down that master."""
886 pifrec = db.get_pif_record(pif)
888 pifs = [ __pif for __pif in db.get_all_pifs() if
889 (not __pif in get_bond_masters_of_pif(pif)) ]
892 defaultroute_pif = None
894 # loop through all the pifs on this host looking for one with
895 # other-config:peerdns = true, and one with
896 # other-config:default-route=true
898 __pifrec = db.get_pif_record(__pif)
899 __oc = __pifrec['other_config']
900 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
901 if peerdns_pif == None:
904 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
905 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
906 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
907 if defaultroute_pif == None:
908 defaultroute_pif = __pif
910 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
911 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
913 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
914 if peerdns_pif == None:
915 peerdns_pif = management_pif
916 if defaultroute_pif == None:
917 defaultroute_pif = management_pif
919 return peerdns_pif, defaultroute_pif
921 def run_ethtool(device, oc):
922 # Run "ethtool -s" if there are any settings.
924 if oc.has_key('ethtool-speed'):
925 val = oc['ethtool-speed']
926 if val in ["10", "100", "1000"]:
927 settings += ['speed', val]
929 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
930 if oc.has_key('ethtool-duplex'):
931 val = oc['ethtool-duplex']
932 if val in ["10", "100", "1000"]:
933 settings += ['duplex', 'val']
935 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
936 if oc.has_key('ethtool-autoneg'):
937 val = oc['ethtool-autoneg']
938 if val in ["true", "on"]:
939 settings += ['autoneg', 'on']
940 elif val in ["false", "off"]:
941 settings += ['autoneg', 'off']
943 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
945 run_command(['/sbin/ethtool', '-s', device] + settings)
947 # Run "ethtool -K" if there are any offload settings.
949 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
950 if oc.has_key("ethtool-" + opt):
951 val = oc["ethtool-" + opt]
952 if val in ["true", "on"]:
953 offload += [opt, 'on']
954 elif val in ["false", "off"]:
955 offload += [opt, 'off']
957 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
959 run_command(['/sbin/ethtool', '-K', device] + offload)
962 if oc.has_key('mtu'):
964 int(oc['mtu']) # Check that the value is an integer
965 return ['mtu', oc['mtu']]
966 except ValueError, x:
967 log("Invalid value for mtu = %s" % mtu)
970 def configure_local_port(pif):
971 pifrec = db.get_pif_record(pif)
972 datapath = datapath_name(pif)
973 ipdev = ipdev_name(pif)
975 nw = pifrec['network']
976 nwrec = db.get_network_record(nw)
978 pif_oc = pifrec['other_config']
979 nw_oc = nwrec['other_config']
981 # IP (except DHCP) and MTU.
982 ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up']
984 if pifrec['ip_configuration_mode'] == "DHCP":
986 elif pifrec['ip_configuration_mode'] == "Static":
987 ifconfig_argv += [pifrec['IP']]
988 ifconfig_argv += ['netmask', pifrec['netmask']]
989 gateway = pifrec['gateway']
990 elif pifrec['ip_configuration_mode'] == "None":
994 raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode'])
995 ifconfig_argv += mtu_setting(nw_oc)
996 run_command(ifconfig_argv)
998 (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif)
1001 if peerdns_pif == pif:
1002 f = ConfigurationFile('resolv.conf', "/etc")
1003 if pif_oc.has_key('domain'):
1004 f.write("search %s\n" % pif_oc['domain'])
1005 for dns in pifrec['DNS'].split(","):
1006 f.write("nameserver %s\n" % dns)
1012 if defaultroute_pif == pif and gateway != '':
1013 run_command(['/sbin/ip', 'route', 'replace', 'default',
1014 'via', gateway, 'dev', ipdev])
1015 if nw_oc.has_key('static-routes'):
1016 for line in nw_oc['static-routes'].split(','):
1017 network, masklen, gateway = line.split('/')
1018 run_command(['/sbin/ip', 'route', 'add',
1019 '%s/%s' % (network, masklen), 'via', gateway,
1023 run_ethtool(ipdev, nw_oc)
1026 if pifrec['ip_configuration_mode'] == "DHCP":
1028 print "Determining IP information for %s..." % ipdev,
1029 argv = ['/sbin/dhclient', '-q',
1030 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev,
1031 '-pf', '/var/run/dhclient-%s.pid' % ipdev,
1033 if run_command(argv):
1038 def configure_physdev(pif):
1039 pifrec = db.get_pif_record(pif)
1040 device = pifrec['device']
1041 oc = pifrec['other_config']
1043 run_command(['/sbin/ifconfig', device, 'up'] + mtu_setting(oc))
1044 run_ethtool(device, oc)
1046 def modify_config(commands):
1047 run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer',
1048 '-F', '/etc/ovs-vswitchd.conf']
1049 + commands + ['-c'])
1050 run_command(['/sbin/service', 'vswitch', 'reload'])
1052 def is_bond_pif(pif):
1053 pifrec = db.get_pif_record(pif)
1054 return len(pifrec['bond_master_of']) != 0
1056 def configure_bond(pif):
1057 pifrec = db.get_pif_record(pif)
1058 interface = interface_name(pif)
1059 ipdev = ipdev_name(pif)
1060 datapath = datapath_name(pif)
1061 physdev_names = get_physdev_names(pif)
1063 argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
1064 argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
1065 for slave in physdev_names]
1066 argv += ['--add=bonding.%s.fake-iface=true' % interface]
1068 if pifrec['MAC'] != "":
1069 argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
1073 "mode": "balance-slb",
1079 # override defaults with values from other-config whose keys
1080 # being with "bond-"
1081 oc = pifrec['other_config']
1082 overrides = filter(lambda (key,val):
1083 key.startswith("bond-"), oc.items())
1084 overrides = map(lambda (key,val): (key[5:], val), overrides)
1085 bond_options.update(overrides)
1086 for (name,val) in bond_options.items():
1087 argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
1091 pifrec = db.get_pif_record(pif)
1093 bridge = bridge_name(pif)
1094 interface = interface_name(pif)
1095 ipdev = ipdev_name(pif)
1096 datapath = datapath_name(pif)
1097 physdev_pifs = get_physdev_pifs(pif)
1098 physdev_names = get_physdev_names(pif)
1100 if pifrec['VLAN'] != '-1':
1101 vlan_slave = get_vlan_slave_of_pif(pif)
1102 if vlan_slave and is_bond_pif(vlan_slave):
1103 bond_master = vlan_slave
1104 elif is_bond_pif(pif):
1109 bond_slaves = get_bond_slaves_of_pif(bond_master)
1112 bond_masters = get_bond_masters_of_pif(pif)
1114 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1115 # files up-to-date, even though we don't use them or need them.
1116 f = configure_pif(pif)
1117 mode = pifrec['ip_configuration_mode']
1119 log("Configuring %s using %s configuration" % (bridge, mode))
1120 br = open_network_ifcfg(pif)
1121 configure_network(pif, br)
1125 log("Configuring %s using %s configuration" % (interface, mode))
1126 configure_network(pif, f)
1128 for master in bond_masters:
1129 master_bridge = bridge_name(master)
1130 removed = unconfigure_pif(master)
1131 f.attach_child(removed)
1133 removed = open_network_ifcfg(master)
1134 log("Unlinking stale file %s" % removed.path())
1136 f.attach_child(removed)
1138 # /etc/xensource/scripts/vif needs to know where to add VIFs.
1140 if not os.path.exists(vswitch_state_dir):
1141 os.mkdir(vswitch_state_dir)
1142 br = ConfigurationFile("br-%s" % bridge, vswitch_state_dir)
1143 br.write("VLAN_SLAVE=%s\n" % datapath)
1144 br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
1148 # Update all configuration files (both ours and Centos's).
1152 # Check the MAC address of each network device and remap if
1153 # necessary to make names match our expectations.
1154 for physdev_pif in physdev_pifs:
1155 remap_pif(physdev_pif)
1157 # "ifconfig down" the network device and delete its IP address, etc.
1159 for physdev_name in physdev_names:
1160 down_netdev(physdev_name)
1162 # If we are bringing up a bond, remove IP addresses from the
1163 # slaves (because we are implicitly being asked to take them down).
1165 # Conversely, if we are bringing up an interface that has bond
1166 # masters, remove IP addresses from the bond master (because we
1167 # are implicitly being asked to take it down).
1168 for bond_pif in bond_slaves + bond_masters:
1169 run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0'])
1171 # Remove all keys related to pif and any bond masters linked to PIF.
1172 del_ports = [ipdev] + physdev_names + bond_masters
1173 if vlan_slave and bond_master:
1174 del_ports += [interface_name(bond_master)]
1176 # What ports do we need to add to the datapath?
1178 # We definitely need the ipdev, and ordinarily we want the
1179 # physical devices too, but for bonds we need the bond as bridge
1181 add_ports = [ipdev, datapath]
1183 add_ports += physdev_names
1185 add_ports += [interface_name(bond_master)]
1187 # What ports do we need to delete?
1189 # - All the ports that we add, to avoid duplication and to drop
1190 # them from another datapath in case they're misassigned.
1192 # - The physical devices, since they will either be in add_ports
1193 # or added to the bonding device (see below).
1195 # - The bond masters for pif. (Ordinarily pif shouldn't have any
1196 # bond masters. If it does then interface-reconfigure is
1197 # implicitly being asked to take them down.)
1198 del_ports = add_ports + physdev_names + bond_masters
1200 # What networks does this datapath carry?
1202 # - The network corresponding to the datapath's PIF.
1204 # - The networks corresponding to any VLANs attached to the
1207 for nwpif in db.get_pifs_by_device({'device': pifrec['device']}):
1208 net = db.get_pif_record(nwpif)['network']
1209 network_uuids += [db.get_network_record(net)['uuid']]
1211 # Bring up bond slaves early, because ovs-vswitchd initially
1212 # enables or disables bond slaves based on whether carrier is
1213 # detected when they are added, and a network device that is down
1214 # always reports "no carrier".
1215 bond_slave_physdev_pifs = []
1216 for slave in bond_slaves:
1217 bond_slave_physdev_pifs += get_physdev_pifs(slave)
1218 for slave_physdev_pif in set(bond_slave_physdev_pifs):
1219 configure_physdev(slave_physdev_pif)
1221 # Now modify the ovs-vswitchd config file.
1223 for port in set(del_ports):
1224 argv += interface_deconfigure_commands(port)
1225 for port in set(add_ports):
1226 argv += ['--add=bridge.%s.port=%s' % (datapath, port)]
1228 argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
1229 argv += ['--add=iface.%s.internal=true' % (ipdev)]
1231 # xapi creates a bridge by the name of the ipdev and requires
1232 # that the IP address will be on it. We need to delete this
1233 # bridge because we need that device to be a member of our
1235 argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev]
1237 # xapi insists that its attempts to create the bridge succeed,
1238 # so force that to happen.
1239 argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
1242 os.unlink("%s/br-%s" % (vswitch_state_dir, bridge))
1245 argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath]
1246 argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid)
1247 for uuid in set(network_uuids)]
1249 argv += configure_bond(bond_master)
1252 # Bring up VLAN slave, plus physical devices other than bond
1253 # slaves (which we brought up earlier).
1255 up_netdev(ipdev_name(vlan_slave))
1256 for physdev_pif in set(physdev_pifs) - set(bond_slave_physdev_pifs):
1257 configure_physdev(physdev_pif)
1259 # Configure network device for local port.
1260 configure_local_port(pif)
1262 # Update /etc/issue (which contains the IP address of the management interface)
1263 os.system("/sbin/update-issue")
1266 # There seems to be a race somewhere: without this sleep, using
1267 # XenCenter to create a bond that becomes the management interface
1268 # fails with "The underlying connection was closed: A connection that
1269 # was expected to be kept alive was closed by the server." on every
1270 # second or third try, even though /var/log/messages doesn't show
1273 # The race is probably present even without vswitch, but bringing up a
1274 # bond without vswitch involves a built-in pause of 10 seconds or more
1275 # to wait for the bond to transition from learning to forwarding state.
1278 def action_down(pif):
1279 rec = db.get_pif_record(pif)
1280 interface = interface_name(pif)
1281 bridge = bridge_name(pif)
1282 ipdev = ipdev_name(pif)
1284 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1285 # files up-to-date, even though we don't use them or need them.
1286 f = unconfigure_pif(pif)
1288 br = open_network_ifcfg(pif)
1289 log("Unlinking stale file %s" % br.path())
1296 log("action_down failed to apply changes: %s" % e.msg)
1301 if rec['VLAN'] != '-1':
1302 # Get rid of the VLAN device itself.
1304 argv += interface_deconfigure_commands(ipdev)
1306 # If the VLAN's slave is attached, stop here.
1307 slave = get_vlan_slave_of_pif(pif)
1308 if db.get_pif_record(slave)['currently_attached']:
1309 log("VLAN slave is currently attached")
1313 # If the VLAN's slave has other VLANs that are attached, stop here.
1314 masters = get_vlan_masters_of_pif(slave)
1316 if m != pif and db.get_pif_record(m)['currently_attached']:
1317 log("VLAN slave has other master %s" % interface_naem(m))
1321 # Otherwise, take down the VLAN's slave too.
1322 log("No more masters, bring down vlan slave %s" % interface_name(slave))
1325 # Stop here if this PIF has attached VLAN masters.
1326 vlan_masters = get_vlan_masters_of_pif(pif)
1327 log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters]))
1328 for m in vlan_masters:
1329 if db.get_pif_record(m)['currently_attached']:
1330 log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m)))
1333 # pif is now either a bond or a physical device which needs to be
1334 # brought down. pif might have changed so re-check all its attributes.
1335 rec = db.get_pif_record(pif)
1336 interface = interface_name(pif)
1337 bridge = bridge_name(pif)
1338 ipdev = ipdev_name(pif)
1341 bond_slaves = get_bond_slaves_of_pif(pif)
1342 log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves]))
1343 for slave in bond_slaves:
1344 slave_interface = interface_name(slave)
1345 log("bring down bond slave %s" % slave_interface)
1346 argv += interface_deconfigure_commands(slave_interface)
1347 down_netdev(slave_interface)
1349 argv += interface_deconfigure_commands(ipdev)
1352 argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)]
1353 argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface]
1356 def action_rewrite(pif):
1357 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1358 # files up-to-date, even though we don't use them or need them.
1359 pifrec = db.get_pif_record(pif)
1360 f = configure_pif(pif)
1361 interface = interface_name(pif)
1362 bridge = bridge_name(pif)
1363 mode = pifrec['ip_configuration_mode']
1365 log("Configuring %s using %s configuration" % (bridge, mode))
1366 br = open_network_ifcfg(pif)
1367 configure_network(pif, br)
1371 log("Configuring %s using %s configuration" % (interface, mode))
1372 configure_network(pif, f)
1378 log("failed to apply changes: %s" % e.msg)
1382 # We have no code of our own to run here.
1385 def main(argv=None):
1386 global output_directory, management_pif
1392 force_interface = None
1393 force_management = False
1401 longops = [ "output-directory=",
1402 "pif=", "pif-uuid=",
1408 "device=", "mode=", "ip=", "netmask=", "gateway=",
1410 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1411 except getopt.GetoptError, msg:
1414 force_rewrite_config = {}
1417 if o == "--output-directory":
1418 output_directory = a
1421 elif o == "--pif-uuid":
1423 elif o == "--session":
1425 elif o == "--force-interface" or o == "--force":
1427 elif o == "--management":
1428 force_management = True
1429 elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1430 force_rewrite_config[o[2:]] = a
1431 elif o == "-h" or o == "--help":
1432 print __doc__ % {'command-name': os.path.basename(argv[0])}
1435 if not debug_mode():
1436 syslog.openlog(os.path.basename(argv[0]))
1437 log("Called as " + str.join(" ", argv))
1439 raise Usage("Required option <action> not present")
1441 raise Usage("Too many arguments")
1444 # backwards compatibility
1445 if action == "rewrite-configuration": action = "rewrite"
1447 if output_directory and ( session or pif ):
1448 raise Usage("--session/--pif cannot be used with --output-directory")
1449 if ( session or pif ) and pif_uuid:
1450 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1451 if ( session and not pif ) or ( not session and pif ):
1452 raise Usage("--session and --pif must be used together.")
1453 if force_interface and ( session or pif or pif_uuid ):
1454 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1455 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1456 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1460 log("Force interface %s %s" % (force_interface, action))
1462 if action == "rewrite":
1463 action_force_rewrite(force_interface, force_rewrite_config)
1465 db = DatabaseCache(cache_file=dbcache_file)
1466 pif = db.get_pif_by_bridge(force_interface)
1467 management_pif = db.get_management_pif()
1471 elif action == "down":
1474 raise Usage("Unknown action %s" % action)
1476 db = DatabaseCache(session_ref=session)
1479 pif = db.get_pif_by_uuid(pif_uuid)
1482 raise Usage("No PIF given")
1484 if force_management:
1485 # pif is going to be the management pif
1486 management_pif = pif
1488 # pif is not going to be the management pif.
1489 # Search DB cache for pif on same host with management=true
1490 pifrec = db.get_pif_record(pif)
1491 management_pif = db.get_management_pif()
1493 log_pif_action(action, pif)
1495 if not check_allowed(pif):
1500 elif action == "down":
1502 elif action == "rewrite":
1505 raise Usage("Unknown action %s" % action)
1508 pifrec = db.get_pif_record(pif)
1509 db.save(dbcache_file)
1512 print >>sys.stderr, err.msg
1513 print >>sys.stderr, "For help use --help."
1521 # The following code allows interface-reconfigure to keep Centos
1522 # network configuration files up-to-date, even though the vswitch
1523 # never uses them. In turn, that means that "rpm -e vswitch" does not
1524 # have to update any configuration files.
1526 def configure_ethtool(oc, f):
1527 # Options for "ethtool -s"
1529 setting_opts = ["autoneg", "speed", "duplex"]
1530 # Options for "ethtool -K"
1532 offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
1534 for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
1535 val = oc["ethtool-" + opt]
1537 if opt in ["speed"]:
1538 if val in ["10", "100", "1000"]:
1539 val = "speed " + val
1541 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
1543 elif opt in ["duplex"]:
1544 if val in ["half", "full"]:
1545 val = "duplex " + val
1547 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
1549 elif opt in ["autoneg"] + offload_opts:
1550 if val in ["true", "on"]:
1552 elif val in ["false", "off"]:
1555 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
1558 if opt in setting_opts:
1559 if val and settings:
1560 settings = settings + " " + val
1563 elif opt in offload_opts:
1565 offload = offload + " " + val
1570 f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
1572 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
1574 def configure_mtu(oc, f):
1575 if not oc.has_key('mtu'):
1579 mtu = int(oc['mtu'])
1580 f.write("MTU=%d\n" % mtu)
1581 except ValueError, x:
1582 log("Invalid value for mtu = %s" % mtu)
1584 def configure_static_routes(interface, oc, f):
1585 """Open a route-<interface> file for static routes.
1587 Opens the static routes configuration file for interface and writes one
1588 line for each route specified in the network's other config "static-routes" value.
1590 interface ( RO): xenbr1
1591 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1593 Then route-xenbr1 should be
1594 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1595 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1597 fname = "route-%s" % interface
1598 if oc.has_key('static-routes'):
1599 # The key is present - extract comma seperates entries
1600 lines = oc['static-routes'].split(',')
1602 # The key is not present, i.e. there are no static routes
1605 child = ConfigurationFile(fname)
1606 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1607 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
1611 network, masklen, gateway = l.split('/')
1612 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1614 f.attach_child(child)
1617 except ValueError, e:
1618 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1620 def __open_ifcfg(interface):
1621 """Open a network interface configuration file.
1623 Opens the configuration file for interface, writes a header and
1624 common options and returns the file object.
1626 fname = "ifcfg-%s" % interface
1627 f = ConfigurationFile(fname)
1629 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1630 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1631 f.write("XEMANAGED=yes\n")
1632 f.write("DEVICE=%s\n" % interface)
1633 f.write("ONBOOT=no\n")
1637 def open_network_ifcfg(pif):
1638 bridge = bridge_name(pif)
1639 interface = interface_name(pif)
1641 return __open_ifcfg(bridge)
1643 return __open_ifcfg(interface)
1646 def open_pif_ifcfg(pif):
1647 pifrec = db.get_pif_record(pif)
1649 log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
1651 f = __open_ifcfg(interface_name(pif))
1653 if pifrec.has_key('other_config'):
1654 configure_ethtool(pifrec['other_config'], f)
1655 configure_mtu(pifrec['other_config'], f)
1659 def configure_network(pif, f):
1660 """Write the configuration file for a network.
1662 Writes configuration derived from the network object into the relevant
1663 ifcfg file. The configuration file is passed in, but if the network is
1664 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1666 This routine may also write ifcfg files of the networks corresponding to other PIFs
1667 in order to maintain consistency.
1670 pif: Opaque_ref of pif
1671 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1674 pifrec = db.get_pif_record(pif)
1675 nw = pifrec['network']
1676 nwrec = db.get_network_record(nw)
1678 bridge = bridge_name(pif)
1679 interface = interface_name(pif)
1685 if nwrec.has_key('other_config'):
1686 configure_ethtool(nwrec['other_config'], f)
1687 configure_mtu(nwrec['other_config'], f)
1688 configure_static_routes(device, nwrec['other_config'], f)
1691 if pifrec.has_key('other_config'):
1692 oc = pifrec['other_config']
1694 if device == bridge:
1695 f.write("TYPE=Bridge\n")
1696 f.write("DELAY=0\n")
1697 f.write("STP=off\n")
1698 f.write("PIFDEV=%s\n" % interface_name(pif))
1700 if pifrec['ip_configuration_mode'] == "DHCP":
1701 f.write("BOOTPROTO=dhcp\n")
1702 f.write("PERSISTENT_DHCLIENT=yes\n")
1703 elif pifrec['ip_configuration_mode'] == "Static":
1704 f.write("BOOTPROTO=none\n")
1705 f.write("NETMASK=%(netmask)s\n" % pifrec)
1706 f.write("IPADDR=%(IP)s\n" % pifrec)
1707 f.write("GATEWAY=%(gateway)s\n" % pifrec)
1708 elif pifrec['ip_configuration_mode'] == "None":
1709 f.write("BOOTPROTO=none\n")
1711 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1713 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1714 ServerList = pifrec['DNS'].split(",")
1715 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1716 if oc and oc.has_key('domain'):
1717 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1719 # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1720 # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1721 # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1723 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1725 # Note: we prune out the bond master pif (if it exists).
1726 # This is because when we are called to bring up an interface with a bond master, it is implicit that
1727 # we should bring down that master.
1728 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1729 not __pif in get_bond_masters_of_pif(pif) ]
1730 other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1733 defaultroute_pif = None
1735 # loop through all the pifs on this host looking for one with
1736 # other-config:peerdns = true, and one with
1737 # other-config:default-route=true
1738 for __pif in pifs_on_host:
1739 __pifrec = db.get_pif_record(__pif)
1740 __oc = __pifrec['other_config']
1741 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1742 if peerdns_pif == None:
1745 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1746 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1747 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1748 if defaultroute_pif == None:
1749 defaultroute_pif = __pif
1751 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1752 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1754 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1755 if peerdns_pif == None:
1756 peerdns_pif = management_pif
1757 if defaultroute_pif == None:
1758 defaultroute_pif = management_pif
1760 # Update all the other network's ifcfg files and ensure consistency
1761 for __pif in other_pifs_on_host:
1762 __f = open_network_ifcfg(__pif)
1763 peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1764 lines = __f.readlines()
1766 if not peerdns_line_wanted in lines:
1767 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1769 if not line.lstrip().startswith('PEERDNS'):
1771 log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1772 __f.write(peerdns_line_wanted)
1777 # There is no need to change this ifcfg file. So don't attach_child.
1780 # ... and for this pif too
1781 f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1784 fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1785 for line in fnetwork.readlines():
1786 if line.lstrip().startswith('GATEWAY') :
1788 fnetwork.write(line)
1789 if defaultroute_pif:
1790 gatewaydev = bridge_name(defaultroute_pif)
1792 gatewaydev = interface_name(defaultroute_pif)
1793 fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1795 f.attach_child(fnetwork)
1800 def configure_physical_interface(pif):
1801 """Write the configuration for a physical interface.
1803 Writes the configuration file for the physical interface described by
1806 Returns the open file handle for the interface configuration file.
1809 pifrec = db.get_pif_record(pif)
1811 f = open_pif_ifcfg(pif)
1813 f.write("TYPE=Ethernet\n")
1814 f.write("HWADDR=%(MAC)s\n" % pifrec)
1818 def configure_bond_interface(pif):
1819 """Write the configuration for a bond interface.
1821 Writes the configuration file for the bond interface described by
1822 the pif object. Handles writing the configuration for the slave
1825 Returns the open file handle for the bond interface configuration
1829 pifrec = db.get_pif_record(pif)
1830 oc = pifrec['other_config']
1831 f = open_pif_ifcfg(pif)
1833 if pifrec['MAC'] != "":
1834 f.write("MACADDR=%s\n" % pifrec['MAC'])
1836 for slave in get_bond_slaves_of_pif(pif):
1837 s = configure_physical_interface(slave)
1838 s.write("MASTER=%(device)s\n" % pifrec)
1839 s.write("SLAVE=yes\n")
1843 # The bond option defaults
1845 "mode": "balance-slb",
1852 # override defaults with values from other-config whose keys being with "bond-"
1853 overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
1854 overrides = map(lambda (key,val): (key[5:], val), overrides)
1855 bond_options.update(overrides)
1857 # write the bond options to ifcfg-bondX
1858 f.write('BONDING_OPTS="')
1859 for (name,val) in bond_options.items():
1860 f.write("%s=%s " % (name,val))
1864 def configure_vlan_interface(pif):
1865 """Write the configuration for a VLAN interface.
1867 Writes the configuration file for the VLAN interface described by
1868 the pif object. Handles writing the configuration for the master
1869 interface if necessary.
1871 Returns the open file handle for the VLAN interface configuration
1875 slave = configure_pif(get_vlan_slave_of_pif(pif))
1878 f = open_pif_ifcfg(pif)
1879 f.write("VLAN=yes\n")
1880 f.attach_child(slave)
1884 def configure_pif(pif):
1885 """Write the configuration for a PIF object.
1887 Writes the configuration file the PIF and all dependent
1888 interfaces (bond slaves and VLAN masters etc).
1890 Returns the open file handle for the interface configuration file.
1893 pifrec = db.get_pif_record(pif)
1895 if pifrec['VLAN'] != '-1':
1896 f = configure_vlan_interface(pif)
1897 elif len(pifrec['bond_master_of']) != 0:
1898 f = configure_bond_interface(pif)
1900 f = configure_physical_interface(pif)
1902 bridge = bridge_name(pif)
1904 f.write("BRIDGE=%s\n" % bridge)
1908 def unconfigure_pif(pif):
1909 """Clear up the files created by configure_pif"""
1910 f = open_pif_ifcfg(pif)
1911 log("Unlinking stale file %s" % f.path())
1915 if __name__ == "__main__":
1921 err = traceback.format_exception(*ex)
1925 if not debug_mode():