3 # Copyright (c) Citrix Systems 2008. 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
65 from xml.dom.minidom import getDOMImplementation
66 from xml.dom.minidom import parse as parseXML
68 output_directory = None
73 dbcache_file = "/etc/ovs-vswitch.dbcache"
74 vswitch_config_dir = "/etc/openvswitch"
76 class Usage(Exception):
77 def __init__(self, msg):
78 Exception.__init__(self)
81 class Error(Exception):
82 def __init__(self, msg):
83 Exception.__init__(self)
86 class ConfigurationFile(object):
87 """Write a file, tracking old and new versions.
89 Supports writing a new version of a file and applying and
90 reverting those changes.
93 __STATE = {"OPEN":"OPEN",
94 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
95 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
97 def __init__(self, fname, path="/etc/sysconfig/network-scripts"):
99 self.__state = self.__STATE['OPEN']
104 dirname = output_directory
108 self.__path = os.path.join(dirname, fname)
109 self.__oldpath = os.path.join(dirname, "." + fname + ".xapi-old")
110 self.__newpath = os.path.join(dirname, "." + fname + ".xapi-new")
111 self.__unlink = False
113 self.__f = open(self.__newpath, "w")
115 def attach_child(self, child):
116 self.__children.append(child)
123 return open(self.path()).readlines()
127 def write(self, args):
128 if self.__state != self.__STATE['OPEN']:
129 raise Error("Attempt to write to file in state %s" % self.__state)
133 if self.__state != self.__STATE['OPEN']:
134 raise Error("Attempt to unlink file in state %s" % self.__state)
137 self.__state = self.__STATE['NOT-APPLIED']
140 if self.__state != self.__STATE['OPEN']:
141 raise Error("Attempt to close file in state %s" % self.__state)
144 self.__state = self.__STATE['NOT-APPLIED']
147 if self.__state != self.__STATE['NOT-APPLIED']:
148 raise Error("Attempt to compare file in state %s" % self.__state)
153 if self.__state != self.__STATE['NOT-APPLIED']:
154 raise Error("Attempt to apply configuration from state %s" % self.__state)
156 for child in self.__children:
159 log("Applying changes to %s configuration" % self.__fname)
161 # Remove previous backup.
162 if os.access(self.__oldpath, os.F_OK):
163 os.unlink(self.__oldpath)
165 # Save current configuration.
166 if os.access(self.__path, os.F_OK):
167 os.link(self.__path, self.__oldpath)
168 os.unlink(self.__path)
170 # Apply new configuration.
171 assert(os.path.exists(self.__newpath))
172 if not self.__unlink:
173 os.link(self.__newpath, self.__path)
175 pass # implicit unlink of original file
177 # Remove temporary file.
178 os.unlink(self.__newpath)
180 self.__state = self.__STATE['APPLIED']
183 if self.__state != self.__STATE['APPLIED']:
184 raise Error("Attempt to revert configuration from state %s" % self.__state)
186 for child in self.__children:
189 log("Reverting changes to %s configuration" % self.__fname)
191 # Remove existing new configuration
192 if os.access(self.__newpath, os.F_OK):
193 os.unlink(self.__newpath)
195 # Revert new configuration.
196 if os.access(self.__path, os.F_OK):
197 os.link(self.__path, self.__newpath)
198 os.unlink(self.__path)
200 # Revert to old configuration.
201 if os.access(self.__oldpath, os.F_OK):
202 os.link(self.__oldpath, self.__path)
203 os.unlink(self.__oldpath)
205 # Leave .*.xapi-new as an aid to debugging.
207 self.__state = self.__STATE['REVERTED']
210 if self.__state != self.__STATE['APPLIED']:
211 raise Error("Attempt to commit configuration from state %s" % self.__state)
213 for child in self.__children:
216 log("Committing changes to %s configuration" % self.__fname)
218 if os.access(self.__oldpath, os.F_OK):
219 os.unlink(self.__oldpath)
220 if os.access(self.__newpath, os.F_OK):
221 os.unlink(self.__newpath)
223 self.__state = self.__STATE['COMMITTED']
226 return output_directory is not None
230 print >>sys.stderr, s
234 def check_allowed(pif):
235 pifrec = db.get_pif_record(pif)
237 f = open("/proc/ardence")
238 macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
240 if len(macline) == 1:
241 p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
242 if p.match(macline[0]):
243 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
249 def interface_exists(i):
250 return os.path.exists("/sys/class/net/" + i)
253 # Helper functions for encoding/decoding database attributes to/from XML.
255 def str_to_xml(xml, parent, tag, val):
256 e = xml.createElement(tag)
257 parent.appendChild(e)
258 v = xml.createTextNode(val)
261 def getText(nodelist):
263 for node in nodelist:
264 if node.nodeType == node.TEXT_NODE:
267 return getText(n.childNodes).strip()
270 def bool_to_xml(xml, parent, tag, val):
272 str_to_xml(xml, parent, tag, "True")
274 str_to_xml(xml, parent, tag, "False")
275 def bool_from_xml(n):
282 raise Error("Unknown boolean value %s" % s);
284 def strlist_to_xml(xml, parent, ltag, itag, val):
285 e = xml.createElement(ltag)
286 parent.appendChild(e)
288 c = xml.createElement(itag)
290 cv = xml.createTextNode(v)
292 def strlist_from_xml(n, ltag, itag):
294 for n in n.childNodes:
295 if n.nodeName == itag:
296 ret.append(str_from_xml(n))
299 def otherconfig_to_xml(xml, parent, val, attrs):
300 otherconfig = xml.createElement("other_config")
301 parent.appendChild(otherconfig)
302 for n,v in val.items():
304 raise Error("Unknown other-config attribute: %s" % n)
305 str_to_xml(xml, otherconfig, n, v)
306 def otherconfig_from_xml(n, attrs):
308 for n in n.childNodes:
309 if n.nodeName in attrs:
310 ret[n.nodeName] = str_from_xml(n)
314 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
316 # Each object is defined by a dictionary mapping an attribute name in
317 # the xapi database to a tuple containing two items:
318 # - a function which takes this attribute and encodes it as XML.
319 # - a function which takes XML and decocdes it into a value.
321 # other-config attributes are specified as a simple array of strings
324 VLAN_XML_TAG = "vlan"
325 BOND_XML_TAG = "bond"
326 NETWORK_XML_TAG = "network"
328 ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
330 PIF_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
331 'management': (bool_to_xml,bool_from_xml),
332 'network': (str_to_xml,str_from_xml),
333 'device': (str_to_xml,str_from_xml),
334 'bond_master_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
335 lambda n: strlist_from_xml(n, 'bond_master_of', 'slave')),
336 'bond_slave_of': (str_to_xml,str_from_xml),
337 'VLAN': (str_to_xml,str_from_xml),
338 'VLAN_master_of': (str_to_xml,str_from_xml),
339 'VLAN_slave_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
340 lambda n: strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
341 'ip_configuration_mode': (str_to_xml,str_from_xml),
342 'IP': (str_to_xml,str_from_xml),
343 'netmask': (str_to_xml,str_from_xml),
344 'gateway': (str_to_xml,str_from_xml),
345 'DNS': (str_to_xml,str_from_xml),
346 'MAC': (str_to_xml,str_from_xml),
347 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, PIF_OTHERCONFIG_ATTRS),
348 lambda n: otherconfig_from_xml(n, PIF_OTHERCONFIG_ATTRS)),
351 PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
352 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
353 ETHTOOL_OTHERCONFIG_ATTRS
355 VLAN_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
356 'tagged_PIF': (str_to_xml,str_from_xml),
357 'untagged_PIF': (str_to_xml,str_from_xml),
360 BOND_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
361 'master': (str_to_xml,str_from_xml),
362 'slaves': (lambda x, p, t, v: strlist_to_xml(x, p, 'slaves', 'slave', v),
363 lambda n: strlist_from_xml(n, 'slaves', 'slave')),
366 NETWORK_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
367 'bridge': (str_to_xml,str_from_xml),
368 'PIFs': (lambda x, p, t, v: strlist_to_xml(x, p, 'PIFs', 'PIF', v),
369 lambda n: strlist_from_xml(n, 'PIFs', 'PIF')),
370 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, NETWORK_OTHERCONFIG_ATTRS),
371 lambda n: otherconfig_from_xml(n, NETWORK_OTHERCONFIG_ATTRS)),
374 NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + ETHTOOL_OTHERCONFIG_ATTRS
376 class DatabaseCache(object):
377 def __read_xensource_inventory(self):
378 filename = "/etc/xensource-inventory"
379 f = open(filename, "r")
380 lines = [x.strip("\n") for x in f.readlines()]
383 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
384 defs = [ (a, b.strip("'")) for (a,b) in defs ]
387 def __pif_on_host(self,pif):
388 return self.__pifs.has_key(pif)
390 def __get_pif_records_from_xapi(self, session, host):
392 for (p,rec) in session.xenapi.PIF.get_all_records().items():
393 if rec['host'] != host:
397 self.__pifs[p][f] = rec[f]
398 self.__pifs[p]['other_config'] = {}
399 for f in PIF_OTHERCONFIG_ATTRS:
400 if not rec['other_config'].has_key(f): continue
401 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
403 def __get_vlan_records_from_xapi(self, session):
405 for v in session.xenapi.VLAN.get_all():
406 rec = session.xenapi.VLAN.get_record(v)
407 if not self.__pif_on_host(rec['untagged_PIF']):
411 self.__vlans[v][f] = rec[f]
413 def __get_bond_records_from_xapi(self, session):
415 for b in session.xenapi.Bond.get_all():
416 rec = session.xenapi.Bond.get_record(b)
417 if not self.__pif_on_host(rec['master']):
421 self.__bonds[b][f] = rec[f]
423 def __get_network_records_from_xapi(self, session):
425 for n in session.xenapi.network.get_all():
426 rec = session.xenapi.network.get_record(n)
427 self.__networks[n] = {}
428 for f in NETWORK_ATTRS:
429 self.__networks[n][f] = rec[f]
430 self.__networks[n]['other_config'] = {}
431 for f in NETWORK_OTHERCONFIG_ATTRS:
432 if not rec['other_config'].has_key(f): continue
433 self.__networks[n]['other_config'][f] = rec['other_config'][f]
435 def __to_xml(self, xml, parent, key, ref, rec, attrs):
436 """Encode a database object as XML"""
437 e = xml.createElement(key)
438 parent.appendChild(e)
440 e.setAttribute('ref', ref)
442 for n,v in rec.items():
447 raise Error("Unknown attribute %s" % n)
448 def __from_xml(self, e, attrs):
449 """Decode a database object from XML"""
450 ref = e.attributes['ref'].value
452 for n in e.childNodes:
453 if n.nodeName in attrs:
454 _,h = attrs[n.nodeName]
455 rec[n.nodeName] = h(n)
458 def __init__(self, session_ref=None, cache_file=None):
459 if session_ref and cache_file:
460 raise Error("can't specify session reference and cache file")
461 if cache_file == None:
462 session = XenAPI.xapi_local()
465 log("No session ref given on command line, logging in.")
466 session.xenapi.login_with_password("root", "")
468 session._session = session_ref
472 inventory = self.__read_xensource_inventory()
473 assert(inventory.has_key('INSTALLATION_UUID'))
474 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
476 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
478 self.__get_pif_records_from_xapi(session, host)
480 self.__get_vlan_records_from_xapi(session)
481 self.__get_bond_records_from_xapi(session)
482 self.__get_network_records_from_xapi(session)
485 session.xenapi.session.logout()
487 log("Loading xapi database cache from %s" % cache_file)
489 xml = parseXML(cache_file)
496 assert(len(xml.childNodes) == 1)
497 toplevel = xml.childNodes[0]
499 assert(toplevel.nodeName == "xenserver-network-configuration")
501 for n in toplevel.childNodes:
502 if n.nodeName == "#text":
504 elif n.nodeName == PIF_XML_TAG:
505 (ref,rec) = self.__from_xml(n, PIF_ATTRS)
506 self.__pifs[ref] = rec
507 elif n.nodeName == BOND_XML_TAG:
508 (ref,rec) = self.__from_xml(n, BOND_ATTRS)
509 self.__bonds[ref] = rec
510 elif n.nodeName == VLAN_XML_TAG:
511 (ref,rec) = self.__from_xml(n, VLAN_ATTRS)
512 self.__vlans[ref] = rec
513 elif n.nodeName == NETWORK_XML_TAG:
514 (ref,rec) = self.__from_xml(n, NETWORK_ATTRS)
515 self.__networks[ref] = rec
517 raise Error("Unknown XML element %s" % n.nodeName)
519 def save(self, cache_file):
521 xml = getDOMImplementation().createDocument(
522 None, "xenserver-network-configuration", None)
523 for (ref,rec) in self.__pifs.items():
524 self.__to_xml(xml, xml.documentElement, PIF_XML_TAG, ref, rec, PIF_ATTRS)
525 for (ref,rec) in self.__bonds.items():
526 self.__to_xml(xml, xml.documentElement, BOND_XML_TAG, ref, rec, BOND_ATTRS)
527 for (ref,rec) in self.__vlans.items():
528 self.__to_xml(xml, xml.documentElement, VLAN_XML_TAG, ref, rec, VLAN_ATTRS)
529 for (ref,rec) in self.__networks.items():
530 self.__to_xml(xml, xml.documentElement, NETWORK_XML_TAG, ref, rec,
533 f = open(cache_file, 'w')
534 f.write(xml.toprettyxml())
537 def get_pif_by_uuid(self, uuid):
538 pifs = map(lambda (ref,rec): ref,
539 filter(lambda (ref,rec): uuid == rec['uuid'],
540 self.__pifs.items()))
542 raise Error("Unknown PIF \"%s\"" % uuid)
544 raise Error("Non-unique PIF \"%s\"" % uuid)
548 def get_pifs_by_device(self, device):
549 return map(lambda (ref,rec): ref,
550 filter(lambda (ref,rec): rec['device'] == device,
551 self.__pifs.items()))
553 def get_pif_by_bridge(self, bridge):
554 networks = map(lambda (ref,rec): ref,
555 filter(lambda (ref,rec): rec['bridge'] == bridge,
556 self.__networks.items()))
557 if len(networks) == 0:
558 raise Error("No matching network \"%s\"")
561 for network in networks:
562 nwrec = self.get_network_record(network)
563 for pif in nwrec['PIFs']:
564 pifrec = self.get_pif_record(pif)
566 raise Error("Multiple PIFs on host for network %s" % (bridge))
569 raise Error("No PIF on host for network %s" % (bridge))
572 def get_pif_record(self, pif):
573 if self.__pifs.has_key(pif):
574 return self.__pifs[pif]
575 raise Error("Unknown PIF \"%s\" (get_pif_record)" % pif)
576 def get_all_pifs(self):
578 def pif_exists(self, pif):
579 return self.__pifs.has_key(pif)
581 def get_management_pif(self):
582 """ Returns the management pif on host
584 all = self.get_all_pifs()
586 pifrec = self.get_pif_record(pif)
587 if pifrec['management']: return pif
590 def get_network_record(self, network):
591 if self.__networks.has_key(network):
592 return self.__networks[network]
593 raise Error("Unknown network \"%s\"" % network)
594 def get_all_networks(self):
595 return self.__networks
597 def get_bond_record(self, bond):
598 if self.__bonds.has_key(bond):
599 return self.__bonds[bond]
603 def get_vlan_record(self, vlan):
604 if self.__vlans.has_key(vlan):
605 return self.__vlans[vlan]
609 def bridge_name(pif):
610 """Return the bridge name associated with pif, or None if network is bridgeless"""
611 pifrec = db.get_pif_record(pif)
612 nwrec = db.get_network_record(pifrec['network'])
615 # TODO: sanity check that nwrec['bridgeless'] != 'true'
616 return nwrec['bridge']
618 # TODO: sanity check that nwrec['bridgeless'] == 'true'
621 def interface_name(pif):
622 """Construct an interface name from the given PIF record."""
624 pifrec = db.get_pif_record(pif)
626 if pifrec['VLAN'] == '-1':
627 return pifrec['device']
629 return "%(device)s.%(VLAN)s" % pifrec
631 def datapath_name(pif):
632 """Return the OpenFlow datapath name associated with pif.
633 For a non-VLAN PIF, the datapath name is the bridge name.
634 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
635 (xapi will create a datapath named with the bridge name even though we won't
640 pifrec = db.get_pif_record(pif)
642 if pifrec['VLAN'] == '-1':
643 return bridge_name(pif)
645 return bridge_name(get_vlan_slave_of_pif(pif))
648 """Return the the name of the network device that carries the
649 IP configuration (if any) associated with pif.
650 The ipdev name is the same as the bridge name.
653 pifrec = db.get_pif_record(pif)
654 return bridge_name(pif)
656 def physdev_names(pif):
657 """Return the name(s) of the physical network device(s) associated with pif.
658 For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
659 For a bond master PIF, the physical devices are the bond slaves.
660 For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
662 pifrec = db.get_pif_record(pif)
664 if pifrec['VLAN'] != '-1':
665 return physdev_names(get_vlan_slave_of_pif(pif))
666 elif len(pifrec['bond_master_of']) != 0:
668 for slave in get_bond_slaves_of_pif(pif):
669 physdevs += physdev_names(slave)
672 return [pifrec['device']]
674 def log_pif_action(action, pif):
675 pifrec = db.get_pif_record(pif)
677 rec['uuid'] = pifrec['uuid']
678 rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
679 rec['action'] = action
680 rec['interface-name'] = interface_name(pif)
681 if action == "rewrite":
682 rec['message'] = "Rewrite PIF %(uuid)s configuration" % rec
684 rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
685 log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % rec)
687 def get_bond_masters_of_pif(pif):
688 """Returns a list of PIFs which are bond masters of this PIF"""
690 pifrec = db.get_pif_record(pif)
692 bso = pifrec['bond_slave_of']
694 # bond-slave-of is currently a single reference but in principle a
695 # PIF could be a member of several bonds which are not
696 # concurrently attached. Be robust to this possibility.
697 if not bso or bso == "OpaqueRef:NULL":
699 elif not type(bso) == list:
702 bondrecs = [db.get_bond_record(bond) for bond in bso]
703 bondrecs = [rec for rec in bondrecs if rec]
705 return [bond['master'] for bond in bondrecs]
707 def get_bond_slaves_of_pif(pif):
708 """Returns a list of PIFs which make up the given bonded pif."""
710 pifrec = db.get_pif_record(pif)
712 bmo = pifrec['bond_master_of']
714 raise Error("Bond-master-of contains too many elements")
719 bondrec = db.get_bond_record(bmo[0])
721 raise Error("No bond record for bond master PIF")
723 return bondrec['slaves']
725 def get_vlan_slave_of_pif(pif):
726 """Find the PIF which is the VLAN slave of pif.
728 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
730 pifrec = db.get_pif_record(pif)
732 vlan = pifrec['VLAN_master_of']
733 if not vlan or vlan == "OpaqueRef:NULL":
734 raise Error("PIF is not a VLAN master")
736 vlanrec = db.get_vlan_record(vlan)
738 raise Error("No VLAN record found for PIF")
740 return vlanrec['tagged_PIF']
742 def get_vlan_masters_of_pif(pif):
743 """Returns a list of PIFs which are VLANs on top of the given pif."""
745 pifrec = db.get_pif_record(pif)
746 vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
747 return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])]
749 def interface_deconfigure_commands(interface):
750 # The use of [!0-9] keeps an interface of 'eth0' from matching
751 # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
753 return ['--del-match=bridge.*.port=%s' % interface,
754 '--del-match=bonding.%s.[!0-9]*' % interface,
755 '--del-match=bonding.*.slave=%s' % interface,
756 '--del-match=vlan.%s.[!0-9]*' % interface,
757 '--del-match=port.%s.[!0-9]*' % interface,
758 '--del-match=iface.%s.[!0-9]*' % interface]
760 def run_command(command):
761 log("Running command: " + ' '.join(command))
762 if os.spawnl(os.P_WAIT, command[0], *command) != 0:
763 log("Command failed: " + ' '.join(command))
767 def down_netdev(interface, deconfigure=True):
768 if not interface_exists(interface):
769 log("down_netdev: interface %s does not exist, ignoring" % interface)
771 argv = ["/sbin/ifconfig", interface, 'down']
776 pidfile_name = '/var/run/dhclient-%s.pid' % interface
779 pidfile = open(pidfile_name, 'r')
780 os.kill(int(pidfile.readline()), signal.SIGTERM)
786 # Remove dhclient pidfile.
788 os.remove(pidfile_name)
793 def up_netdev(interface):
794 run_command(["/sbin/ifconfig", interface, 'up'])
796 def find_distinguished_pifs(pif):
797 """Returns the PIFs on host that own DNS and the default route.
798 The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
799 The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
801 Note: we prune out the bond master pif (if it exists).
802 This is because when we are called to bring up an interface with a bond master, it is implicit that
803 we should bring down that master."""
805 pifrec = db.get_pif_record(pif)
807 pifs = [ __pif for __pif in db.get_all_pifs() if
808 (not __pif in get_bond_masters_of_pif(pif)) ]
811 defaultroute_pif = None
813 # loop through all the pifs on this host looking for one with
814 # other-config:peerdns = true, and one with
815 # other-config:default-route=true
817 __pifrec = db.get_pif_record(__pif)
818 __oc = __pifrec['other_config']
819 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
820 if peerdns_pif == None:
823 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
824 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
825 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
826 if defaultroute_pif == None:
827 defaultroute_pif = __pif
829 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
830 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
832 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
833 if peerdns_pif == None:
834 peerdns_pif = management_pif
835 if defaultroute_pif == None:
836 defaultroute_pif = management_pif
838 return peerdns_pif, defaultroute_pif
840 def ethtool_settings(oc):
841 # Options for "ethtool -s"
843 if oc.has_key('ethtool-speed'):
844 val = oc['ethtool-speed']
845 if val in ["10", "100", "1000"]:
846 settings += ['speed', val]
848 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
849 if oc.has_key('ethtool-duplex'):
850 val = oc['ethtool-duplex']
851 if val in ["10", "100", "1000"]:
852 settings += ['duplex', 'val']
854 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
855 if oc.has_key('ethtool-autoneg'):
856 val = oc['ethtool-autoneg']
857 if val in ["true", "on"]:
858 settings += ['autoneg', 'on']
859 elif val in ["false", "off"]:
860 settings += ['autoneg', 'off']
862 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
864 # Options for "ethtool -K"
866 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
867 if oc.has_key("ethtool-" + opt):
868 val = oc["ethtool-" + opt]
869 if val in ["true", "on"]:
870 offload += [opt, 'on']
871 elif val in ["false", "off"]:
872 offload += [opt, 'off']
874 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
876 return settings, offload
878 def configure_netdev(pif):
879 pifrec = db.get_pif_record(pif)
880 datapath = datapath_name(pif)
881 ipdev = ipdev_name(pif)
883 nw = pifrec['network']
884 nwrec = db.get_network_record(nw)
886 ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up']
888 if pifrec['ip_configuration_mode'] == "DHCP":
890 elif pifrec['ip_configuration_mode'] == "Static":
891 ifconfig_argv += [pifrec['IP']]
892 ifconfig_argv += ['netmask', pifrec['netmask']]
893 gateway = pifrec['gateway']
894 elif pifrec['ip_configuration_mode'] == "None":
898 raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode'])
901 if pifrec.has_key('other_config'):
902 oc = pifrec['other_config']
903 if oc.has_key('mtu'):
904 int(oc['mtu']) # Check that the value is an integer
905 ifconfig_argv += ['mtu', oc['mtu']]
907 run_command(ifconfig_argv)
909 (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif)
911 if peerdns_pif == pif:
912 f = ConfigurationFile('resolv.conf', "/etc")
913 if oc.has_key('domain'):
914 f.write("search %s\n" % oc['domain'])
915 for dns in pifrec['DNS'].split(","):
916 f.write("nameserver %s\n" % dns)
921 if defaultroute_pif == pif and gateway != '':
922 run_command(['/sbin/ip', 'route', 'replace', 'default',
923 'via', gateway, 'dev', ipdev])
925 if oc.has_key('static-routes'):
926 for line in oc['static-routes'].split(','):
927 network, masklen, gateway = line.split('/')
928 run_command(['/sbin/ip', 'route', 'add',
929 '%s/%s' % (netmask, masklen), 'via', gateway,
932 settings, offload = ethtool_settings(oc)
934 run_command(['/sbin/ethtool', '-s', ipdev] + settings)
936 run_command(['/sbin/ethtool', '-K', ipdev] + offload)
938 if pifrec['ip_configuration_mode'] == "DHCP":
940 print "Determining IP information for %s..." % ipdev,
941 argv = ['/sbin/dhclient', '-q',
942 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev,
943 '-pf', '/var/run/dhclient-%s.pid' % ipdev,
945 if run_command(argv):
950 def modify_config(commands):
951 run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer',
952 '-F', '/etc/ovs-vswitchd.conf']
954 run_command(['/sbin/service', 'vswitch', 'reload'])
956 def is_bond_pif(pif):
957 pifrec = db.get_pif_record(pif)
958 return len(pifrec['bond_master_of']) != 0
960 def configure_bond(pif):
961 pifrec = db.get_pif_record(pif)
962 interface = interface_name(pif)
963 ipdev = ipdev_name(pif)
964 datapath = datapath_name(pif)
965 physdevs = physdev_names(pif)
967 argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
968 argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
969 for slave in physdevs]
973 "mode": "balance-slb",
979 # override defaults with values from other-config whose keys
981 oc = pifrec['other_config']
982 overrides = filter(lambda (key,val):
983 key.startswith("bond-"), oc.items())
984 overrides = map(lambda (key,val): (key[5:], val), overrides)
985 bond_options.update(overrides)
986 for (name,val) in bond_options.items():
987 argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
991 pifrec = db.get_pif_record(pif)
993 bridge = bridge_name(pif)
994 interface = interface_name(pif)
995 ipdev = ipdev_name(pif)
996 datapath = datapath_name(pif)
997 physdevs = physdev_names(pif)
999 if pifrec['VLAN'] != '-1':
1000 vlan_slave = get_vlan_slave_of_pif(pif)
1001 if vlan_slave and is_bond_pif(vlan_slave):
1002 bond_master = vlan_slave
1003 elif is_bond_pif(pif):
1008 bond_slaves = get_bond_slaves_of_pif(bond_master)
1011 bond_masters = get_bond_masters_of_pif(pif)
1013 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1014 # files up-to-date, even though we don't use them or need them.
1015 f = configure_pif(pif)
1016 mode = pifrec['ip_configuration_mode']
1018 log("Configuring %s using %s configuration" % (bridge, mode))
1019 br = open_network_ifcfg(pif)
1020 configure_network(pif, br)
1024 log("Configuring %s using %s configuration" % (interface, mode))
1025 configure_network(pif, f)
1027 for master in bond_masters:
1028 master_bridge = bridge_name(master)
1029 removed = unconfigure_pif(master)
1030 f.attach_child(removed)
1032 removed = open_network_ifcfg(master)
1033 log("Unlinking stale file %s" % removed.path())
1035 f.attach_child(removed)
1037 # /etc/xensource/scripts/vif needs to know where to add VIFs.
1039 if not os.path.exists(vswitch_config_dir):
1040 os.mkdir(vswitch_config_dir)
1041 br = ConfigurationFile("br-%s" % bridge, vswitch_config_dir)
1042 br.write("VLAN_SLAVE=%s\n" % datapath)
1043 br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
1047 # Update all configuration files (both ours and Centos's).
1051 # "ifconfig down" the network device and delete its IP address, etc.
1053 for physdev in physdevs:
1054 down_netdev(physdev)
1056 # If we are bringing up a bond, remove IP addresses from the
1057 # slaves (because we are implicitly being asked to take them down).
1059 # Conversely, if we are bringing up an interface that has bond
1060 # masters, remove IP addresses from the bond master (because we
1061 # are implicitly being asked to take it down).
1062 for bond_pif in bond_slaves + bond_masters:
1063 run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0'])
1065 # Remove all keys related to pif and any bond masters linked to PIF.
1066 del_ports = [ipdev] + physdevs + bond_masters
1067 if vlan_slave and bond_master:
1068 del_ports += [interface_name(bond_master)]
1070 # What ports do we need to add to the datapath?
1072 # We definitely need the ipdev, and ordinarily we want the
1073 # physical devices too, but for bonds we need the bond as bridge
1075 add_ports = [ipdev, datapath]
1077 add_ports += physdevs
1079 add_ports += [interface_name(bond_master)]
1081 # What ports do we need to delete?
1083 # - All the ports that we add, to avoid duplication and to drop
1084 # them from another datapath in case they're misassigned.
1086 # - The physical devices, since they will either be in add_ports
1087 # or added to the bonding device (see below).
1089 # - The bond masters for pif. (Ordinarily pif shouldn't have any
1090 # bond masters. If it does then interface-reconfigure is
1091 # implicitly being asked to take them down.)
1092 del_ports = add_ports + physdevs + bond_masters
1094 # What networks does this datapath carry?
1096 # - The network corresponding to the datapath's PIF.
1098 # - The networks corresponding to any VLANs attached to the
1101 for nwpif in db.get_pifs_by_device({'device': pifrec['device']}):
1102 net = db.get_pif_record(nwpif)['network']
1103 network_uuids += [db.get_network_record(net)['uuid']]
1105 # Bring up bond slaves early, because ovs-vswitchd initially
1106 # enables or disables bond slaves based on whether carrier is
1107 # detected when they are added, and a network device that is down
1108 # always reports "no carrier".
1109 bond_slave_physdevs = []
1110 for slave in bond_slaves:
1111 bond_slave_physdevs += physdev_names(slave)
1112 for slave_physdev in bond_slave_physdevs:
1113 up_netdev(slave_physdev)
1115 # Now modify the ovs-vswitchd config file.
1117 for port in set(del_ports):
1118 argv += interface_deconfigure_commands(port)
1119 for port in set(add_ports):
1120 argv += ['--add=bridge.%s.port=%s' % (datapath, port)]
1122 argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
1123 argv += ['--add=iface.%s.internal=true' % (ipdev)]
1125 # xapi creates a bridge by the name of the ipdev and requires
1126 # that the IP address will be on it. We need to delete this
1127 # bridge because we need that device to be a member of our
1129 argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev]
1131 # xapi insists that its attempts to create the bridge succeed,
1132 # so force that to happen.
1133 argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
1136 os.unlink("%s/br-%s" % (vswitch_config_dir, bridge))
1139 argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath]
1140 argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid)
1141 for uuid in set(network_uuids)]
1143 argv += configure_bond(bond_master)
1146 # Configure network devices.
1147 configure_netdev(pif)
1149 # Bring up VLAN slave, plus physical devices other than bond
1150 # slaves (which we brought up earlier).
1152 up_netdev(ipdev_name(vlan_slave))
1153 for physdev in set(physdevs) - set(bond_slave_physdevs):
1156 # Update /etc/issue (which contains the IP address of the management interface)
1157 os.system("/sbin/update-issue")
1160 # There seems to be a race somewhere: without this sleep, using
1161 # XenCenter to create a bond that becomes the management interface
1162 # fails with "The underlying connection was closed: A connection that
1163 # was expected to be kept alive was closed by the server." on every
1164 # second or third try, even though /var/log/messages doesn't show
1167 # The race is probably present even without vswitch, but bringing up a
1168 # bond without vswitch involves a built-in pause of 10 seconds or more
1169 # to wait for the bond to transition from learning to forwarding state.
1172 def action_down(pif):
1173 rec = db.get_pif_record(pif)
1174 interface = interface_name(pif)
1175 bridge = bridge_name(pif)
1176 ipdev = ipdev_name(pif)
1178 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1179 # files up-to-date, even though we don't use them or need them.
1180 f = unconfigure_pif(pif)
1182 br = open_network_ifcfg(pif)
1183 log("Unlinking stale file %s" % br.path())
1190 log("action_down failed to apply changes: %s" % e.msg)
1195 if rec['VLAN'] != '-1':
1196 # Get rid of the VLAN device itself.
1198 argv += interface_deconfigure_commands(ipdev)
1200 # If the VLAN's slave is attached, stop here.
1201 slave = get_vlan_slave_of_pif(pif)
1202 if db.get_pif_record(slave)['currently_attached']:
1203 log("VLAN slave is currently attached")
1207 # If the VLAN's slave has other VLANs that are attached, stop here.
1208 masters = get_vlan_masters_of_pif(slave)
1210 if m != pif and db.get_pif_record(m)['currently_attached']:
1211 log("VLAN slave has other master %s" % interface_naem(m))
1215 # Otherwise, take down the VLAN's slave too.
1216 log("No more masters, bring down vlan slave %s" % interface_name(slave))
1219 # Stop here if this PIF has attached VLAN masters.
1220 vlan_masters = get_vlan_masters_of_pif(pif)
1221 log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters]))
1222 for m in vlan_masters:
1223 if db.get_pif_record(m)['currently_attached']:
1224 log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m)))
1227 # pif is now either a bond or a physical device which needs to be
1228 # brought down. pif might have changed so re-check all its attributes.
1229 rec = db.get_pif_record(pif)
1230 interface = interface_name(pif)
1231 bridge = bridge_name(pif)
1232 ipdev = ipdev_name(pif)
1235 bond_slaves = get_bond_slaves_of_pif(pif)
1236 log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves]))
1237 for slave in bond_slaves:
1238 slave_interface = interface_name(slave)
1239 log("bring down bond slave %s" % slave_interface)
1240 argv += interface_deconfigure_commands(slave_interface)
1241 down_netdev(slave_interface)
1243 argv += interface_deconfigure_commands(ipdev)
1246 argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)]
1247 argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface]
1250 def action_rewrite(pif):
1251 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1252 # files up-to-date, even though we don't use them or need them.
1253 pifrec = db.get_pif_record(pif)
1254 f = configure_pif(pif)
1255 interface = interface_name(pif)
1256 bridge = bridge_name(pif)
1257 mode = pifrec['ip_configuration_mode']
1259 log("Configuring %s using %s configuration" % (bridge, mode))
1260 br = open_network_ifcfg(pif)
1261 configure_network(pif, br)
1265 log("Configuring %s using %s configuration" % (interface, mode))
1266 configure_network(pif, f)
1272 log("failed to apply changes: %s" % e.msg)
1276 # We have no code of our own to run here.
1279 def main(argv=None):
1280 global output_directory, management_pif
1286 force_interface = None
1287 force_management = False
1295 longops = [ "output-directory=",
1296 "pif=", "pif-uuid=",
1302 "device=", "mode=", "ip=", "netmask=", "gateway=",
1304 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1305 except getopt.GetoptError, msg:
1308 force_rewrite_config = {}
1311 if o == "--output-directory":
1312 output_directory = a
1315 elif o == "--pif-uuid":
1317 elif o == "--session":
1319 elif o == "--force-interface" or o == "--force":
1321 elif o == "--management":
1322 force_management = True
1323 elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1324 force_rewrite_config[o[2:]] = a
1325 elif o == "-h" or o == "--help":
1326 print __doc__ % {'command-name': os.path.basename(argv[0])}
1329 if not debug_mode():
1330 syslog.openlog(os.path.basename(argv[0]))
1331 log("Called as " + str.join(" ", argv))
1333 raise Usage("Required option <action> not present")
1335 raise Usage("Too many arguments")
1338 # backwards compatibility
1339 if action == "rewrite-configuration": action = "rewrite"
1341 if output_directory and ( session or pif ):
1342 raise Usage("--session/--pif cannot be used with --output-directory")
1343 if ( session or pif ) and pif_uuid:
1344 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1345 if ( session and not pif ) or ( not session and pif ):
1346 raise Usage("--session and --pif must be used together.")
1347 if force_interface and ( session or pif or pif_uuid ):
1348 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1349 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1350 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1354 log("Force interface %s %s" % (force_interface, action))
1356 if action == "rewrite":
1357 action_force_rewrite(force_interface, force_rewrite_config)
1359 db = DatabaseCache(cache_file=dbcache_file)
1360 pif = db.get_pif_by_bridge(force_interface)
1361 management_pif = db.get_management_pif()
1365 elif action == "down":
1368 raise Usage("Unknown action %s" % action)
1370 db = DatabaseCache(session_ref=session)
1373 pif = db.get_pif_by_uuid(pif_uuid)
1376 raise Usage("No PIF given")
1378 if force_management:
1379 # pif is going to be the management pif
1380 management_pif = pif
1382 # pif is not going to be the management pif.
1383 # Search DB cache for pif on same host with management=true
1384 pifrec = db.get_pif_record(pif)
1385 management_pif = db.get_management_pif()
1387 log_pif_action(action, pif)
1389 if not check_allowed(pif):
1394 elif action == "down":
1396 elif action == "rewrite":
1399 raise Usage("Unknown action %s" % action)
1402 pifrec = db.get_pif_record(pif)
1403 db.save(dbcache_file)
1406 print >>sys.stderr, err.msg
1407 print >>sys.stderr, "For help use --help."
1415 # The following code allows interface-reconfigure to keep Centos
1416 # network configuration files up-to-date, even though the vswitch
1417 # never uses them. In turn, that means that "rpm -e vswitch" does not
1418 # have to update any configuration files.
1420 def configure_ethtool(oc, f):
1421 # Options for "ethtool -s"
1423 setting_opts = ["autoneg", "speed", "duplex"]
1424 # Options for "ethtool -K"
1426 offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
1428 for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
1429 val = oc["ethtool-" + opt]
1431 if opt in ["speed"]:
1432 if val in ["10", "100", "1000"]:
1433 val = "speed " + val
1435 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
1437 elif opt in ["duplex"]:
1438 if val in ["half", "full"]:
1439 val = "duplex " + val
1441 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
1443 elif opt in ["autoneg"] + offload_opts:
1444 if val in ["true", "on"]:
1446 elif val in ["false", "off"]:
1449 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
1452 if opt in setting_opts:
1453 if val and settings:
1454 settings = settings + " " + val
1457 elif opt in offload_opts:
1459 offload = offload + " " + val
1464 f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
1466 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
1468 def configure_mtu(oc, f):
1469 if not oc.has_key('mtu'):
1473 mtu = int(oc['mtu'])
1474 f.write("MTU=%d\n" % mtu)
1475 except ValueError, x:
1476 log("Invalid value for mtu = %s" % mtu)
1478 def configure_static_routes(interface, oc, f):
1479 """Open a route-<interface> file for static routes.
1481 Opens the static routes configuration file for interface and writes one
1482 line for each route specified in the network's other config "static-routes" value.
1484 interface ( RO): xenbr1
1485 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1487 Then route-xenbr1 should be
1488 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1489 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1491 fname = "route-%s" % interface
1492 if oc.has_key('static-routes'):
1493 # The key is present - extract comma seperates entries
1494 lines = oc['static-routes'].split(',')
1496 # The key is not present, i.e. there are no static routes
1499 child = ConfigurationFile(fname)
1500 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1501 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
1505 network, masklen, gateway = l.split('/')
1506 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1508 f.attach_child(child)
1511 except ValueError, e:
1512 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1514 def __open_ifcfg(interface):
1515 """Open a network interface configuration file.
1517 Opens the configuration file for interface, writes a header and
1518 common options and returns the file object.
1520 fname = "ifcfg-%s" % interface
1521 f = ConfigurationFile(fname)
1523 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1524 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1525 f.write("XEMANAGED=yes\n")
1526 f.write("DEVICE=%s\n" % interface)
1527 f.write("ONBOOT=no\n")
1531 def open_network_ifcfg(pif):
1532 bridge = bridge_name(pif)
1533 interface = interface_name(pif)
1535 return __open_ifcfg(bridge)
1537 return __open_ifcfg(interface)
1540 def open_pif_ifcfg(pif):
1541 pifrec = db.get_pif_record(pif)
1543 log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
1545 f = __open_ifcfg(interface_name(pif))
1547 if pifrec.has_key('other_config'):
1548 configure_ethtool(pifrec['other_config'], f)
1549 configure_mtu(pifrec['other_config'], f)
1553 def configure_network(pif, f):
1554 """Write the configuration file for a network.
1556 Writes configuration derived from the network object into the relevant
1557 ifcfg file. The configuration file is passed in, but if the network is
1558 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1560 This routine may also write ifcfg files of the networks corresponding to other PIFs
1561 in order to maintain consistency.
1564 pif: Opaque_ref of pif
1565 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1568 pifrec = db.get_pif_record(pif)
1569 nw = pifrec['network']
1570 nwrec = db.get_network_record(nw)
1572 bridge = bridge_name(pif)
1573 interface = interface_name(pif)
1579 if nwrec.has_key('other_config'):
1580 configure_ethtool(nwrec['other_config'], f)
1581 configure_mtu(nwrec['other_config'], f)
1582 configure_static_routes(device, nwrec['other_config'], f)
1585 if pifrec.has_key('other_config'):
1586 oc = pifrec['other_config']
1588 if device == bridge:
1589 f.write("TYPE=Bridge\n")
1590 f.write("DELAY=0\n")
1591 f.write("STP=off\n")
1592 f.write("PIFDEV=%s\n" % interface_name(pif))
1594 if pifrec['ip_configuration_mode'] == "DHCP":
1595 f.write("BOOTPROTO=dhcp\n")
1596 f.write("PERSISTENT_DHCLIENT=yes\n")
1597 elif pifrec['ip_configuration_mode'] == "Static":
1598 f.write("BOOTPROTO=none\n")
1599 f.write("NETMASK=%(netmask)s\n" % pifrec)
1600 f.write("IPADDR=%(IP)s\n" % pifrec)
1601 f.write("GATEWAY=%(gateway)s\n" % pifrec)
1602 elif pifrec['ip_configuration_mode'] == "None":
1603 f.write("BOOTPROTO=none\n")
1605 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1607 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1608 ServerList = pifrec['DNS'].split(",")
1609 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1610 if oc and oc.has_key('domain'):
1611 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1613 # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1614 # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1615 # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1617 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1619 # Note: we prune out the bond master pif (if it exists).
1620 # This is because when we are called to bring up an interface with a bond master, it is implicit that
1621 # we should bring down that master.
1622 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1623 not __pif in get_bond_masters_of_pif(pif) ]
1624 other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1627 defaultroute_pif = None
1629 # loop through all the pifs on this host looking for one with
1630 # other-config:peerdns = true, and one with
1631 # other-config:default-route=true
1632 for __pif in pifs_on_host:
1633 __pifrec = db.get_pif_record(__pif)
1634 __oc = __pifrec['other_config']
1635 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1636 if peerdns_pif == None:
1639 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1640 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1641 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1642 if defaultroute_pif == None:
1643 defaultroute_pif = __pif
1645 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1646 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1648 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1649 if peerdns_pif == None:
1650 peerdns_pif = management_pif
1651 if defaultroute_pif == None:
1652 defaultroute_pif = management_pif
1654 # Update all the other network's ifcfg files and ensure consistency
1655 for __pif in other_pifs_on_host:
1656 __f = open_network_ifcfg(__pif)
1657 peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1658 lines = __f.readlines()
1660 if not peerdns_line_wanted in lines:
1661 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1663 if not line.lstrip().startswith('PEERDNS'):
1665 log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1666 __f.write(peerdns_line_wanted)
1671 # There is no need to change this ifcfg file. So don't attach_child.
1674 # ... and for this pif too
1675 f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1678 fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1679 for line in fnetwork.readlines():
1680 if line.lstrip().startswith('GATEWAY') :
1682 fnetwork.write(line)
1683 if defaultroute_pif:
1684 gatewaydev = bridge_name(defaultroute_pif)
1686 gatewaydev = interface_name(defaultroute_pif)
1687 fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1689 f.attach_child(fnetwork)
1694 def configure_physical_interface(pif):
1695 """Write the configuration for a physical interface.
1697 Writes the configuration file for the physical interface described by
1700 Returns the open file handle for the interface configuration file.
1703 pifrec = db.get_pif_record(pif)
1705 f = open_pif_ifcfg(pif)
1707 f.write("TYPE=Ethernet\n")
1708 f.write("HWADDR=%(MAC)s\n" % pifrec)
1712 def configure_bond_interface(pif):
1713 """Write the configuration for a bond interface.
1715 Writes the configuration file for the bond interface described by
1716 the pif object. Handles writing the configuration for the slave
1719 Returns the open file handle for the bond interface configuration
1723 pifrec = db.get_pif_record(pif)
1724 oc = pifrec['other_config']
1725 f = open_pif_ifcfg(pif)
1727 if pifrec['MAC'] != "":
1728 f.write("MACADDR=%s\n" % pifrec['MAC'])
1730 for slave in get_bond_slaves_of_pif(pif):
1731 s = configure_physical_interface(slave)
1732 s.write("MASTER=%(device)s\n" % pifrec)
1733 s.write("SLAVE=yes\n")
1737 # The bond option defaults
1739 "mode": "balance-slb",
1746 # override defaults with values from other-config whose keys being with "bond-"
1747 overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
1748 overrides = map(lambda (key,val): (key[5:], val), overrides)
1749 bond_options.update(overrides)
1751 # write the bond options to ifcfg-bondX
1752 f.write('BONDING_OPTS="')
1753 for (name,val) in bond_options.items():
1754 f.write("%s=%s " % (name,val))
1758 def configure_vlan_interface(pif):
1759 """Write the configuration for a VLAN interface.
1761 Writes the configuration file for the VLAN interface described by
1762 the pif object. Handles writing the configuration for the master
1763 interface if necessary.
1765 Returns the open file handle for the VLAN interface configuration
1769 slave = configure_pif(get_vlan_slave_of_pif(pif))
1772 f = open_pif_ifcfg(pif)
1773 f.write("VLAN=yes\n")
1774 f.attach_child(slave)
1778 def configure_pif(pif):
1779 """Write the configuration for a PIF object.
1781 Writes the configuration file the PIF and all dependent
1782 interfaces (bond slaves and VLAN masters etc).
1784 Returns the open file handle for the interface configuration file.
1787 pifrec = db.get_pif_record(pif)
1789 if pifrec['VLAN'] != '-1':
1790 f = configure_vlan_interface(pif)
1791 elif len(pifrec['bond_master_of']) != 0:
1792 f = configure_bond_interface(pif)
1794 f = configure_physical_interface(pif)
1796 bridge = bridge_name(pif)
1798 f.write("BRIDGE=%s\n" % bridge)
1802 def unconfigure_pif(pif):
1803 """Clear up the files created by configure_pif"""
1804 f = open_pif_ifcfg(pif)
1805 log("Unlinking stale file %s" % f.path())
1809 if __name__ == "__main__":
1815 err = traceback.format_exception(*ex)
1819 if not debug_mode():