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 __init__(self, session_ref=None, cache_file=None):
253 if session_ref and cache_file:
254 raise Error("can't specify session reference and cache file")
256 if cache_file == None:
257 session = XenAPI.xapi_local()
260 log("No session ref given on command line, logging in.")
261 session.xenapi.login_with_password("root", "")
263 session._session = session_ref
266 self.__vlans = session.xenapi.VLAN.get_all_records()
267 self.__bonds = session.xenapi.Bond.get_all_records()
268 self.__pifs = session.xenapi.PIF.get_all_records()
269 self.__networks = session.xenapi.network.get_all_records()
272 session.xenapi.session.logout()
274 log("Loading xapi database cache from %s" % cache_file)
275 f = open(cache_file, 'r')
276 members = pickle.load(f)
277 self.extras = pickle.load(f)
280 self.__vlans = members['vlans']
281 self.__bonds = members['bonds']
282 self.__pifs = members['pifs']
283 self.__networks = members['networks']
285 def save(self, cache_file, extras):
286 f = open(cache_file, 'w')
287 pickle.dump({'vlans': self.__vlans,
288 'bonds': self.__bonds,
290 'networks': self.__networks}, f)
291 pickle.dump(extras, f)
294 def get_pif_by_uuid(self, uuid):
295 pifs = map(lambda (ref,rec): ref,
296 filter(lambda (ref,rec): uuid == rec['uuid'],
297 self.__pifs.items()))
299 raise Error("Unknown PIF \"%s\"" % uuid)
301 raise Error("Non-unique PIF \"%s\"" % uuid)
305 def get_pifs_by_record(self, record):
306 """record is partial pif record.
307 Get the pif(s) whose record matches.
311 if record[key] != pifrec[key]:
315 return map(lambda (ref,rec): ref,
316 filter(lambda (ref,rec): match(rec),
317 self.__pifs.items()))
319 def get_pif_by_record(self, record):
320 """record is partial pif record.
321 Get the pif whose record matches.
323 pifs = self.get_pifs_by_record(record)
325 raise Error("No matching PIF \"%s\"" % str(record))
327 raise Error("Multiple matching PIFs \"%s\"" % str(record))
331 def get_pif_by_bridge(self, host, bridge):
332 networks = map(lambda (ref,rec): ref,
333 filter(lambda (ref,rec): rec['bridge'] == bridge,
334 self.__networks.items()))
335 if len(networks) == 0:
336 raise Error("No matching network \"%s\"")
339 for network in networks:
340 nwrec = self.get_network_record(network)
341 for pif in nwrec['PIFs']:
342 pifrec = self.get_pif_record(pif)
343 if pifrec['host'] != host:
346 raise Error("Multiple PIFs on %s for network %s" % (host, bridge))
349 raise Error("No PIF on %s for network %s" % (host, bridge))
352 def get_pif_record(self, pif):
353 if self.__pifs.has_key(pif):
354 return self.__pifs[pif]
355 raise Error("Unknown PIF \"%s\"" % pif)
356 def get_all_pifs(self):
358 def pif_exists(self, pif):
359 return self.__pifs.has_key(pif)
361 def get_management_pif(self, host):
362 """ Returns the management pif on host
364 all = self.get_all_pifs()
366 pifrec = self.get_pif_record(pif)
367 if pifrec['management'] and pifrec['host'] == host :
371 def get_network_record(self, network):
372 if self.__networks.has_key(network):
373 return self.__networks[network]
374 raise Error("Unknown network \"%s\"" % network)
375 def get_all_networks(self):
376 return self.__networks
378 def get_bond_record(self, bond):
379 if self.__bonds.has_key(bond):
380 return self.__bonds[bond]
384 def get_vlan_record(self, vlan):
385 if self.__vlans.has_key(vlan):
386 return self.__vlans[vlan]
390 def bridge_name(pif):
391 """Return the bridge name associated with pif, or None if network is bridgeless"""
392 pifrec = db.get_pif_record(pif)
393 nwrec = db.get_network_record(pifrec['network'])
396 # TODO: sanity check that nwrec['bridgeless'] != 'true'
397 return nwrec['bridge']
399 # TODO: sanity check that nwrec['bridgeless'] == 'true'
402 def interface_name(pif):
403 """Construct an interface name from the given PIF record."""
405 pifrec = db.get_pif_record(pif)
407 if pifrec['VLAN'] == '-1':
408 return pifrec['device']
410 return "%(device)s.%(VLAN)s" % pifrec
412 def datapath_name(pif):
413 """Return the OpenFlow datapath name associated with pif.
414 For a non-VLAN PIF, the datapath name is the bridge name.
415 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
416 (xapi will create a datapath named with the bridge name even though we won't
420 pifrec = db.get_pif_record(pif)
422 if pifrec['VLAN'] == '-1':
423 return bridge_name(pif)
425 return bridge_name(get_vlan_slave_of_pif(pif))
428 """Return the the name of the network device that carries the
429 IP configuration (if any) associated with pif.
430 The ipdev name is the same as the bridge name.
433 pifrec = db.get_pif_record(pif)
434 return bridge_name(pif)
436 def physdev_names(pif):
437 """Return the name(s) of the physical network device(s) associated with pif.
438 For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
439 For a bond master PIF, the physical devices are the bond slaves.
440 For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
443 pifrec = db.get_pif_record(pif)
445 if pifrec['VLAN'] != '-1':
446 return physdev_names(get_vlan_slave_of_pif(pif))
447 elif len(pifrec['bond_master_of']) != 0:
449 for slave in get_bond_slaves_of_pif(pif):
450 physdevs += physdev_names(slave)
453 return [pifrec['device']]
455 def log_pif_action(action, pif):
456 pifrec = db.get_pif_record(pif)
457 pifrec['action'] = action
458 pifrec['interface-name'] = interface_name(pif)
459 if action == "rewrite":
460 pifrec['message'] = "Rewrite PIF %(uuid)s configuration" % pifrec
462 pifrec['message'] = "Bring %(action)s PIF %(uuid)s" % pifrec
463 log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % pifrec)
465 def get_bond_masters_of_pif(pif):
466 """Returns a list of PIFs which are bond masters of this PIF"""
468 pifrec = db.get_pif_record(pif)
470 bso = pifrec['bond_slave_of']
472 # bond-slave-of is currently a single reference but in principle a
473 # PIF could be a member of several bonds which are not
474 # concurrently attached. Be robust to this possibility.
475 if not bso or bso == "OpaqueRef:NULL":
477 elif not type(bso) == list:
480 bondrecs = [db.get_bond_record(bond) for bond in bso]
481 bondrecs = [rec for rec in bondrecs if rec]
483 return [bond['master'] for bond in bondrecs]
485 def get_bond_slaves_of_pif(pif):
486 """Returns a list of PIFs which make up the given bonded pif."""
488 pifrec = db.get_pif_record(pif)
489 host = pifrec['host']
491 bmo = pifrec['bond_master_of']
493 raise Error("Bond-master-of contains too many elements")
498 bondrec = db.get_bond_record(bmo[0])
500 raise Error("No bond record for bond master PIF")
502 return bondrec['slaves']
504 def get_vlan_slave_of_pif(pif):
505 """Find the PIF which is the VLAN slave of pif.
507 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
509 pifrec = db.get_pif_record(pif)
511 vlan = pifrec['VLAN_master_of']
512 if not vlan or vlan == "OpaqueRef:NULL":
513 raise Error("PIF is not a VLAN master")
515 vlanrec = db.get_vlan_record(vlan)
517 raise Error("No VLAN record found for PIF")
519 return vlanrec['tagged_PIF']
521 def get_vlan_masters_of_pif(pif):
522 """Returns a list of PIFs which are VLANs on top of the given pif."""
524 pifrec = db.get_pif_record(pif)
525 vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
526 return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])]
528 def interface_deconfigure_commands(interface):
529 # The use of [!0-9] keeps an interface of 'eth0' from matching
530 # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
532 return ['--del-match=bridge.*.port=%s' % interface,
533 '--del-match=bonding.%s.[!0-9]*' % interface,
534 '--del-match=bonding.*.slave=%s' % interface,
535 '--del-match=vlan.%s.[!0-9]*' % interface,
536 '--del-match=port.%s.[!0-9]*' % interface,
537 '--del-match=iface.%s.[!0-9]*' % interface]
539 def run_command(command):
540 log("Running command: " + ' '.join(command))
541 if os.spawnl(os.P_WAIT, command[0], *command) != 0:
542 log("Command failed: " + ' '.join(command))
546 def read_first_line_of_file(name):
549 file = open(name, 'r')
550 return file.readline().rstrip('\n')
555 def down_netdev(interface, deconfigure=True):
556 if not interface_exists(interface):
557 log("down_netdev: interface %s does not exist, ignoring" % interface)
561 pidfile_name = '/var/run/dhclient-%s.pid' % interface
563 os.kill(int(read_first_line_of_file(pidfile_name)), signal.SIGTERM)
567 # Remove dhclient pidfile.
569 os.remove(pidfile_name)
573 run_command(["/sbin/ifconfig", interface, '0.0.0.0'])
575 run_command(["/sbin/ifconfig", interface, 'down'])
577 def up_netdev(interface):
578 run_command(["/sbin/ifconfig", interface, 'up'])
580 def find_distinguished_pifs(pif):
581 """Returns the PIFs on host that own DNS and the default route.
582 The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
583 The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
585 Note: we prune out the bond master pif (if it exists).
586 This is because when we are called to bring up an interface with a bond master, it is implicit that
587 we should bring down that master."""
589 pifrec = db.get_pif_record(pif)
590 host = pifrec['host']
592 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
593 db.get_pif_record(__pif)['host'] == host and
594 (not __pif in get_bond_masters_of_pif(pif)) ]
597 defaultroute_pif = None
599 # loop through all the pifs on this host looking for one with
600 # other-config:peerdns = true, and one with
601 # other-config:default-route=true
602 for __pif in pifs_on_host:
603 __pifrec = db.get_pif_record(__pif)
604 __oc = __pifrec['other_config']
605 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
606 if peerdns_pif == None:
609 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
610 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
611 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
612 if defaultroute_pif == None:
613 defaultroute_pif = __pif
615 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
616 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
618 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
619 if peerdns_pif == None:
620 peerdns_pif = management_pif
621 if defaultroute_pif == None:
622 defaultroute_pif = management_pif
624 return peerdns_pif, defaultroute_pif
626 def ethtool_settings(oc):
627 # Options for "ethtool -s"
629 if oc.has_key('ethtool-speed'):
630 val = oc['ethtool-speed']
631 if val in ["10", "100", "1000"]:
632 settings += ['speed', val]
634 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
635 if oc.has_key('ethtool-duplex'):
636 val = oc['ethtool-duplex']
637 if val in ["10", "100", "1000"]:
638 settings += ['duplex', 'val']
640 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
641 if oc.has_key('ethtool-autoneg'):
642 val = oc['ethtool-autoneg']
643 if val in ["true", "on"]:
644 settings += ['autoneg', 'on']
645 elif val in ["false", "off"]:
646 settings += ['autoneg', 'off']
648 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
650 # Options for "ethtool -K"
652 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
653 if oc.has_key("ethtool-" + opt):
654 val = oc["ethtool-" + opt]
655 if val in ["true", "on"]:
656 offload += [opt, 'on']
657 elif val in ["false", "off"]:
658 offload += [opt, 'off']
660 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
662 return settings, offload
664 def configure_netdev(pif):
665 pifrec = db.get_pif_record(pif)
666 datapath = datapath_name(pif)
667 ipdev = ipdev_name(pif)
669 host = pifrec['host']
670 nw = pifrec['network']
671 nwrec = db.get_network_record(nw)
673 ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up']
675 if pifrec['ip_configuration_mode'] == "DHCP":
677 elif pifrec['ip_configuration_mode'] == "Static":
678 ifconfig_argv += [pifrec['IP']]
679 ifconfig_argv += ['netmask', pifrec['netmask']]
680 gateway = pifrec['gateway']
681 elif pifrec['ip_configuration_mode'] == "None":
685 raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode'])
688 if pifrec.has_key('other_config'):
689 oc = pifrec['other_config']
690 if oc.has_key('mtu'):
691 int(oc['mtu']) # Check that the value is an integer
692 ifconfig_argv += ['mtu', oc['mtu']]
694 run_command(ifconfig_argv)
696 (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif)
698 if peerdns_pif == pif:
699 f = ConfigurationFile('resolv.conf', "/etc")
700 if oc.has_key('domain'):
701 f.write("search %s\n" % oc['domain'])
702 for dns in pifrec['DNS'].split(","):
703 f.write("nameserver %s\n" % dns)
708 if defaultroute_pif == pif and gateway != '':
709 run_command(['/sbin/ip', 'route', 'replace', 'default',
710 'via', gateway, 'dev', ipdev])
712 if oc.has_key('static-routes'):
713 for line in oc['static-routes'].split(','):
714 network, masklen, gateway = line.split('/')
715 run_command(['/sbin/ip', 'route', 'add',
716 '%s/%s' % (netmask, masklen), 'via', gateway,
719 settings, offload = ethtool_settings(oc)
721 run_command(['/sbin/ethtool', '-s', ipdev] + settings)
723 run_command(['/sbin/ethtool', '-K', ipdev] + offload)
725 if pifrec['ip_configuration_mode'] == "DHCP":
727 print "Determining IP information for %s..." % ipdev,
728 argv = ['/sbin/dhclient', '-q',
729 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev,
730 '-pf', '/var/run/dhclient-%s.pid' % ipdev,
732 if run_command(argv):
737 def modify_config(commands):
738 run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer',
739 '-F', '/etc/ovs-vswitchd.conf']
741 run_command(['/sbin/service', 'vswitch', 'reload'])
743 def is_bond_pif(pif):
744 pifrec = db.get_pif_record(pif)
745 return len(pifrec['bond_master_of']) != 0
747 def configure_bond(pif):
748 pifrec = db.get_pif_record(pif)
749 interface = interface_name(pif)
750 ipdev = ipdev_name(pif)
751 datapath = datapath_name(pif)
752 physdevs = physdev_names(pif)
754 argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
755 argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
756 for slave in physdevs]
758 if pifrec['MAC'] != "":
759 argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
763 "mode": "balance-slb",
769 # override defaults with values from other-config whose keys
771 oc = pifrec['other_config']
772 overrides = filter(lambda (key,val):
773 key.startswith("bond-"), oc.items())
774 overrides = map(lambda (key,val): (key[5:], val), overrides)
775 bond_options.update(overrides)
776 for (name,val) in bond_options.items():
777 argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
781 pifrec = db.get_pif_record(pif)
783 bridge = bridge_name(pif)
784 interface = interface_name(pif)
785 ipdev = ipdev_name(pif)
786 datapath = datapath_name(pif)
787 physdevs = physdev_names(pif)
789 if pifrec['VLAN'] != '-1':
790 vlan_slave = get_vlan_slave_of_pif(pif)
791 if vlan_slave and is_bond_pif(vlan_slave):
792 bond_master = vlan_slave
793 elif is_bond_pif(pif):
798 bond_slaves = get_bond_slaves_of_pif(bond_master)
801 bond_masters = get_bond_masters_of_pif(pif)
803 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
804 # files up-to-date, even though we don't use them or need them.
805 f = configure_pif(pif)
806 mode = pifrec['ip_configuration_mode']
808 log("Configuring %s using %s configuration" % (bridge, mode))
809 br = open_network_ifcfg(pif)
810 configure_network(pif, br)
814 log("Configuring %s using %s configuration" % (interface, mode))
815 configure_network(pif, f)
817 for master in bond_masters:
818 master_bridge = bridge_name(master)
819 removed = unconfigure_pif(master)
820 f.attach_child(removed)
822 removed = open_network_ifcfg(master)
823 log("Unlinking stale file %s" % removed.path())
825 f.attach_child(removed)
827 # /etc/xensource/scripts/vif needs to know where to add VIFs.
829 if not os.path.exists(vswitch_config_dir):
830 os.mkdir(vswitch_config_dir)
831 br = ConfigurationFile("br-%s" % bridge, vswitch_config_dir)
832 br.write("VLAN_SLAVE=%s\n" % datapath)
833 br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
837 # Update all configuration files (both ours and Centos's).
841 # "ifconfig down" the network device and delete its IP address, etc.
843 for physdev in physdevs:
846 # If we are bringing up a bond, remove IP addresses from the
847 # slaves (because we are implicitly being asked to take them down).
849 # Conversely, if we are bringing up an interface that has bond
850 # masters, remove IP addresses from the bond master (because we
851 # are implicitly being asked to take it down).
852 for bond_pif in bond_slaves + bond_masters:
853 run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0'])
855 # Remove all keys related to pif and any bond masters linked to PIF.
856 del_ports = [ipdev] + physdevs + bond_masters
857 if vlan_slave and bond_master:
858 del_ports += [interface_name(bond_master)]
860 # What ports do we need to add to the datapath?
862 # We definitely need the ipdev, and ordinarily we want the
863 # physical devices too, but for bonds we need the bond as bridge
865 add_ports = [ipdev, datapath]
867 add_ports += physdevs
869 add_ports += [interface_name(bond_master)]
871 # What ports do we need to delete?
873 # - All the ports that we add, to avoid duplication and to drop
874 # them from another datapath in case they're misassigned.
876 # - The physical devices, since they will either be in add_ports
877 # or added to the bonding device (see below).
879 # - The bond masters for pif. (Ordinarily pif shouldn't have any
880 # bond masters. If it does then interface-reconfigure is
881 # implicitly being asked to take them down.)
882 del_ports = add_ports + physdevs + bond_masters
884 # What networks does this datapath carry?
886 # - The network corresponding to the datapath's PIF.
888 # - The networks corresponding to any VLANs attached to the
891 for nwpif in db.get_pifs_by_record({'device': pifrec['device'],
892 'host': pifrec['host']}):
893 net = db.get_pif_record(nwpif)['network']
894 network_uuids += [db.get_network_record(net)['uuid']]
896 # Bring up bond slaves early, because ovs-vswitchd initially
897 # enables or disables bond slaves based on whether carrier is
898 # detected when they are added, and a network device that is down
899 # always reports "no carrier".
900 bond_slave_physdevs = []
901 for slave in bond_slaves:
902 bond_slave_physdevs += physdev_names(slave)
903 for slave_physdev in bond_slave_physdevs:
904 up_netdev(slave_physdev)
906 # Now modify the ovs-vswitchd config file.
908 for port in set(del_ports):
909 argv += interface_deconfigure_commands(port)
910 for port in set(add_ports):
911 argv += ['--add=bridge.%s.port=%s' % (datapath, port)]
913 argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
914 argv += ['--add=iface.%s.internal=true' % (ipdev)]
916 # xapi creates a bridge by the name of the ipdev and requires
917 # that the IP address will be on it. We need to delete this
918 # bridge because we need that device to be a member of our
920 argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev]
922 # xapi insists that its attempts to create the bridge succeed,
923 # so force that to happen.
924 argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
927 os.unlink("%s/br-%s" % (vswitch_config_dir, bridge))
930 argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath]
931 argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid)
932 for uuid in set(network_uuids)]
934 argv += configure_bond(bond_master)
937 # Configure network devices.
938 configure_netdev(pif)
940 # Bring up VLAN slave, plus physical devices other than bond
941 # slaves (which we brought up earlier).
943 up_netdev(ipdev_name(vlan_slave))
944 for physdev in set(physdevs) - set(bond_slave_physdevs):
947 # Update /etc/issue (which contains the IP address of the management interface)
948 os.system("/sbin/update-issue")
951 # There seems to be a race somewhere: without this sleep, using
952 # XenCenter to create a bond that becomes the management interface
953 # fails with "The underlying connection was closed: A connection that
954 # was expected to be kept alive was closed by the server." on every
955 # second or third try, even though /var/log/messages doesn't show
958 # The race is probably present even without vswitch, but bringing up a
959 # bond without vswitch involves a built-in pause of 10 seconds or more
960 # to wait for the bond to transition from learning to forwarding state.
963 def action_down(pif):
964 rec = db.get_pif_record(pif)
965 interface = interface_name(pif)
966 bridge = bridge_name(pif)
967 ipdev = ipdev_name(pif)
969 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
970 # files up-to-date, even though we don't use them or need them.
971 f = unconfigure_pif(pif)
973 br = open_network_ifcfg(pif)
974 log("Unlinking stale file %s" % br.path())
981 log("action_down failed to apply changes: %s" % e.msg)
986 if rec['VLAN'] != '-1':
987 # Get rid of the VLAN device itself.
989 argv += interface_deconfigure_commands(ipdev)
991 # If the VLAN's slave is attached, stop here.
992 slave = get_vlan_slave_of_pif(pif)
993 if db.get_pif_record(slave)['currently_attached']:
994 log("VLAN slave is currently attached")
998 # If the VLAN's slave has other VLANs that are attached, stop here.
999 masters = get_vlan_masters_of_pif(slave)
1001 if m != pif and db.get_pif_record(m)['currently_attached']:
1002 log("VLAN slave has other master %s" % interface_naem(m))
1006 # Otherwise, take down the VLAN's slave too.
1007 log("No more masters, bring down vlan slave %s" % interface_name(slave))
1010 # Stop here if this PIF has attached VLAN masters.
1011 vlan_masters = get_vlan_masters_of_pif(pif)
1012 log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters]))
1013 for m in vlan_masters:
1014 if db.get_pif_record(m)['currently_attached']:
1015 log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m)))
1018 # pif is now either a bond or a physical device which needs to be
1019 # brought down. pif might have changed so re-check all its attributes.
1020 rec = db.get_pif_record(pif)
1021 interface = interface_name(pif)
1022 bridge = bridge_name(pif)
1023 ipdev = ipdev_name(pif)
1026 bond_slaves = get_bond_slaves_of_pif(pif)
1027 log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves]))
1028 for slave in bond_slaves:
1029 slave_interface = interface_name(slave)
1030 log("bring down bond slave %s" % slave_interface)
1031 argv += interface_deconfigure_commands(slave_interface)
1032 down_netdev(slave_interface)
1034 argv += interface_deconfigure_commands(ipdev)
1037 argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)]
1038 argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface]
1041 def action_rewrite(pif):
1042 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1043 # files up-to-date, even though we don't use them or need them.
1044 pifrec = db.get_pif_record(pif)
1045 f = configure_pif(pif)
1046 interface = interface_name(pif)
1047 bridge = bridge_name(pif)
1048 mode = pifrec['ip_configuration_mode']
1050 log("Configuring %s using %s configuration" % (bridge, mode))
1051 br = open_network_ifcfg(pif)
1052 configure_network(pif, br)
1056 log("Configuring %s using %s configuration" % (interface, mode))
1057 configure_network(pif, f)
1063 log("failed to apply changes: %s" % e.msg)
1067 # We have no code of our own to run here.
1070 def main(argv=None):
1071 global output_directory, management_pif
1077 force_interface = None
1078 force_management = False
1086 longops = [ "output-directory=",
1087 "pif=", "pif-uuid=",
1093 "device=", "mode=", "ip=", "netmask=", "gateway=",
1095 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1096 except getopt.GetoptError, msg:
1099 force_rewrite_config = {}
1102 if o == "--output-directory":
1103 output_directory = a
1106 elif o == "--pif-uuid":
1108 elif o == "--session":
1110 elif o == "--force-interface" or o == "--force":
1112 elif o == "--management":
1113 force_management = True
1114 elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1115 force_rewrite_config[o[2:]] = a
1116 elif o == "-h" or o == "--help":
1117 print __doc__ % {'command-name': os.path.basename(argv[0])}
1120 if not debug_mode():
1121 syslog.openlog(os.path.basename(argv[0]))
1122 log("Called as " + str.join(" ", argv))
1124 raise Usage("Required option <action> not present")
1126 raise Usage("Too many arguments")
1129 # backwards compatibility
1130 if action == "rewrite-configuration": action = "rewrite"
1132 if output_directory and ( session or pif ):
1133 raise Usage("--session/--pif cannot be used with --output-directory")
1134 if ( session or pif ) and pif_uuid:
1135 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1136 if ( session and not pif ) or ( not session and pif ):
1137 raise Usage("--session and --pif must be used together.")
1138 if force_interface and ( session or pif or pif_uuid ):
1139 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1140 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1141 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1145 log("Force interface %s %s" % (force_interface, action))
1147 if action == "rewrite":
1148 action_force_rewrite(force_interface, force_rewrite_config)
1150 db = DatabaseCache(cache_file=dbcache_file)
1151 host = db.extras['host']
1152 pif = db.get_pif_by_bridge(host, force_interface)
1153 management_pif = db.get_management_pif(host)
1157 elif action == "down":
1160 raise Usage("Unknown action %s" % action)
1162 db = DatabaseCache(session_ref=session)
1165 pif = db.get_pif_by_uuid(pif_uuid)
1168 raise Usage("No PIF given")
1170 if force_management:
1171 # pif is going to be the management pif
1172 management_pif = pif
1174 # pif is not going to be the management pif.
1175 # Search DB cache for pif on same host with management=true
1176 pifrec = db.get_pif_record(pif)
1177 host = pifrec['host']
1178 management_pif = db.get_management_pif(host)
1180 log_pif_action(action, pif)
1182 if not check_allowed(pif):
1187 elif action == "down":
1189 elif action == "rewrite":
1192 raise Usage("Unknown action %s" % action)
1195 pifrec = db.get_pif_record(pif)
1196 db.save(dbcache_file, {'host': pifrec['host']})
1199 print >>sys.stderr, err.msg
1200 print >>sys.stderr, "For help use --help."
1208 # The following code allows interface-reconfigure to keep Centos
1209 # network configuration files up-to-date, even though the vswitch
1210 # never uses them. In turn, that means that "rpm -e vswitch" does not
1211 # have to update any configuration files.
1213 def configure_ethtool(oc, f):
1214 # Options for "ethtool -s"
1216 setting_opts = ["autoneg", "speed", "duplex"]
1217 # Options for "ethtool -K"
1219 offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
1221 for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
1222 val = oc["ethtool-" + opt]
1224 if opt in ["speed"]:
1225 if val in ["10", "100", "1000"]:
1226 val = "speed " + val
1228 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
1230 elif opt in ["duplex"]:
1231 if val in ["half", "full"]:
1232 val = "duplex " + val
1234 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
1236 elif opt in ["autoneg"] + offload_opts:
1237 if val in ["true", "on"]:
1239 elif val in ["false", "off"]:
1242 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
1245 if opt in setting_opts:
1246 if val and settings:
1247 settings = settings + " " + val
1250 elif opt in offload_opts:
1252 offload = offload + " " + val
1257 f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
1259 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
1261 def configure_mtu(oc, f):
1262 if not oc.has_key('mtu'):
1266 mtu = int(oc['mtu'])
1267 f.write("MTU=%d\n" % mtu)
1268 except ValueError, x:
1269 log("Invalid value for mtu = %s" % mtu)
1271 def configure_static_routes(interface, oc, f):
1272 """Open a route-<interface> file for static routes.
1274 Opens the static routes configuration file for interface and writes one
1275 line for each route specified in the network's other config "static-routes" value.
1277 interface ( RO): xenbr1
1278 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1280 Then route-xenbr1 should be
1281 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1282 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1284 fname = "route-%s" % interface
1285 if oc.has_key('static-routes'):
1286 # The key is present - extract comma seperates entries
1287 lines = oc['static-routes'].split(',')
1289 # The key is not present, i.e. there are no static routes
1292 child = ConfigurationFile(fname)
1293 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1294 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
1298 network, masklen, gateway = l.split('/')
1299 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1301 f.attach_child(child)
1304 except ValueError, e:
1305 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1307 def __open_ifcfg(interface):
1308 """Open a network interface configuration file.
1310 Opens the configuration file for interface, writes a header and
1311 common options and returns the file object.
1313 fname = "ifcfg-%s" % interface
1314 f = ConfigurationFile(fname)
1316 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1317 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1318 f.write("XEMANAGED=yes\n")
1319 f.write("DEVICE=%s\n" % interface)
1320 f.write("ONBOOT=no\n")
1324 def open_network_ifcfg(pif):
1325 bridge = bridge_name(pif)
1326 interface = interface_name(pif)
1328 return __open_ifcfg(bridge)
1330 return __open_ifcfg(interface)
1333 def open_pif_ifcfg(pif):
1334 pifrec = db.get_pif_record(pif)
1336 log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
1338 f = __open_ifcfg(interface_name(pif))
1340 if pifrec.has_key('other_config'):
1341 configure_ethtool(pifrec['other_config'], f)
1342 configure_mtu(pifrec['other_config'], f)
1346 def configure_network(pif, f):
1347 """Write the configuration file for a network.
1349 Writes configuration derived from the network object into the relevant
1350 ifcfg file. The configuration file is passed in, but if the network is
1351 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1353 This routine may also write ifcfg files of the networks corresponding to other PIFs
1354 in order to maintain consistency.
1357 pif: Opaque_ref of pif
1358 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1361 pifrec = db.get_pif_record(pif)
1362 host = pifrec['host']
1363 nw = pifrec['network']
1364 nwrec = db.get_network_record(nw)
1366 bridge = bridge_name(pif)
1367 interface = interface_name(pif)
1373 if nwrec.has_key('other_config'):
1374 configure_ethtool(nwrec['other_config'], f)
1375 configure_mtu(nwrec['other_config'], f)
1376 configure_static_routes(device, nwrec['other_config'], f)
1379 if pifrec.has_key('other_config'):
1380 oc = pifrec['other_config']
1382 if device == bridge:
1383 f.write("TYPE=Bridge\n")
1384 f.write("DELAY=0\n")
1385 f.write("STP=off\n")
1386 f.write("PIFDEV=%s\n" % interface_name(pif))
1388 if pifrec['ip_configuration_mode'] == "DHCP":
1389 f.write("BOOTPROTO=dhcp\n")
1390 f.write("PERSISTENT_DHCLIENT=yes\n")
1391 elif pifrec['ip_configuration_mode'] == "Static":
1392 f.write("BOOTPROTO=none\n")
1393 f.write("NETMASK=%(netmask)s\n" % pifrec)
1394 f.write("IPADDR=%(IP)s\n" % pifrec)
1395 f.write("GATEWAY=%(gateway)s\n" % pifrec)
1396 elif pifrec['ip_configuration_mode'] == "None":
1397 f.write("BOOTPROTO=none\n")
1399 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1401 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1402 ServerList = pifrec['DNS'].split(",")
1403 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1404 if oc and oc.has_key('domain'):
1405 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1407 # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1408 # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1409 # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1411 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1413 # Note: we prune out the bond master pif (if it exists).
1414 # This is because when we are called to bring up an interface with a bond master, it is implicit that
1415 # we should bring down that master.
1416 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1417 db.get_pif_record(__pif)['host'] == host and
1418 (not __pif in get_bond_masters_of_pif(pif)) ]
1419 other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1422 defaultroute_pif = None
1424 # loop through all the pifs on this host looking for one with
1425 # other-config:peerdns = true, and one with
1426 # other-config:default-route=true
1427 for __pif in pifs_on_host:
1428 __pifrec = db.get_pif_record(__pif)
1429 __oc = __pifrec['other_config']
1430 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1431 if peerdns_pif == None:
1434 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1435 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1436 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1437 if defaultroute_pif == None:
1438 defaultroute_pif = __pif
1440 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1441 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1443 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1444 if peerdns_pif == None:
1445 peerdns_pif = management_pif
1446 if defaultroute_pif == None:
1447 defaultroute_pif = management_pif
1449 # Update all the other network's ifcfg files and ensure consistency
1450 for __pif in other_pifs_on_host:
1451 __f = open_network_ifcfg(__pif)
1452 peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1453 lines = __f.readlines()
1455 if not peerdns_line_wanted in lines:
1456 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1458 if not line.lstrip().startswith('PEERDNS'):
1460 log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1461 __f.write(peerdns_line_wanted)
1466 # There is no need to change this ifcfg file. So don't attach_child.
1469 # ... and for this pif too
1470 f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1473 fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1474 for line in fnetwork.readlines():
1475 if line.lstrip().startswith('GATEWAY') :
1477 fnetwork.write(line)
1478 if defaultroute_pif:
1479 gatewaydev = bridge_name(defaultroute_pif)
1481 gatewaydev = interface_name(defaultroute_pif)
1482 fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1484 f.attach_child(fnetwork)
1489 def configure_physical_interface(pif):
1490 """Write the configuration for a physical interface.
1492 Writes the configuration file for the physical interface described by
1495 Returns the open file handle for the interface configuration file.
1498 pifrec = db.get_pif_record(pif)
1500 f = open_pif_ifcfg(pif)
1502 f.write("TYPE=Ethernet\n")
1503 f.write("HWADDR=%(MAC)s\n" % pifrec)
1507 def configure_bond_interface(pif):
1508 """Write the configuration for a bond interface.
1510 Writes the configuration file for the bond interface described by
1511 the pif object. Handles writing the configuration for the slave
1514 Returns the open file handle for the bond interface configuration
1518 pifrec = db.get_pif_record(pif)
1519 oc = pifrec['other_config']
1520 f = open_pif_ifcfg(pif)
1522 if pifrec['MAC'] != "":
1523 f.write("MACADDR=%s\n" % pifrec['MAC'])
1525 for slave in get_bond_slaves_of_pif(pif):
1526 s = configure_physical_interface(slave)
1527 s.write("MASTER=%(device)s\n" % pifrec)
1528 s.write("SLAVE=yes\n")
1532 # The bond option defaults
1534 "mode": "balance-slb",
1541 # override defaults with values from other-config whose keys being with "bond-"
1542 overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
1543 overrides = map(lambda (key,val): (key[5:], val), overrides)
1544 bond_options.update(overrides)
1546 # write the bond options to ifcfg-bondX
1547 f.write('BONDING_OPTS="')
1548 for (name,val) in bond_options.items():
1549 f.write("%s=%s " % (name,val))
1553 def configure_vlan_interface(pif):
1554 """Write the configuration for a VLAN interface.
1556 Writes the configuration file for the VLAN interface described by
1557 the pif object. Handles writing the configuration for the master
1558 interface if necessary.
1560 Returns the open file handle for the VLAN interface configuration
1564 slave = configure_pif(get_vlan_slave_of_pif(pif))
1567 f = open_pif_ifcfg(pif)
1568 f.write("VLAN=yes\n")
1569 f.attach_child(slave)
1573 def configure_pif(pif):
1574 """Write the configuration for a PIF object.
1576 Writes the configuration file the PIF and all dependent
1577 interfaces (bond slaves and VLAN masters etc).
1579 Returns the open file handle for the interface configuration file.
1582 pifrec = db.get_pif_record(pif)
1584 if pifrec['VLAN'] != '-1':
1585 f = configure_vlan_interface(pif)
1586 elif len(pifrec['bond_master_of']) != 0:
1587 f = configure_bond_interface(pif)
1589 f = configure_physical_interface(pif)
1591 bridge = bridge_name(pif)
1593 f.write("BRIDGE=%s\n" % bridge)
1597 def unconfigure_pif(pif):
1598 """Clear up the files created by configure_pif"""
1599 f = open_pif_ifcfg(pif)
1600 log("Unlinking stale file %s" % f.path())
1604 if __name__ == "__main__":
1610 err = traceback.format_exception(*ex)
1614 if not debug_mode():