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
67 output_directory = None
72 dbcache_file = "/etc/ovs-vswitch.dbcache"
73 vswitch_config_dir = "/etc/openvswitch"
75 class Usage(Exception):
76 def __init__(self, msg):
77 Exception.__init__(self)
80 class Error(Exception):
81 def __init__(self, msg):
82 Exception.__init__(self)
85 class ConfigurationFile(object):
86 """Write a file, tracking old and new versions.
88 Supports writing a new version of a file and applying and
89 reverting those changes.
92 __STATE = {"OPEN":"OPEN",
93 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
94 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
96 def __init__(self, fname, path="/etc/sysconfig/network-scripts"):
98 self.__state = self.__STATE['OPEN']
103 dirname = output_directory
107 self.__path = os.path.join(dirname, fname)
108 self.__oldpath = os.path.join(dirname, "." + fname + ".xapi-old")
109 self.__newpath = os.path.join(dirname, "." + fname + ".xapi-new")
110 self.__unlink = False
112 self.__f = open(self.__newpath, "w")
114 def attach_child(self, child):
115 self.__children.append(child)
122 return open(self.path()).readlines()
126 def write(self, args):
127 if self.__state != self.__STATE['OPEN']:
128 raise Error("Attempt to write to file in state %s" % self.__state)
132 if self.__state != self.__STATE['OPEN']:
133 raise Error("Attempt to unlink file in state %s" % self.__state)
136 self.__state = self.__STATE['NOT-APPLIED']
139 if self.__state != self.__STATE['OPEN']:
140 raise Error("Attempt to close file in state %s" % self.__state)
143 self.__state = self.__STATE['NOT-APPLIED']
146 if self.__state != self.__STATE['NOT-APPLIED']:
147 raise Error("Attempt to compare file in state %s" % self.__state)
152 if self.__state != self.__STATE['NOT-APPLIED']:
153 raise Error("Attempt to apply configuration from state %s" % self.__state)
155 for child in self.__children:
158 log("Applying changes to %s configuration" % self.__fname)
160 # Remove previous backup.
161 if os.access(self.__oldpath, os.F_OK):
162 os.unlink(self.__oldpath)
164 # Save current configuration.
165 if os.access(self.__path, os.F_OK):
166 os.link(self.__path, self.__oldpath)
167 os.unlink(self.__path)
169 # Apply new configuration.
170 assert(os.path.exists(self.__newpath))
171 if not self.__unlink:
172 os.link(self.__newpath, self.__path)
174 pass # implicit unlink of original file
176 # Remove temporary file.
177 os.unlink(self.__newpath)
179 self.__state = self.__STATE['APPLIED']
182 if self.__state != self.__STATE['APPLIED']:
183 raise Error("Attempt to revert configuration from state %s" % self.__state)
185 for child in self.__children:
188 log("Reverting changes to %s configuration" % self.__fname)
190 # Remove existing new configuration
191 if os.access(self.__newpath, os.F_OK):
192 os.unlink(self.__newpath)
194 # Revert new configuration.
195 if os.access(self.__path, os.F_OK):
196 os.link(self.__path, self.__newpath)
197 os.unlink(self.__path)
199 # Revert to old configuration.
200 if os.access(self.__oldpath, os.F_OK):
201 os.link(self.__oldpath, self.__path)
202 os.unlink(self.__oldpath)
204 # Leave .*.xapi-new as an aid to debugging.
206 self.__state = self.__STATE['REVERTED']
209 if self.__state != self.__STATE['APPLIED']:
210 raise Error("Attempt to commit configuration from state %s" % self.__state)
212 for child in self.__children:
215 log("Committing changes to %s configuration" % self.__fname)
217 if os.access(self.__oldpath, os.F_OK):
218 os.unlink(self.__oldpath)
219 if os.access(self.__newpath, os.F_OK):
220 os.unlink(self.__newpath)
222 self.__state = self.__STATE['COMMITTED']
225 return output_directory is not None
229 print >>sys.stderr, s
233 def check_allowed(pif):
234 pifrec = db.get_pif_record(pif)
236 f = open("/proc/ardence")
237 macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
239 if len(macline) == 1:
240 p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
241 if p.match(macline[0]):
242 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
248 def interface_exists(i):
249 return os.path.exists("/sys/class/net/" + i)
251 class DatabaseCache(object):
252 def __get_pif_records_from_xapi(self, session):
253 self.__pifs = session.xenapi.PIF.get_all_records()
255 def __get_vlan_records_from_xapi(self, session):
256 self.__vlans = session.xenapi.VLAN.get_all_records()
258 def __get_bond_records_from_xapi(self, session):
259 self.__bonds = session.xenapi.Bond.get_all_records()
261 def __get_network_records_from_xapi(self, session):
262 self.__networks = session.xenapi.network.get_all_records()
264 def __init__(self, session_ref=None, cache_file=None):
265 if session_ref and cache_file:
266 raise Error("can't specify session reference and cache file")
268 if cache_file == None:
269 session = XenAPI.xapi_local()
272 log("No session ref given on command line, logging in.")
273 session.xenapi.login_with_password("root", "")
275 session._session = session_ref
278 self.__get_pif_records_from_xapi(session)
279 self.__get_vlan_records_from_xapi(session)
280 self.__get_bond_records_from_xapi(session)
281 self.__get_network_records_from_xapi(session)
284 session.xenapi.session.logout()
286 log("Loading xapi database cache from %s" % cache_file)
287 f = open(cache_file, 'r')
288 members = pickle.load(f)
289 self.extras = pickle.load(f)
292 self.__vlans = members['vlans']
293 self.__bonds = members['bonds']
294 self.__pifs = members['pifs']
295 self.__networks = members['networks']
297 def save(self, cache_file, extras):
298 f = open(cache_file, 'w')
299 pickle.dump({'vlans': self.__vlans,
300 'bonds': self.__bonds,
302 'networks': self.__networks}, f)
303 pickle.dump(extras, f)
306 def get_pif_by_uuid(self, uuid):
307 pifs = map(lambda (ref,rec): ref,
308 filter(lambda (ref,rec): uuid == rec['uuid'],
309 self.__pifs.items()))
311 raise Error("Unknown PIF \"%s\"" % uuid)
313 raise Error("Non-unique PIF \"%s\"" % uuid)
317 def get_pifs_by_record(self, record):
318 """record is partial pif record.
319 Get the pif(s) whose record matches.
323 if record[key] != pifrec[key]:
327 return map(lambda (ref,rec): ref,
328 filter(lambda (ref,rec): match(rec),
329 self.__pifs.items()))
331 def get_pif_by_record(self, record):
332 """record is partial pif record.
333 Get the pif whose record matches.
335 pifs = self.get_pifs_by_record(record)
337 raise Error("No matching PIF \"%s\"" % str(record))
339 raise Error("Multiple matching PIFs \"%s\"" % str(record))
343 def get_pif_by_bridge(self, host, bridge):
344 networks = map(lambda (ref,rec): ref,
345 filter(lambda (ref,rec): rec['bridge'] == bridge,
346 self.__networks.items()))
347 if len(networks) == 0:
348 raise Error("No matching network \"%s\"")
351 for network in networks:
352 nwrec = self.get_network_record(network)
353 for pif in nwrec['PIFs']:
354 pifrec = self.get_pif_record(pif)
355 if pifrec['host'] != host:
358 raise Error("Multiple PIFs on %s for network %s" % (host, bridge))
361 raise Error("No PIF on %s for network %s" % (host, bridge))
364 def get_pif_record(self, pif):
365 if self.__pifs.has_key(pif):
366 return self.__pifs[pif]
367 raise Error("Unknown PIF \"%s\"" % pif)
368 def get_all_pifs(self):
370 def pif_exists(self, pif):
371 return self.__pifs.has_key(pif)
373 def get_management_pif(self, host):
374 """ Returns the management pif on host
376 all = self.get_all_pifs()
378 pifrec = self.get_pif_record(pif)
379 if pifrec['management'] and pifrec['host'] == host :
383 def get_network_record(self, network):
384 if self.__networks.has_key(network):
385 return self.__networks[network]
386 raise Error("Unknown network \"%s\"" % network)
387 def get_all_networks(self):
388 return self.__networks
390 def get_bond_record(self, bond):
391 if self.__bonds.has_key(bond):
392 return self.__bonds[bond]
396 def get_vlan_record(self, vlan):
397 if self.__vlans.has_key(vlan):
398 return self.__vlans[vlan]
402 def bridge_name(pif):
403 """Return the bridge name associated with pif, or None if network is bridgeless"""
404 pifrec = db.get_pif_record(pif)
405 nwrec = db.get_network_record(pifrec['network'])
408 # TODO: sanity check that nwrec['bridgeless'] != 'true'
409 return nwrec['bridge']
411 # TODO: sanity check that nwrec['bridgeless'] == 'true'
414 def interface_name(pif):
415 """Construct an interface name from the given PIF record."""
417 pifrec = db.get_pif_record(pif)
419 if pifrec['VLAN'] == '-1':
420 return pifrec['device']
422 return "%(device)s.%(VLAN)s" % pifrec
424 def datapath_name(pif):
425 """Return the OpenFlow datapath name associated with pif.
426 For a non-VLAN PIF, the datapath name is the bridge name.
427 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
428 (xapi will create a datapath named with the bridge name even though we won't
432 pifrec = db.get_pif_record(pif)
434 if pifrec['VLAN'] == '-1':
435 return bridge_name(pif)
437 return bridge_name(get_vlan_slave_of_pif(pif))
440 """Return the the name of the network device that carries the
441 IP configuration (if any) associated with pif.
442 The ipdev name is the same as the bridge name.
445 pifrec = db.get_pif_record(pif)
446 return bridge_name(pif)
448 def physdev_names(pif):
449 """Return the name(s) of the physical network device(s) associated with pif.
450 For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
451 For a bond master PIF, the physical devices are the bond slaves.
452 For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
455 pifrec = db.get_pif_record(pif)
457 if pifrec['VLAN'] != '-1':
458 return physdev_names(get_vlan_slave_of_pif(pif))
459 elif len(pifrec['bond_master_of']) != 0:
461 for slave in get_bond_slaves_of_pif(pif):
462 physdevs += physdev_names(slave)
465 return [pifrec['device']]
467 def log_pif_action(action, pif):
468 pifrec = db.get_pif_record(pif)
469 pifrec['action'] = action
470 pifrec['interface-name'] = interface_name(pif)
471 if action == "rewrite":
472 pifrec['message'] = "Rewrite PIF %(uuid)s configuration" % pifrec
474 pifrec['message'] = "Bring %(action)s PIF %(uuid)s" % pifrec
475 log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % pifrec)
477 def get_bond_masters_of_pif(pif):
478 """Returns a list of PIFs which are bond masters of this PIF"""
480 pifrec = db.get_pif_record(pif)
482 bso = pifrec['bond_slave_of']
484 # bond-slave-of is currently a single reference but in principle a
485 # PIF could be a member of several bonds which are not
486 # concurrently attached. Be robust to this possibility.
487 if not bso or bso == "OpaqueRef:NULL":
489 elif not type(bso) == list:
492 bondrecs = [db.get_bond_record(bond) for bond in bso]
493 bondrecs = [rec for rec in bondrecs if rec]
495 return [bond['master'] for bond in bondrecs]
497 def get_bond_slaves_of_pif(pif):
498 """Returns a list of PIFs which make up the given bonded pif."""
500 pifrec = db.get_pif_record(pif)
501 host = pifrec['host']
503 bmo = pifrec['bond_master_of']
505 raise Error("Bond-master-of contains too many elements")
510 bondrec = db.get_bond_record(bmo[0])
512 raise Error("No bond record for bond master PIF")
514 return bondrec['slaves']
516 def get_vlan_slave_of_pif(pif):
517 """Find the PIF which is the VLAN slave of pif.
519 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
521 pifrec = db.get_pif_record(pif)
523 vlan = pifrec['VLAN_master_of']
524 if not vlan or vlan == "OpaqueRef:NULL":
525 raise Error("PIF is not a VLAN master")
527 vlanrec = db.get_vlan_record(vlan)
529 raise Error("No VLAN record found for PIF")
531 return vlanrec['tagged_PIF']
533 def get_vlan_masters_of_pif(pif):
534 """Returns a list of PIFs which are VLANs on top of the given pif."""
536 pifrec = db.get_pif_record(pif)
537 vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
538 return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])]
540 def interface_deconfigure_commands(interface):
541 # The use of [!0-9] keeps an interface of 'eth0' from matching
542 # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
544 return ['--del-match=bridge.*.port=%s' % interface,
545 '--del-match=bonding.%s.[!0-9]*' % interface,
546 '--del-match=bonding.*.slave=%s' % interface,
547 '--del-match=vlan.%s.[!0-9]*' % interface,
548 '--del-match=port.%s.[!0-9]*' % interface,
549 '--del-match=iface.%s.[!0-9]*' % interface]
551 def run_command(command):
552 log("Running command: " + ' '.join(command))
553 if os.spawnl(os.P_WAIT, command[0], *command) != 0:
554 log("Command failed: " + ' '.join(command))
558 def down_netdev(interface, deconfigure=True):
559 if not interface_exists(interface):
560 log("down_netdev: interface %s does not exist, ignoring" % interface)
562 argv = ["/sbin/ifconfig", interface, 'down']
567 pidfile_name = '/var/run/dhclient-%s.pid' % interface
570 pidfile = open(pidfile_name, 'r')
571 os.kill(int(pidfile.readline()), signal.SIGTERM)
577 # Remove dhclient pidfile.
579 os.remove(pidfile_name)
584 def up_netdev(interface):
585 run_command(["/sbin/ifconfig", interface, 'up'])
587 def find_distinguished_pifs(pif):
588 """Returns the PIFs on host that own DNS and the default route.
589 The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
590 The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
592 Note: we prune out the bond master pif (if it exists).
593 This is because when we are called to bring up an interface with a bond master, it is implicit that
594 we should bring down that master."""
596 pifrec = db.get_pif_record(pif)
597 host = pifrec['host']
599 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
600 db.get_pif_record(__pif)['host'] == host and
601 (not __pif in get_bond_masters_of_pif(pif)) ]
604 defaultroute_pif = None
606 # loop through all the pifs on this host looking for one with
607 # other-config:peerdns = true, and one with
608 # other-config:default-route=true
609 for __pif in pifs_on_host:
610 __pifrec = db.get_pif_record(__pif)
611 __oc = __pifrec['other_config']
612 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
613 if peerdns_pif == None:
616 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
617 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
618 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
619 if defaultroute_pif == None:
620 defaultroute_pif = __pif
622 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
623 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
625 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
626 if peerdns_pif == None:
627 peerdns_pif = management_pif
628 if defaultroute_pif == None:
629 defaultroute_pif = management_pif
631 return peerdns_pif, defaultroute_pif
633 def ethtool_settings(oc):
634 # Options for "ethtool -s"
636 if oc.has_key('ethtool-speed'):
637 val = oc['ethtool-speed']
638 if val in ["10", "100", "1000"]:
639 settings += ['speed', val]
641 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
642 if oc.has_key('ethtool-duplex'):
643 val = oc['ethtool-duplex']
644 if val in ["10", "100", "1000"]:
645 settings += ['duplex', 'val']
647 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
648 if oc.has_key('ethtool-autoneg'):
649 val = oc['ethtool-autoneg']
650 if val in ["true", "on"]:
651 settings += ['autoneg', 'on']
652 elif val in ["false", "off"]:
653 settings += ['autoneg', 'off']
655 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
657 # Options for "ethtool -K"
659 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
660 if oc.has_key("ethtool-" + opt):
661 val = oc["ethtool-" + opt]
662 if val in ["true", "on"]:
663 offload += [opt, 'on']
664 elif val in ["false", "off"]:
665 offload += [opt, 'off']
667 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
669 return settings, offload
671 def configure_netdev(pif):
672 pifrec = db.get_pif_record(pif)
673 datapath = datapath_name(pif)
674 ipdev = ipdev_name(pif)
676 host = pifrec['host']
677 nw = pifrec['network']
678 nwrec = db.get_network_record(nw)
680 ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up']
682 if pifrec['ip_configuration_mode'] == "DHCP":
684 elif pifrec['ip_configuration_mode'] == "Static":
685 ifconfig_argv += [pifrec['IP']]
686 ifconfig_argv += ['netmask', pifrec['netmask']]
687 gateway = pifrec['gateway']
688 elif pifrec['ip_configuration_mode'] == "None":
692 raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode'])
695 if pifrec.has_key('other_config'):
696 oc = pifrec['other_config']
697 if oc.has_key('mtu'):
698 int(oc['mtu']) # Check that the value is an integer
699 ifconfig_argv += ['mtu', oc['mtu']]
701 run_command(ifconfig_argv)
703 (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif)
705 if peerdns_pif == pif:
706 f = ConfigurationFile('resolv.conf', "/etc")
707 if oc.has_key('domain'):
708 f.write("search %s\n" % oc['domain'])
709 for dns in pifrec['DNS'].split(","):
710 f.write("nameserver %s\n" % dns)
715 if defaultroute_pif == pif and gateway != '':
716 run_command(['/sbin/ip', 'route', 'replace', 'default',
717 'via', gateway, 'dev', ipdev])
719 if oc.has_key('static-routes'):
720 for line in oc['static-routes'].split(','):
721 network, masklen, gateway = line.split('/')
722 run_command(['/sbin/ip', 'route', 'add',
723 '%s/%s' % (netmask, masklen), 'via', gateway,
726 settings, offload = ethtool_settings(oc)
728 run_command(['/sbin/ethtool', '-s', ipdev] + settings)
730 run_command(['/sbin/ethtool', '-K', ipdev] + offload)
732 if pifrec['ip_configuration_mode'] == "DHCP":
734 print "Determining IP information for %s..." % ipdev,
735 argv = ['/sbin/dhclient', '-q',
736 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev,
737 '-pf', '/var/run/dhclient-%s.pid' % ipdev,
739 if run_command(argv):
744 def modify_config(commands):
745 run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer',
746 '-F', '/etc/ovs-vswitchd.conf']
748 run_command(['/sbin/service', 'vswitch', 'reload'])
750 def is_bond_pif(pif):
751 pifrec = db.get_pif_record(pif)
752 return len(pifrec['bond_master_of']) != 0
754 def configure_bond(pif):
755 pifrec = db.get_pif_record(pif)
756 interface = interface_name(pif)
757 ipdev = ipdev_name(pif)
758 datapath = datapath_name(pif)
759 physdevs = physdev_names(pif)
761 argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
762 argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
763 for slave in physdevs]
767 "mode": "balance-slb",
773 # override defaults with values from other-config whose keys
775 oc = pifrec['other_config']
776 overrides = filter(lambda (key,val):
777 key.startswith("bond-"), oc.items())
778 overrides = map(lambda (key,val): (key[5:], val), overrides)
779 bond_options.update(overrides)
780 for (name,val) in bond_options.items():
781 argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
785 pifrec = db.get_pif_record(pif)
787 bridge = bridge_name(pif)
788 interface = interface_name(pif)
789 ipdev = ipdev_name(pif)
790 datapath = datapath_name(pif)
791 physdevs = physdev_names(pif)
793 if pifrec['VLAN'] != '-1':
794 vlan_slave = get_vlan_slave_of_pif(pif)
795 if vlan_slave and is_bond_pif(vlan_slave):
796 bond_master = vlan_slave
797 elif is_bond_pif(pif):
802 bond_slaves = get_bond_slaves_of_pif(bond_master)
805 bond_masters = get_bond_masters_of_pif(pif)
807 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
808 # files up-to-date, even though we don't use them or need them.
809 f = configure_pif(pif)
810 mode = pifrec['ip_configuration_mode']
812 log("Configuring %s using %s configuration" % (bridge, mode))
813 br = open_network_ifcfg(pif)
814 configure_network(pif, br)
818 log("Configuring %s using %s configuration" % (interface, mode))
819 configure_network(pif, f)
821 for master in bond_masters:
822 master_bridge = bridge_name(master)
823 removed = unconfigure_pif(master)
824 f.attach_child(removed)
826 removed = open_network_ifcfg(master)
827 log("Unlinking stale file %s" % removed.path())
829 f.attach_child(removed)
831 # /etc/xensource/scripts/vif needs to know where to add VIFs.
833 if not os.path.exists(vswitch_config_dir):
834 os.mkdir(vswitch_config_dir)
835 br = ConfigurationFile("br-%s" % bridge, vswitch_config_dir)
836 br.write("VLAN_SLAVE=%s\n" % datapath)
837 br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
841 # Update all configuration files (both ours and Centos's).
845 # "ifconfig down" the network device and delete its IP address, etc.
847 for physdev in physdevs:
850 # If we are bringing up a bond, remove IP addresses from the
851 # slaves (because we are implicitly being asked to take them down).
853 # Conversely, if we are bringing up an interface that has bond
854 # masters, remove IP addresses from the bond master (because we
855 # are implicitly being asked to take it down).
856 for bond_pif in bond_slaves + bond_masters:
857 run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0'])
859 # Remove all keys related to pif and any bond masters linked to PIF.
860 del_ports = [ipdev] + physdevs + bond_masters
861 if vlan_slave and bond_master:
862 del_ports += [interface_name(bond_master)]
864 # What ports do we need to add to the datapath?
866 # We definitely need the ipdev, and ordinarily we want the
867 # physical devices too, but for bonds we need the bond as bridge
869 add_ports = [ipdev, datapath]
871 add_ports += physdevs
873 add_ports += [interface_name(bond_master)]
875 # What ports do we need to delete?
877 # - All the ports that we add, to avoid duplication and to drop
878 # them from another datapath in case they're misassigned.
880 # - The physical devices, since they will either be in add_ports
881 # or added to the bonding device (see below).
883 # - The bond masters for pif. (Ordinarily pif shouldn't have any
884 # bond masters. If it does then interface-reconfigure is
885 # implicitly being asked to take them down.)
886 del_ports = add_ports + physdevs + bond_masters
888 # What networks does this datapath carry?
890 # - The network corresponding to the datapath's PIF.
892 # - The networks corresponding to any VLANs attached to the
895 for nwpif in db.get_pifs_by_record({'device': pifrec['device'],
896 'host': pifrec['host']}):
897 net = db.get_pif_record(nwpif)['network']
898 network_uuids += [db.get_network_record(net)['uuid']]
900 # Bring up bond slaves early, because ovs-vswitchd initially
901 # enables or disables bond slaves based on whether carrier is
902 # detected when they are added, and a network device that is down
903 # always reports "no carrier".
904 bond_slave_physdevs = []
905 for slave in bond_slaves:
906 bond_slave_physdevs += physdev_names(slave)
907 for slave_physdev in bond_slave_physdevs:
908 up_netdev(slave_physdev)
910 # Now modify the ovs-vswitchd config file.
912 for port in set(del_ports):
913 argv += interface_deconfigure_commands(port)
914 for port in set(add_ports):
915 argv += ['--add=bridge.%s.port=%s' % (datapath, port)]
917 argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
918 argv += ['--add=iface.%s.internal=true' % (ipdev)]
920 # xapi creates a bridge by the name of the ipdev and requires
921 # that the IP address will be on it. We need to delete this
922 # bridge because we need that device to be a member of our
924 argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev]
926 # xapi insists that its attempts to create the bridge succeed,
927 # so force that to happen.
928 argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
931 os.unlink("%s/br-%s" % (vswitch_config_dir, bridge))
934 argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath]
935 argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid)
936 for uuid in set(network_uuids)]
938 argv += configure_bond(bond_master)
941 # Configure network devices.
942 configure_netdev(pif)
944 # Bring up VLAN slave, plus physical devices other than bond
945 # slaves (which we brought up earlier).
947 up_netdev(ipdev_name(vlan_slave))
948 for physdev in set(physdevs) - set(bond_slave_physdevs):
951 # Update /etc/issue (which contains the IP address of the management interface)
952 os.system("/sbin/update-issue")
955 # There seems to be a race somewhere: without this sleep, using
956 # XenCenter to create a bond that becomes the management interface
957 # fails with "The underlying connection was closed: A connection that
958 # was expected to be kept alive was closed by the server." on every
959 # second or third try, even though /var/log/messages doesn't show
962 # The race is probably present even without vswitch, but bringing up a
963 # bond without vswitch involves a built-in pause of 10 seconds or more
964 # to wait for the bond to transition from learning to forwarding state.
967 def action_down(pif):
968 rec = db.get_pif_record(pif)
969 interface = interface_name(pif)
970 bridge = bridge_name(pif)
971 ipdev = ipdev_name(pif)
973 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
974 # files up-to-date, even though we don't use them or need them.
975 f = unconfigure_pif(pif)
977 br = open_network_ifcfg(pif)
978 log("Unlinking stale file %s" % br.path())
985 log("action_down failed to apply changes: %s" % e.msg)
990 if rec['VLAN'] != '-1':
991 # Get rid of the VLAN device itself.
993 argv += interface_deconfigure_commands(ipdev)
995 # If the VLAN's slave is attached, stop here.
996 slave = get_vlan_slave_of_pif(pif)
997 if db.get_pif_record(slave)['currently_attached']:
998 log("VLAN slave is currently attached")
1002 # If the VLAN's slave has other VLANs that are attached, stop here.
1003 masters = get_vlan_masters_of_pif(slave)
1005 if m != pif and db.get_pif_record(m)['currently_attached']:
1006 log("VLAN slave has other master %s" % interface_naem(m))
1010 # Otherwise, take down the VLAN's slave too.
1011 log("No more masters, bring down vlan slave %s" % interface_name(slave))
1014 # Stop here if this PIF has attached VLAN masters.
1015 vlan_masters = get_vlan_masters_of_pif(pif)
1016 log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters]))
1017 for m in vlan_masters:
1018 if db.get_pif_record(m)['currently_attached']:
1019 log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m)))
1022 # pif is now either a bond or a physical device which needs to be
1023 # brought down. pif might have changed so re-check all its attributes.
1024 rec = db.get_pif_record(pif)
1025 interface = interface_name(pif)
1026 bridge = bridge_name(pif)
1027 ipdev = ipdev_name(pif)
1030 bond_slaves = get_bond_slaves_of_pif(pif)
1031 log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves]))
1032 for slave in bond_slaves:
1033 slave_interface = interface_name(slave)
1034 log("bring down bond slave %s" % slave_interface)
1035 argv += interface_deconfigure_commands(slave_interface)
1036 down_netdev(slave_interface)
1038 argv += interface_deconfigure_commands(ipdev)
1041 argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)]
1042 argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface]
1045 def action_rewrite(pif):
1046 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1047 # files up-to-date, even though we don't use them or need them.
1048 pifrec = db.get_pif_record(pif)
1049 f = configure_pif(pif)
1050 interface = interface_name(pif)
1051 bridge = bridge_name(pif)
1052 mode = pifrec['ip_configuration_mode']
1054 log("Configuring %s using %s configuration" % (bridge, mode))
1055 br = open_network_ifcfg(pif)
1056 configure_network(pif, br)
1060 log("Configuring %s using %s configuration" % (interface, mode))
1061 configure_network(pif, f)
1067 log("failed to apply changes: %s" % e.msg)
1071 # We have no code of our own to run here.
1074 def main(argv=None):
1075 global output_directory, management_pif
1081 force_interface = None
1082 force_management = False
1090 longops = [ "output-directory=",
1091 "pif=", "pif-uuid=",
1097 "device=", "mode=", "ip=", "netmask=", "gateway=",
1099 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1100 except getopt.GetoptError, msg:
1103 force_rewrite_config = {}
1106 if o == "--output-directory":
1107 output_directory = a
1110 elif o == "--pif-uuid":
1112 elif o == "--session":
1114 elif o == "--force-interface" or o == "--force":
1116 elif o == "--management":
1117 force_management = True
1118 elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1119 force_rewrite_config[o[2:]] = a
1120 elif o == "-h" or o == "--help":
1121 print __doc__ % {'command-name': os.path.basename(argv[0])}
1124 if not debug_mode():
1125 syslog.openlog(os.path.basename(argv[0]))
1126 log("Called as " + str.join(" ", argv))
1128 raise Usage("Required option <action> not present")
1130 raise Usage("Too many arguments")
1133 # backwards compatibility
1134 if action == "rewrite-configuration": action = "rewrite"
1136 if output_directory and ( session or pif ):
1137 raise Usage("--session/--pif cannot be used with --output-directory")
1138 if ( session or pif ) and pif_uuid:
1139 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1140 if ( session and not pif ) or ( not session and pif ):
1141 raise Usage("--session and --pif must be used together.")
1142 if force_interface and ( session or pif or pif_uuid ):
1143 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1144 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1145 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1149 log("Force interface %s %s" % (force_interface, action))
1151 if action == "rewrite":
1152 action_force_rewrite(force_interface, force_rewrite_config)
1154 db = DatabaseCache(cache_file=dbcache_file)
1155 host = db.extras['host']
1156 pif = db.get_pif_by_bridge(host, force_interface)
1157 management_pif = db.get_management_pif(host)
1161 elif action == "down":
1164 raise Usage("Unknown action %s" % action)
1166 db = DatabaseCache(session_ref=session)
1169 pif = db.get_pif_by_uuid(pif_uuid)
1172 raise Usage("No PIF given")
1174 if force_management:
1175 # pif is going to be the management pif
1176 management_pif = pif
1178 # pif is not going to be the management pif.
1179 # Search DB cache for pif on same host with management=true
1180 pifrec = db.get_pif_record(pif)
1181 host = pifrec['host']
1182 management_pif = db.get_management_pif(host)
1184 log_pif_action(action, pif)
1186 if not check_allowed(pif):
1191 elif action == "down":
1193 elif action == "rewrite":
1196 raise Usage("Unknown action %s" % action)
1199 pifrec = db.get_pif_record(pif)
1200 db.save(dbcache_file, {'host': pifrec['host']})
1203 print >>sys.stderr, err.msg
1204 print >>sys.stderr, "For help use --help."
1212 # The following code allows interface-reconfigure to keep Centos
1213 # network configuration files up-to-date, even though the vswitch
1214 # never uses them. In turn, that means that "rpm -e vswitch" does not
1215 # have to update any configuration files.
1217 def configure_ethtool(oc, f):
1218 # Options for "ethtool -s"
1220 setting_opts = ["autoneg", "speed", "duplex"]
1221 # Options for "ethtool -K"
1223 offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
1225 for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
1226 val = oc["ethtool-" + opt]
1228 if opt in ["speed"]:
1229 if val in ["10", "100", "1000"]:
1230 val = "speed " + val
1232 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
1234 elif opt in ["duplex"]:
1235 if val in ["half", "full"]:
1236 val = "duplex " + val
1238 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
1240 elif opt in ["autoneg"] + offload_opts:
1241 if val in ["true", "on"]:
1243 elif val in ["false", "off"]:
1246 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
1249 if opt in setting_opts:
1250 if val and settings:
1251 settings = settings + " " + val
1254 elif opt in offload_opts:
1256 offload = offload + " " + val
1261 f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
1263 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
1265 def configure_mtu(oc, f):
1266 if not oc.has_key('mtu'):
1270 mtu = int(oc['mtu'])
1271 f.write("MTU=%d\n" % mtu)
1272 except ValueError, x:
1273 log("Invalid value for mtu = %s" % mtu)
1275 def configure_static_routes(interface, oc, f):
1276 """Open a route-<interface> file for static routes.
1278 Opens the static routes configuration file for interface and writes one
1279 line for each route specified in the network's other config "static-routes" value.
1281 interface ( RO): xenbr1
1282 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1284 Then route-xenbr1 should be
1285 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1286 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1288 fname = "route-%s" % interface
1289 if oc.has_key('static-routes'):
1290 # The key is present - extract comma seperates entries
1291 lines = oc['static-routes'].split(',')
1293 # The key is not present, i.e. there are no static routes
1296 child = ConfigurationFile(fname)
1297 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1298 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
1302 network, masklen, gateway = l.split('/')
1303 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1305 f.attach_child(child)
1308 except ValueError, e:
1309 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1311 def __open_ifcfg(interface):
1312 """Open a network interface configuration file.
1314 Opens the configuration file for interface, writes a header and
1315 common options and returns the file object.
1317 fname = "ifcfg-%s" % interface
1318 f = ConfigurationFile(fname)
1320 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1321 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1322 f.write("XEMANAGED=yes\n")
1323 f.write("DEVICE=%s\n" % interface)
1324 f.write("ONBOOT=no\n")
1328 def open_network_ifcfg(pif):
1329 bridge = bridge_name(pif)
1330 interface = interface_name(pif)
1332 return __open_ifcfg(bridge)
1334 return __open_ifcfg(interface)
1337 def open_pif_ifcfg(pif):
1338 pifrec = db.get_pif_record(pif)
1340 log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
1342 f = __open_ifcfg(interface_name(pif))
1344 if pifrec.has_key('other_config'):
1345 configure_ethtool(pifrec['other_config'], f)
1346 configure_mtu(pifrec['other_config'], f)
1350 def configure_network(pif, f):
1351 """Write the configuration file for a network.
1353 Writes configuration derived from the network object into the relevant
1354 ifcfg file. The configuration file is passed in, but if the network is
1355 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1357 This routine may also write ifcfg files of the networks corresponding to other PIFs
1358 in order to maintain consistency.
1361 pif: Opaque_ref of pif
1362 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1365 pifrec = db.get_pif_record(pif)
1366 host = pifrec['host']
1367 nw = pifrec['network']
1368 nwrec = db.get_network_record(nw)
1370 bridge = bridge_name(pif)
1371 interface = interface_name(pif)
1377 if nwrec.has_key('other_config'):
1378 configure_ethtool(nwrec['other_config'], f)
1379 configure_mtu(nwrec['other_config'], f)
1380 configure_static_routes(device, nwrec['other_config'], f)
1383 if pifrec.has_key('other_config'):
1384 oc = pifrec['other_config']
1386 if device == bridge:
1387 f.write("TYPE=Bridge\n")
1388 f.write("DELAY=0\n")
1389 f.write("STP=off\n")
1390 f.write("PIFDEV=%s\n" % interface_name(pif))
1392 if pifrec['ip_configuration_mode'] == "DHCP":
1393 f.write("BOOTPROTO=dhcp\n")
1394 f.write("PERSISTENT_DHCLIENT=yes\n")
1395 elif pifrec['ip_configuration_mode'] == "Static":
1396 f.write("BOOTPROTO=none\n")
1397 f.write("NETMASK=%(netmask)s\n" % pifrec)
1398 f.write("IPADDR=%(IP)s\n" % pifrec)
1399 f.write("GATEWAY=%(gateway)s\n" % pifrec)
1400 elif pifrec['ip_configuration_mode'] == "None":
1401 f.write("BOOTPROTO=none\n")
1403 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1405 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1406 ServerList = pifrec['DNS'].split(",")
1407 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1408 if oc and oc.has_key('domain'):
1409 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1411 # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1412 # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1413 # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1415 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1417 # Note: we prune out the bond master pif (if it exists).
1418 # This is because when we are called to bring up an interface with a bond master, it is implicit that
1419 # we should bring down that master.
1420 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1421 db.get_pif_record(__pif)['host'] == host and
1422 (not __pif in get_bond_masters_of_pif(pif)) ]
1423 other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1426 defaultroute_pif = None
1428 # loop through all the pifs on this host looking for one with
1429 # other-config:peerdns = true, and one with
1430 # other-config:default-route=true
1431 for __pif in pifs_on_host:
1432 __pifrec = db.get_pif_record(__pif)
1433 __oc = __pifrec['other_config']
1434 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1435 if peerdns_pif == None:
1438 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1439 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1440 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1441 if defaultroute_pif == None:
1442 defaultroute_pif = __pif
1444 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1445 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1447 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1448 if peerdns_pif == None:
1449 peerdns_pif = management_pif
1450 if defaultroute_pif == None:
1451 defaultroute_pif = management_pif
1453 # Update all the other network's ifcfg files and ensure consistency
1454 for __pif in other_pifs_on_host:
1455 __f = open_network_ifcfg(__pif)
1456 peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1457 lines = __f.readlines()
1459 if not peerdns_line_wanted in lines:
1460 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1462 if not line.lstrip().startswith('PEERDNS'):
1464 log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1465 __f.write(peerdns_line_wanted)
1470 # There is no need to change this ifcfg file. So don't attach_child.
1473 # ... and for this pif too
1474 f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1477 fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1478 for line in fnetwork.readlines():
1479 if line.lstrip().startswith('GATEWAY') :
1481 fnetwork.write(line)
1482 if defaultroute_pif:
1483 gatewaydev = bridge_name(defaultroute_pif)
1485 gatewaydev = interface_name(defaultroute_pif)
1486 fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1488 f.attach_child(fnetwork)
1493 def configure_physical_interface(pif):
1494 """Write the configuration for a physical interface.
1496 Writes the configuration file for the physical interface described by
1499 Returns the open file handle for the interface configuration file.
1502 pifrec = db.get_pif_record(pif)
1504 f = open_pif_ifcfg(pif)
1506 f.write("TYPE=Ethernet\n")
1507 f.write("HWADDR=%(MAC)s\n" % pifrec)
1511 def configure_bond_interface(pif):
1512 """Write the configuration for a bond interface.
1514 Writes the configuration file for the bond interface described by
1515 the pif object. Handles writing the configuration for the slave
1518 Returns the open file handle for the bond interface configuration
1522 pifrec = db.get_pif_record(pif)
1523 oc = pifrec['other_config']
1524 f = open_pif_ifcfg(pif)
1526 if pifrec['MAC'] != "":
1527 f.write("MACADDR=%s\n" % pifrec['MAC'])
1529 for slave in get_bond_slaves_of_pif(pif):
1530 s = configure_physical_interface(slave)
1531 s.write("MASTER=%(device)s\n" % pifrec)
1532 s.write("SLAVE=yes\n")
1536 # The bond option defaults
1538 "mode": "balance-slb",
1545 # override defaults with values from other-config whose keys being with "bond-"
1546 overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
1547 overrides = map(lambda (key,val): (key[5:], val), overrides)
1548 bond_options.update(overrides)
1550 # write the bond options to ifcfg-bondX
1551 f.write('BONDING_OPTS="')
1552 for (name,val) in bond_options.items():
1553 f.write("%s=%s " % (name,val))
1557 def configure_vlan_interface(pif):
1558 """Write the configuration for a VLAN interface.
1560 Writes the configuration file for the VLAN interface described by
1561 the pif object. Handles writing the configuration for the master
1562 interface if necessary.
1564 Returns the open file handle for the VLAN interface configuration
1568 slave = configure_pif(get_vlan_slave_of_pif(pif))
1571 f = open_pif_ifcfg(pif)
1572 f.write("VLAN=yes\n")
1573 f.attach_child(slave)
1577 def configure_pif(pif):
1578 """Write the configuration for a PIF object.
1580 Writes the configuration file the PIF and all dependent
1581 interfaces (bond slaves and VLAN masters etc).
1583 Returns the open file handle for the interface configuration file.
1586 pifrec = db.get_pif_record(pif)
1588 if pifrec['VLAN'] != '-1':
1589 f = configure_vlan_interface(pif)
1590 elif len(pifrec['bond_master_of']) != 0:
1591 f = configure_bond_interface(pif)
1593 f = configure_physical_interface(pif)
1595 bridge = bridge_name(pif)
1597 f.write("BRIDGE=%s\n" % bridge)
1601 def unconfigure_pif(pif):
1602 """Clear up the files created by configure_pif"""
1603 f = open_pif_ifcfg(pif)
1604 log("Unlinking stale file %s" % f.path())
1608 if __name__ == "__main__":
1614 err = traceback.format_exception(*ex)
1618 if not debug_mode():