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 __read_xensource_inventory(self):
253 filename = "/etc/xensource-inventory"
254 f = open(filename, "r")
255 lines = [x.strip("\n") for x in f.readlines()]
258 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
259 defs = [ (a, b.strip("'")) for (a,b) in defs ]
263 def __pif_on_host(self,pif):
264 return self.__pifs.has_key(pif)
266 def __get_pif_records_from_xapi(self, session, host):
267 recs = session.xenapi.PIF.get_all_records()
268 self.__pifs = dict(filter(lambda (ref,rec): rec['host'] == host, recs.iteritems()))
270 def __get_vlan_records_from_xapi(self, session):
271 self.__vlans = dict(filter(lambda (ref,rec): self.__pif_on_host(rec['untagged_PIF']),
272 session.xenapi.VLAN.get_all_records().iteritems()))
274 def __get_bond_records_from_xapi(self, session):
275 self.__bonds = dict(filter(lambda (ref,rec): self.__pif_on_host(rec['master']),
276 session.xenapi.Bond.get_all_records().iteritems()))
278 def __get_network_records_from_xapi(self, session):
279 self.__networks = session.xenapi.network.get_all_records()
281 def __init__(self, session_ref=None, cache_file=None):
282 if session_ref and cache_file:
283 raise Error("can't specify session reference and cache file")
285 if cache_file == None:
286 session = XenAPI.xapi_local()
289 log("No session ref given on command line, logging in.")
290 session.xenapi.login_with_password("root", "")
292 session._session = session_ref
296 inventory = self.__read_xensource_inventory()
297 assert(inventory.has_key('INSTALLATION_UUID'))
298 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
300 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
302 self.__get_pif_records_from_xapi(session, host)
304 self.__get_vlan_records_from_xapi(session)
305 self.__get_bond_records_from_xapi(session)
306 self.__get_network_records_from_xapi(session)
309 session.xenapi.session.logout()
311 log("Loading xapi database cache from %s" % cache_file)
312 f = open(cache_file, 'r')
313 members = pickle.load(f)
316 self.__vlans = members['vlans']
317 self.__bonds = members['bonds']
318 self.__pifs = members['pifs']
319 self.__networks = members['networks']
321 def save(self, cache_file):
322 f = open(cache_file, 'w')
323 pickle.dump({'vlans': self.__vlans,
324 'bonds': self.__bonds,
326 'networks': self.__networks}, f)
329 def get_pif_by_uuid(self, uuid):
330 pifs = map(lambda (ref,rec): ref,
331 filter(lambda (ref,rec): uuid == rec['uuid'],
332 self.__pifs.items()))
334 raise Error("Unknown PIF \"%s\"" % uuid)
336 raise Error("Non-unique PIF \"%s\"" % uuid)
340 def get_pifs_by_device(self, device):
341 return map(lambda (ref,rec): ref,
342 filter(lambda (ref,rec): rec['device'] == device,
343 self.__pifs.items()))
345 def get_pif_by_bridge(self, bridge):
346 networks = map(lambda (ref,rec): ref,
347 filter(lambda (ref,rec): rec['bridge'] == bridge,
348 self.__networks.items()))
349 if len(networks) == 0:
350 raise Error("No matching network \"%s\"")
353 for network in networks:
354 nwrec = self.get_network_record(network)
355 for pif in nwrec['PIFs']:
356 pifrec = self.get_pif_record(pif)
358 raise Error("Multiple PIFs on host for network %s" % (bridge))
361 raise Error("No PIF on host for network %s" % (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):
374 """ Returns the management pif on host
376 all = self.get_all_pifs()
378 pifrec = self.get_pif_record(pif)
379 if pifrec['management']: return pif
382 def get_network_record(self, network):
383 if self.__networks.has_key(network):
384 return self.__networks[network]
385 raise Error("Unknown network \"%s\"" % network)
386 def get_all_networks(self):
387 return self.__networks
389 def get_bond_record(self, bond):
390 if self.__bonds.has_key(bond):
391 return self.__bonds[bond]
395 def get_vlan_record(self, vlan):
396 if self.__vlans.has_key(vlan):
397 return self.__vlans[vlan]
401 def bridge_name(pif):
402 """Return the bridge name associated with pif, or None if network is bridgeless"""
403 pifrec = db.get_pif_record(pif)
404 nwrec = db.get_network_record(pifrec['network'])
407 # TODO: sanity check that nwrec['bridgeless'] != 'true'
408 return nwrec['bridge']
410 # TODO: sanity check that nwrec['bridgeless'] == 'true'
413 def interface_name(pif):
414 """Construct an interface name from the given PIF record."""
416 pifrec = db.get_pif_record(pif)
418 if pifrec['VLAN'] == '-1':
419 return pifrec['device']
421 return "%(device)s.%(VLAN)s" % pifrec
423 def datapath_name(pif):
424 """Return the OpenFlow datapath name associated with pif.
425 For a non-VLAN PIF, the datapath name is the bridge name.
426 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
427 (xapi will create a datapath named with the bridge name even though we won't
431 pifrec = db.get_pif_record(pif)
433 if pifrec['VLAN'] == '-1':
434 return bridge_name(pif)
436 return bridge_name(get_vlan_slave_of_pif(pif))
439 """Return the the name of the network device that carries the
440 IP configuration (if any) associated with pif.
441 The ipdev name is the same as the bridge name.
444 pifrec = db.get_pif_record(pif)
445 return bridge_name(pif)
447 def physdev_names(pif):
448 """Return the name(s) of the physical network device(s) associated with pif.
449 For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
450 For a bond master PIF, the physical devices are the bond slaves.
451 For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
454 pifrec = db.get_pif_record(pif)
456 if pifrec['VLAN'] != '-1':
457 return physdev_names(get_vlan_slave_of_pif(pif))
458 elif len(pifrec['bond_master_of']) != 0:
460 for slave in get_bond_slaves_of_pif(pif):
461 physdevs += physdev_names(slave)
464 return [pifrec['device']]
466 def log_pif_action(action, pif):
467 pifrec = db.get_pif_record(pif)
468 pifrec['action'] = action
469 pifrec['interface-name'] = interface_name(pif)
470 if action == "rewrite":
471 pifrec['message'] = "Rewrite PIF %(uuid)s configuration" % pifrec
473 pifrec['message'] = "Bring %(action)s PIF %(uuid)s" % pifrec
474 log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % pifrec)
476 def get_bond_masters_of_pif(pif):
477 """Returns a list of PIFs which are bond masters of this PIF"""
479 pifrec = db.get_pif_record(pif)
481 bso = pifrec['bond_slave_of']
483 # bond-slave-of is currently a single reference but in principle a
484 # PIF could be a member of several bonds which are not
485 # concurrently attached. Be robust to this possibility.
486 if not bso or bso == "OpaqueRef:NULL":
488 elif not type(bso) == list:
491 bondrecs = [db.get_bond_record(bond) for bond in bso]
492 bondrecs = [rec for rec in bondrecs if rec]
494 return [bond['master'] for bond in bondrecs]
496 def get_bond_slaves_of_pif(pif):
497 """Returns a list of PIFs which make up the given bonded pif."""
499 pifrec = db.get_pif_record(pif)
501 bmo = pifrec['bond_master_of']
503 raise Error("Bond-master-of contains too many elements")
508 bondrec = db.get_bond_record(bmo[0])
510 raise Error("No bond record for bond master PIF")
512 return bondrec['slaves']
514 def get_vlan_slave_of_pif(pif):
515 """Find the PIF which is the VLAN slave of pif.
517 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
519 pifrec = db.get_pif_record(pif)
521 vlan = pifrec['VLAN_master_of']
522 if not vlan or vlan == "OpaqueRef:NULL":
523 raise Error("PIF is not a VLAN master")
525 vlanrec = db.get_vlan_record(vlan)
527 raise Error("No VLAN record found for PIF")
529 return vlanrec['tagged_PIF']
531 def get_vlan_masters_of_pif(pif):
532 """Returns a list of PIFs which are VLANs on top of the given pif."""
534 pifrec = db.get_pif_record(pif)
535 vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
536 return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])]
538 def interface_deconfigure_commands(interface):
539 # The use of [!0-9] keeps an interface of 'eth0' from matching
540 # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
542 return ['--del-match=bridge.*.port=%s' % interface,
543 '--del-match=bonding.%s.[!0-9]*' % interface,
544 '--del-match=bonding.*.slave=%s' % interface,
545 '--del-match=vlan.%s.[!0-9]*' % interface,
546 '--del-match=port.%s.[!0-9]*' % interface,
547 '--del-match=iface.%s.[!0-9]*' % interface]
549 def run_command(command):
550 log("Running command: " + ' '.join(command))
551 if os.spawnl(os.P_WAIT, command[0], *command) != 0:
552 log("Command failed: " + ' '.join(command))
556 def down_netdev(interface, deconfigure=True):
557 if not interface_exists(interface):
558 log("down_netdev: interface %s does not exist, ignoring" % interface)
560 argv = ["/sbin/ifconfig", interface, 'down']
565 pidfile_name = '/var/run/dhclient-%s.pid' % interface
568 pidfile = open(pidfile_name, 'r')
569 os.kill(int(pidfile.readline()), signal.SIGTERM)
575 # Remove dhclient pidfile.
577 os.remove(pidfile_name)
582 def up_netdev(interface):
583 run_command(["/sbin/ifconfig", interface, 'up'])
585 def find_distinguished_pifs(pif):
586 """Returns the PIFs on host that own DNS and the default route.
587 The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
588 The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
590 Note: we prune out the bond master pif (if it exists).
591 This is because when we are called to bring up an interface with a bond master, it is implicit that
592 we should bring down that master."""
594 pifrec = db.get_pif_record(pif)
596 pifs = [ __pif for __pif in db.get_all_pifs() if
597 (not __pif in get_bond_masters_of_pif(pif)) ]
600 defaultroute_pif = None
602 # loop through all the pifs on this host looking for one with
603 # other-config:peerdns = true, and one with
604 # other-config:default-route=true
606 __pifrec = db.get_pif_record(__pif)
607 __oc = __pifrec['other_config']
608 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
609 if peerdns_pif == None:
612 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
613 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
614 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
615 if defaultroute_pif == None:
616 defaultroute_pif = __pif
618 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
619 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
621 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
622 if peerdns_pif == None:
623 peerdns_pif = management_pif
624 if defaultroute_pif == None:
625 defaultroute_pif = management_pif
627 return peerdns_pif, defaultroute_pif
629 def ethtool_settings(oc):
630 # Options for "ethtool -s"
632 if oc.has_key('ethtool-speed'):
633 val = oc['ethtool-speed']
634 if val in ["10", "100", "1000"]:
635 settings += ['speed', val]
637 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
638 if oc.has_key('ethtool-duplex'):
639 val = oc['ethtool-duplex']
640 if val in ["10", "100", "1000"]:
641 settings += ['duplex', 'val']
643 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
644 if oc.has_key('ethtool-autoneg'):
645 val = oc['ethtool-autoneg']
646 if val in ["true", "on"]:
647 settings += ['autoneg', 'on']
648 elif val in ["false", "off"]:
649 settings += ['autoneg', 'off']
651 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
653 # Options for "ethtool -K"
655 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
656 if oc.has_key("ethtool-" + opt):
657 val = oc["ethtool-" + opt]
658 if val in ["true", "on"]:
659 offload += [opt, 'on']
660 elif val in ["false", "off"]:
661 offload += [opt, 'off']
663 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
665 return settings, offload
667 def configure_netdev(pif):
668 pifrec = db.get_pif_record(pif)
669 datapath = datapath_name(pif)
670 ipdev = ipdev_name(pif)
672 nw = pifrec['network']
673 nwrec = db.get_network_record(nw)
675 ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up']
677 if pifrec['ip_configuration_mode'] == "DHCP":
679 elif pifrec['ip_configuration_mode'] == "Static":
680 ifconfig_argv += [pifrec['IP']]
681 ifconfig_argv += ['netmask', pifrec['netmask']]
682 gateway = pifrec['gateway']
683 elif pifrec['ip_configuration_mode'] == "None":
687 raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode'])
690 if pifrec.has_key('other_config'):
691 oc = pifrec['other_config']
692 if oc.has_key('mtu'):
693 int(oc['mtu']) # Check that the value is an integer
694 ifconfig_argv += ['mtu', oc['mtu']]
696 run_command(ifconfig_argv)
698 (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif)
700 if peerdns_pif == pif:
701 f = ConfigurationFile('resolv.conf', "/etc")
702 if oc.has_key('domain'):
703 f.write("search %s\n" % oc['domain'])
704 for dns in pifrec['DNS'].split(","):
705 f.write("nameserver %s\n" % dns)
710 if defaultroute_pif == pif and gateway != '':
711 run_command(['/sbin/ip', 'route', 'replace', 'default',
712 'via', gateway, 'dev', ipdev])
714 if oc.has_key('static-routes'):
715 for line in oc['static-routes'].split(','):
716 network, masklen, gateway = line.split('/')
717 run_command(['/sbin/ip', 'route', 'add',
718 '%s/%s' % (netmask, masklen), 'via', gateway,
721 settings, offload = ethtool_settings(oc)
723 run_command(['/sbin/ethtool', '-s', ipdev] + settings)
725 run_command(['/sbin/ethtool', '-K', ipdev] + offload)
727 if pifrec['ip_configuration_mode'] == "DHCP":
729 print "Determining IP information for %s..." % ipdev,
730 argv = ['/sbin/dhclient', '-q',
731 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev,
732 '-pf', '/var/run/dhclient-%s.pid' % ipdev,
734 if run_command(argv):
739 def modify_config(commands):
740 run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer',
741 '-F', '/etc/ovs-vswitchd.conf']
743 run_command(['/sbin/service', 'vswitch', 'reload'])
745 def is_bond_pif(pif):
746 pifrec = db.get_pif_record(pif)
747 return len(pifrec['bond_master_of']) != 0
749 def configure_bond(pif):
750 pifrec = db.get_pif_record(pif)
751 interface = interface_name(pif)
752 ipdev = ipdev_name(pif)
753 datapath = datapath_name(pif)
754 physdevs = physdev_names(pif)
756 argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
757 argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
758 for slave in physdevs]
762 "mode": "balance-slb",
768 # override defaults with values from other-config whose keys
770 oc = pifrec['other_config']
771 overrides = filter(lambda (key,val):
772 key.startswith("bond-"), oc.items())
773 overrides = map(lambda (key,val): (key[5:], val), overrides)
774 bond_options.update(overrides)
775 for (name,val) in bond_options.items():
776 argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
780 pifrec = db.get_pif_record(pif)
782 bridge = bridge_name(pif)
783 interface = interface_name(pif)
784 ipdev = ipdev_name(pif)
785 datapath = datapath_name(pif)
786 physdevs = physdev_names(pif)
788 if pifrec['VLAN'] != '-1':
789 vlan_slave = get_vlan_slave_of_pif(pif)
790 if vlan_slave and is_bond_pif(vlan_slave):
791 bond_master = vlan_slave
792 elif is_bond_pif(pif):
797 bond_slaves = get_bond_slaves_of_pif(bond_master)
800 bond_masters = get_bond_masters_of_pif(pif)
802 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
803 # files up-to-date, even though we don't use them or need them.
804 f = configure_pif(pif)
805 mode = pifrec['ip_configuration_mode']
807 log("Configuring %s using %s configuration" % (bridge, mode))
808 br = open_network_ifcfg(pif)
809 configure_network(pif, br)
813 log("Configuring %s using %s configuration" % (interface, mode))
814 configure_network(pif, f)
816 for master in bond_masters:
817 master_bridge = bridge_name(master)
818 removed = unconfigure_pif(master)
819 f.attach_child(removed)
821 removed = open_network_ifcfg(master)
822 log("Unlinking stale file %s" % removed.path())
824 f.attach_child(removed)
826 # /etc/xensource/scripts/vif needs to know where to add VIFs.
828 if not os.path.exists(vswitch_config_dir):
829 os.mkdir(vswitch_config_dir)
830 br = ConfigurationFile("br-%s" % bridge, vswitch_config_dir)
831 br.write("VLAN_SLAVE=%s\n" % datapath)
832 br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
836 # Update all configuration files (both ours and Centos's).
840 # "ifconfig down" the network device and delete its IP address, etc.
842 for physdev in physdevs:
845 # If we are bringing up a bond, remove IP addresses from the
846 # slaves (because we are implicitly being asked to take them down).
848 # Conversely, if we are bringing up an interface that has bond
849 # masters, remove IP addresses from the bond master (because we
850 # are implicitly being asked to take it down).
851 for bond_pif in bond_slaves + bond_masters:
852 run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0'])
854 # Remove all keys related to pif and any bond masters linked to PIF.
855 del_ports = [ipdev] + physdevs + bond_masters
856 if vlan_slave and bond_master:
857 del_ports += [interface_name(bond_master)]
859 # What ports do we need to add to the datapath?
861 # We definitely need the ipdev, and ordinarily we want the
862 # physical devices too, but for bonds we need the bond as bridge
864 add_ports = [ipdev, datapath]
866 add_ports += physdevs
868 add_ports += [interface_name(bond_master)]
870 # What ports do we need to delete?
872 # - All the ports that we add, to avoid duplication and to drop
873 # them from another datapath in case they're misassigned.
875 # - The physical devices, since they will either be in add_ports
876 # or added to the bonding device (see below).
878 # - The bond masters for pif. (Ordinarily pif shouldn't have any
879 # bond masters. If it does then interface-reconfigure is
880 # implicitly being asked to take them down.)
881 del_ports = add_ports + physdevs + bond_masters
883 # What networks does this datapath carry?
885 # - The network corresponding to the datapath's PIF.
887 # - The networks corresponding to any VLANs attached to the
890 for nwpif in db.get_pifs_by_device({'device': pifrec['device']}):
891 net = db.get_pif_record(nwpif)['network']
892 network_uuids += [db.get_network_record(net)['uuid']]
894 # Bring up bond slaves early, because ovs-vswitchd initially
895 # enables or disables bond slaves based on whether carrier is
896 # detected when they are added, and a network device that is down
897 # always reports "no carrier".
898 bond_slave_physdevs = []
899 for slave in bond_slaves:
900 bond_slave_physdevs += physdev_names(slave)
901 for slave_physdev in bond_slave_physdevs:
902 up_netdev(slave_physdev)
904 # Now modify the ovs-vswitchd config file.
906 for port in set(del_ports):
907 argv += interface_deconfigure_commands(port)
908 for port in set(add_ports):
909 argv += ['--add=bridge.%s.port=%s' % (datapath, port)]
911 argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
912 argv += ['--add=iface.%s.internal=true' % (ipdev)]
914 # xapi creates a bridge by the name of the ipdev and requires
915 # that the IP address will be on it. We need to delete this
916 # bridge because we need that device to be a member of our
918 argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev]
920 # xapi insists that its attempts to create the bridge succeed,
921 # so force that to happen.
922 argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
925 os.unlink("%s/br-%s" % (vswitch_config_dir, bridge))
928 argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath]
929 argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid)
930 for uuid in set(network_uuids)]
932 argv += configure_bond(bond_master)
935 # Configure network devices.
936 configure_netdev(pif)
938 # Bring up VLAN slave, plus physical devices other than bond
939 # slaves (which we brought up earlier).
941 up_netdev(ipdev_name(vlan_slave))
942 for physdev in set(physdevs) - set(bond_slave_physdevs):
945 # Update /etc/issue (which contains the IP address of the management interface)
946 os.system("/sbin/update-issue")
949 # There seems to be a race somewhere: without this sleep, using
950 # XenCenter to create a bond that becomes the management interface
951 # fails with "The underlying connection was closed: A connection that
952 # was expected to be kept alive was closed by the server." on every
953 # second or third try, even though /var/log/messages doesn't show
956 # The race is probably present even without vswitch, but bringing up a
957 # bond without vswitch involves a built-in pause of 10 seconds or more
958 # to wait for the bond to transition from learning to forwarding state.
961 def action_down(pif):
962 rec = db.get_pif_record(pif)
963 interface = interface_name(pif)
964 bridge = bridge_name(pif)
965 ipdev = ipdev_name(pif)
967 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
968 # files up-to-date, even though we don't use them or need them.
969 f = unconfigure_pif(pif)
971 br = open_network_ifcfg(pif)
972 log("Unlinking stale file %s" % br.path())
979 log("action_down failed to apply changes: %s" % e.msg)
984 if rec['VLAN'] != '-1':
985 # Get rid of the VLAN device itself.
987 argv += interface_deconfigure_commands(ipdev)
989 # If the VLAN's slave is attached, stop here.
990 slave = get_vlan_slave_of_pif(pif)
991 if db.get_pif_record(slave)['currently_attached']:
992 log("VLAN slave is currently attached")
996 # If the VLAN's slave has other VLANs that are attached, stop here.
997 masters = get_vlan_masters_of_pif(slave)
999 if m != pif and db.get_pif_record(m)['currently_attached']:
1000 log("VLAN slave has other master %s" % interface_naem(m))
1004 # Otherwise, take down the VLAN's slave too.
1005 log("No more masters, bring down vlan slave %s" % interface_name(slave))
1008 # Stop here if this PIF has attached VLAN masters.
1009 vlan_masters = get_vlan_masters_of_pif(pif)
1010 log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters]))
1011 for m in vlan_masters:
1012 if db.get_pif_record(m)['currently_attached']:
1013 log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m)))
1016 # pif is now either a bond or a physical device which needs to be
1017 # brought down. pif might have changed so re-check all its attributes.
1018 rec = db.get_pif_record(pif)
1019 interface = interface_name(pif)
1020 bridge = bridge_name(pif)
1021 ipdev = ipdev_name(pif)
1024 bond_slaves = get_bond_slaves_of_pif(pif)
1025 log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves]))
1026 for slave in bond_slaves:
1027 slave_interface = interface_name(slave)
1028 log("bring down bond slave %s" % slave_interface)
1029 argv += interface_deconfigure_commands(slave_interface)
1030 down_netdev(slave_interface)
1032 argv += interface_deconfigure_commands(ipdev)
1035 argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)]
1036 argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface]
1039 def action_rewrite(pif):
1040 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1041 # files up-to-date, even though we don't use them or need them.
1042 pifrec = db.get_pif_record(pif)
1043 f = configure_pif(pif)
1044 interface = interface_name(pif)
1045 bridge = bridge_name(pif)
1046 mode = pifrec['ip_configuration_mode']
1048 log("Configuring %s using %s configuration" % (bridge, mode))
1049 br = open_network_ifcfg(pif)
1050 configure_network(pif, br)
1054 log("Configuring %s using %s configuration" % (interface, mode))
1055 configure_network(pif, f)
1061 log("failed to apply changes: %s" % e.msg)
1065 # We have no code of our own to run here.
1068 def main(argv=None):
1069 global output_directory, management_pif
1075 force_interface = None
1076 force_management = False
1084 longops = [ "output-directory=",
1085 "pif=", "pif-uuid=",
1091 "device=", "mode=", "ip=", "netmask=", "gateway=",
1093 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1094 except getopt.GetoptError, msg:
1097 force_rewrite_config = {}
1100 if o == "--output-directory":
1101 output_directory = a
1104 elif o == "--pif-uuid":
1106 elif o == "--session":
1108 elif o == "--force-interface" or o == "--force":
1110 elif o == "--management":
1111 force_management = True
1112 elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1113 force_rewrite_config[o[2:]] = a
1114 elif o == "-h" or o == "--help":
1115 print __doc__ % {'command-name': os.path.basename(argv[0])}
1118 if not debug_mode():
1119 syslog.openlog(os.path.basename(argv[0]))
1120 log("Called as " + str.join(" ", argv))
1122 raise Usage("Required option <action> not present")
1124 raise Usage("Too many arguments")
1127 # backwards compatibility
1128 if action == "rewrite-configuration": action = "rewrite"
1130 if output_directory and ( session or pif ):
1131 raise Usage("--session/--pif cannot be used with --output-directory")
1132 if ( session or pif ) and pif_uuid:
1133 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1134 if ( session and not pif ) or ( not session and pif ):
1135 raise Usage("--session and --pif must be used together.")
1136 if force_interface and ( session or pif or pif_uuid ):
1137 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1138 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1139 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1143 log("Force interface %s %s" % (force_interface, action))
1145 if action == "rewrite":
1146 action_force_rewrite(force_interface, force_rewrite_config)
1148 db = DatabaseCache(cache_file=dbcache_file)
1149 pif = db.get_pif_by_bridge(force_interface)
1150 management_pif = db.get_management_pif()
1154 elif action == "down":
1157 raise Usage("Unknown action %s" % action)
1159 db = DatabaseCache(session_ref=session)
1162 pif = db.get_pif_by_uuid(pif_uuid)
1165 raise Usage("No PIF given")
1167 if force_management:
1168 # pif is going to be the management pif
1169 management_pif = pif
1171 # pif is not going to be the management pif.
1172 # Search DB cache for pif on same host with management=true
1173 pifrec = db.get_pif_record(pif)
1174 management_pif = db.get_management_pif()
1176 log_pif_action(action, pif)
1178 if not check_allowed(pif):
1183 elif action == "down":
1185 elif action == "rewrite":
1188 raise Usage("Unknown action %s" % action)
1191 pifrec = db.get_pif_record(pif)
1192 db.save(dbcache_file)
1195 print >>sys.stderr, err.msg
1196 print >>sys.stderr, "For help use --help."
1204 # The following code allows interface-reconfigure to keep Centos
1205 # network configuration files up-to-date, even though the vswitch
1206 # never uses them. In turn, that means that "rpm -e vswitch" does not
1207 # have to update any configuration files.
1209 def configure_ethtool(oc, f):
1210 # Options for "ethtool -s"
1212 setting_opts = ["autoneg", "speed", "duplex"]
1213 # Options for "ethtool -K"
1215 offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
1217 for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
1218 val = oc["ethtool-" + opt]
1220 if opt in ["speed"]:
1221 if val in ["10", "100", "1000"]:
1222 val = "speed " + val
1224 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
1226 elif opt in ["duplex"]:
1227 if val in ["half", "full"]:
1228 val = "duplex " + val
1230 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
1232 elif opt in ["autoneg"] + offload_opts:
1233 if val in ["true", "on"]:
1235 elif val in ["false", "off"]:
1238 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
1241 if opt in setting_opts:
1242 if val and settings:
1243 settings = settings + " " + val
1246 elif opt in offload_opts:
1248 offload = offload + " " + val
1253 f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
1255 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
1257 def configure_mtu(oc, f):
1258 if not oc.has_key('mtu'):
1262 mtu = int(oc['mtu'])
1263 f.write("MTU=%d\n" % mtu)
1264 except ValueError, x:
1265 log("Invalid value for mtu = %s" % mtu)
1267 def configure_static_routes(interface, oc, f):
1268 """Open a route-<interface> file for static routes.
1270 Opens the static routes configuration file for interface and writes one
1271 line for each route specified in the network's other config "static-routes" value.
1273 interface ( RO): xenbr1
1274 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1276 Then route-xenbr1 should be
1277 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1278 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1280 fname = "route-%s" % interface
1281 if oc.has_key('static-routes'):
1282 # The key is present - extract comma seperates entries
1283 lines = oc['static-routes'].split(',')
1285 # The key is not present, i.e. there are no static routes
1288 child = ConfigurationFile(fname)
1289 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1290 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
1294 network, masklen, gateway = l.split('/')
1295 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1297 f.attach_child(child)
1300 except ValueError, e:
1301 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1303 def __open_ifcfg(interface):
1304 """Open a network interface configuration file.
1306 Opens the configuration file for interface, writes a header and
1307 common options and returns the file object.
1309 fname = "ifcfg-%s" % interface
1310 f = ConfigurationFile(fname)
1312 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1313 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1314 f.write("XEMANAGED=yes\n")
1315 f.write("DEVICE=%s\n" % interface)
1316 f.write("ONBOOT=no\n")
1320 def open_network_ifcfg(pif):
1321 bridge = bridge_name(pif)
1322 interface = interface_name(pif)
1324 return __open_ifcfg(bridge)
1326 return __open_ifcfg(interface)
1329 def open_pif_ifcfg(pif):
1330 pifrec = db.get_pif_record(pif)
1332 log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
1334 f = __open_ifcfg(interface_name(pif))
1336 if pifrec.has_key('other_config'):
1337 configure_ethtool(pifrec['other_config'], f)
1338 configure_mtu(pifrec['other_config'], f)
1342 def configure_network(pif, f):
1343 """Write the configuration file for a network.
1345 Writes configuration derived from the network object into the relevant
1346 ifcfg file. The configuration file is passed in, but if the network is
1347 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1349 This routine may also write ifcfg files of the networks corresponding to other PIFs
1350 in order to maintain consistency.
1353 pif: Opaque_ref of pif
1354 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1357 pifrec = db.get_pif_record(pif)
1358 nw = pifrec['network']
1359 nwrec = db.get_network_record(nw)
1361 bridge = bridge_name(pif)
1362 interface = interface_name(pif)
1368 if nwrec.has_key('other_config'):
1369 configure_ethtool(nwrec['other_config'], f)
1370 configure_mtu(nwrec['other_config'], f)
1371 configure_static_routes(device, nwrec['other_config'], f)
1374 if pifrec.has_key('other_config'):
1375 oc = pifrec['other_config']
1377 if device == bridge:
1378 f.write("TYPE=Bridge\n")
1379 f.write("DELAY=0\n")
1380 f.write("STP=off\n")
1381 f.write("PIFDEV=%s\n" % interface_name(pif))
1383 if pifrec['ip_configuration_mode'] == "DHCP":
1384 f.write("BOOTPROTO=dhcp\n")
1385 f.write("PERSISTENT_DHCLIENT=yes\n")
1386 elif pifrec['ip_configuration_mode'] == "Static":
1387 f.write("BOOTPROTO=none\n")
1388 f.write("NETMASK=%(netmask)s\n" % pifrec)
1389 f.write("IPADDR=%(IP)s\n" % pifrec)
1390 f.write("GATEWAY=%(gateway)s\n" % pifrec)
1391 elif pifrec['ip_configuration_mode'] == "None":
1392 f.write("BOOTPROTO=none\n")
1394 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1396 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1397 ServerList = pifrec['DNS'].split(",")
1398 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1399 if oc and oc.has_key('domain'):
1400 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1402 # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1403 # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1404 # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1406 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1408 # Note: we prune out the bond master pif (if it exists).
1409 # This is because when we are called to bring up an interface with a bond master, it is implicit that
1410 # we should bring down that master.
1411 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1412 not __pif in get_bond_masters_of_pif(pif) ]
1413 other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1416 defaultroute_pif = None
1418 # loop through all the pifs on this host looking for one with
1419 # other-config:peerdns = true, and one with
1420 # other-config:default-route=true
1421 for __pif in pifs_on_host:
1422 __pifrec = db.get_pif_record(__pif)
1423 __oc = __pifrec['other_config']
1424 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1425 if peerdns_pif == None:
1428 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1429 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1430 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1431 if defaultroute_pif == None:
1432 defaultroute_pif = __pif
1434 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1435 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1437 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1438 if peerdns_pif == None:
1439 peerdns_pif = management_pif
1440 if defaultroute_pif == None:
1441 defaultroute_pif = management_pif
1443 # Update all the other network's ifcfg files and ensure consistency
1444 for __pif in other_pifs_on_host:
1445 __f = open_network_ifcfg(__pif)
1446 peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1447 lines = __f.readlines()
1449 if not peerdns_line_wanted in lines:
1450 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1452 if not line.lstrip().startswith('PEERDNS'):
1454 log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1455 __f.write(peerdns_line_wanted)
1460 # There is no need to change this ifcfg file. So don't attach_child.
1463 # ... and for this pif too
1464 f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1467 fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1468 for line in fnetwork.readlines():
1469 if line.lstrip().startswith('GATEWAY') :
1471 fnetwork.write(line)
1472 if defaultroute_pif:
1473 gatewaydev = bridge_name(defaultroute_pif)
1475 gatewaydev = interface_name(defaultroute_pif)
1476 fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1478 f.attach_child(fnetwork)
1483 def configure_physical_interface(pif):
1484 """Write the configuration for a physical interface.
1486 Writes the configuration file for the physical interface described by
1489 Returns the open file handle for the interface configuration file.
1492 pifrec = db.get_pif_record(pif)
1494 f = open_pif_ifcfg(pif)
1496 f.write("TYPE=Ethernet\n")
1497 f.write("HWADDR=%(MAC)s\n" % pifrec)
1501 def configure_bond_interface(pif):
1502 """Write the configuration for a bond interface.
1504 Writes the configuration file for the bond interface described by
1505 the pif object. Handles writing the configuration for the slave
1508 Returns the open file handle for the bond interface configuration
1512 pifrec = db.get_pif_record(pif)
1513 oc = pifrec['other_config']
1514 f = open_pif_ifcfg(pif)
1516 if pifrec['MAC'] != "":
1517 f.write("MACADDR=%s\n" % pifrec['MAC'])
1519 for slave in get_bond_slaves_of_pif(pif):
1520 s = configure_physical_interface(slave)
1521 s.write("MASTER=%(device)s\n" % pifrec)
1522 s.write("SLAVE=yes\n")
1526 # The bond option defaults
1528 "mode": "balance-slb",
1535 # override defaults with values from other-config whose keys being with "bond-"
1536 overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
1537 overrides = map(lambda (key,val): (key[5:], val), overrides)
1538 bond_options.update(overrides)
1540 # write the bond options to ifcfg-bondX
1541 f.write('BONDING_OPTS="')
1542 for (name,val) in bond_options.items():
1543 f.write("%s=%s " % (name,val))
1547 def configure_vlan_interface(pif):
1548 """Write the configuration for a VLAN interface.
1550 Writes the configuration file for the VLAN interface described by
1551 the pif object. Handles writing the configuration for the master
1552 interface if necessary.
1554 Returns the open file handle for the VLAN interface configuration
1558 slave = configure_pif(get_vlan_slave_of_pif(pif))
1561 f = open_pif_ifcfg(pif)
1562 f.write("VLAN=yes\n")
1563 f.attach_child(slave)
1567 def configure_pif(pif):
1568 """Write the configuration for a PIF object.
1570 Writes the configuration file the PIF and all dependent
1571 interfaces (bond slaves and VLAN masters etc).
1573 Returns the open file handle for the interface configuration file.
1576 pifrec = db.get_pif_record(pif)
1578 if pifrec['VLAN'] != '-1':
1579 f = configure_vlan_interface(pif)
1580 elif len(pifrec['bond_master_of']) != 0:
1581 f = configure_bond_interface(pif)
1583 f = configure_physical_interface(pif)
1585 bridge = bridge_name(pif)
1587 f.write("BRIDGE=%s\n" % bridge)
1591 def unconfigure_pif(pif):
1592 """Clear up the files created by configure_pif"""
1593 f = open_pif_ifcfg(pif)
1594 log("Unlinking stale file %s" % f.path())
1598 if __name__ == "__main__":
1604 err = traceback.format_exception(*ex)
1608 if not debug_mode():