]> git.proxmox.com Git - mirror_ovs.git/blob - xenserver/opt_xensource_libexec_interface-reconfigure
xenserver: Bring up bond slave device before adding to bond.
[mirror_ovs.git] / xenserver / opt_xensource_libexec_interface-reconfigure
1 #!/usr/bin/python
2 #
3 # Copyright (c) Citrix Systems 2008. All rights reserved.
4 # Copyright (c) 2009 Nicira Networks.
5 #
6 """Usage:
7
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
11
12 where,
13 <CONFIG> = --device=<INTERFACE> --mode=dhcp
14 <CONFIG> = --device=<INTERFACE> --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
15
16 Options:
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.
20
21 Either both --session and --pif or just --pif-uuid.
22
23 <ACTION> is either "up" or "down" or "rewrite"
24 """
25
26 #
27 # Undocumented parameters for test & dev:
28 #
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.
32 #
33 #
34 #
35 # Notes:
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)
40
41 # XXX: --force-interface=all down
42
43 # XXX: --force-interface rewrite
44
45 # XXX: Sometimes this leaves "orphaned" datapaths, e.g. a datapath whose
46 # only port is the local port. Should delete those.
47
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.
58
59 import XenAPI
60 import os, sys, getopt, time, signal
61 import syslog
62 import traceback
63 import time
64 import re
65 import pickle
66
67 output_directory = None
68
69 db = None
70 management_pif = None
71
72 dbcache_file = "/etc/ovs-vswitch.dbcache"
73 vswitch_config_dir = "/etc/openvswitch"
74
75 class Usage(Exception):
76 def __init__(self, msg):
77 Exception.__init__(self)
78 self.msg = msg
79
80 class Error(Exception):
81 def __init__(self, msg):
82 Exception.__init__(self)
83 self.msg = msg
84
85 class ConfigurationFile(object):
86 """Write a file, tracking old and new versions.
87
88 Supports writing a new version of a file and applying and
89 reverting those changes.
90 """
91
92 __STATE = {"OPEN":"OPEN",
93 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
94 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
95
96 def __init__(self, fname, path="/etc/sysconfig/network-scripts"):
97
98 self.__state = self.__STATE['OPEN']
99 self.__fname = fname
100 self.__children = []
101
102 if debug_mode():
103 dirname = output_directory
104 else:
105 dirname = path
106
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
111
112 self.__f = open(self.__newpath, "w")
113
114 def attach_child(self, child):
115 self.__children.append(child)
116
117 def path(self):
118 return self.__path
119
120 def readlines(self):
121 try:
122 return open(self.path()).readlines()
123 except:
124 return ""
125
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)
129 self.__f.write(args)
130
131 def unlink(self):
132 if self.__state != self.__STATE['OPEN']:
133 raise Error("Attempt to unlink file in state %s" % self.__state)
134 self.__unlink = True
135 self.__f.close()
136 self.__state = self.__STATE['NOT-APPLIED']
137
138 def close(self):
139 if self.__state != self.__STATE['OPEN']:
140 raise Error("Attempt to close file in state %s" % self.__state)
141
142 self.__f.close()
143 self.__state = self.__STATE['NOT-APPLIED']
144
145 def changed(self):
146 if self.__state != self.__STATE['NOT-APPLIED']:
147 raise Error("Attempt to compare file in state %s" % self.__state)
148
149 return True
150
151 def apply(self):
152 if self.__state != self.__STATE['NOT-APPLIED']:
153 raise Error("Attempt to apply configuration from state %s" % self.__state)
154
155 for child in self.__children:
156 child.apply()
157
158 log("Applying changes to %s configuration" % self.__fname)
159
160 # Remove previous backup.
161 if os.access(self.__oldpath, os.F_OK):
162 os.unlink(self.__oldpath)
163
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)
168
169 # Apply new configuration.
170 assert(os.path.exists(self.__newpath))
171 if not self.__unlink:
172 os.link(self.__newpath, self.__path)
173 else:
174 pass # implicit unlink of original file
175
176 # Remove temporary file.
177 os.unlink(self.__newpath)
178
179 self.__state = self.__STATE['APPLIED']
180
181 def revert(self):
182 if self.__state != self.__STATE['APPLIED']:
183 raise Error("Attempt to revert configuration from state %s" % self.__state)
184
185 for child in self.__children:
186 child.revert()
187
188 log("Reverting changes to %s configuration" % self.__fname)
189
190 # Remove existing new configuration
191 if os.access(self.__newpath, os.F_OK):
192 os.unlink(self.__newpath)
193
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)
198
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)
203
204 # Leave .*.xapi-new as an aid to debugging.
205
206 self.__state = self.__STATE['REVERTED']
207
208 def commit(self):
209 if self.__state != self.__STATE['APPLIED']:
210 raise Error("Attempt to commit configuration from state %s" % self.__state)
211
212 for child in self.__children:
213 child.commit()
214
215 log("Committing changes to %s configuration" % self.__fname)
216
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)
221
222 self.__state = self.__STATE['COMMITTED']
223
224 def debug_mode():
225 return output_directory is not None
226
227 def log(s):
228 if debug_mode():
229 print >>sys.stderr, s
230 else:
231 syslog.syslog(s)
232
233 def check_allowed(pif):
234 pifrec = db.get_pif_record(pif)
235 try:
236 f = open("/proc/ardence")
237 macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
238 f.close()
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)
243 return False
244 except IOError:
245 pass
246 return True
247
248 def interface_exists(i):
249 return os.path.exists("/sys/class/net/" + i)
250
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")
255
256 if cache_file == None:
257 session = XenAPI.xapi_local()
258
259 if not session_ref:
260 log("No session ref given on command line, logging in.")
261 session.xenapi.login_with_password("root", "")
262 else:
263 session._session = session_ref
264
265 try:
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()
270 finally:
271 if not session_ref:
272 session.xenapi.session.logout()
273 else:
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)
278 f.close()
279
280 self.__vlans = members['vlans']
281 self.__bonds = members['bonds']
282 self.__pifs = members['pifs']
283 self.__networks = members['networks']
284
285 def save(self, cache_file, extras):
286 f = open(cache_file, 'w')
287 pickle.dump({'vlans': self.__vlans,
288 'bonds': self.__bonds,
289 'pifs': self.__pifs,
290 'networks': self.__networks}, f)
291 pickle.dump(extras, f)
292 f.close()
293
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()))
298 if len(pifs) == 0:
299 raise Error("Unknown PIF \"%s\"" % uuid)
300 elif len(pifs) > 1:
301 raise Error("Non-unique PIF \"%s\"" % uuid)
302
303 return pifs[0]
304
305 def get_pifs_by_record(self, record):
306 """record is partial pif record.
307 Get the pif(s) whose record matches.
308 """
309 def match(pifrec):
310 for key in record:
311 if record[key] != pifrec[key]:
312 return False
313 return True
314
315 return map(lambda (ref,rec): ref,
316 filter(lambda (ref,rec): match(rec),
317 self.__pifs.items()))
318
319 def get_pif_by_record(self, record):
320 """record is partial pif record.
321 Get the pif whose record matches.
322 """
323 pifs = self.get_pifs_by_record(record)
324 if len(pifs) == 0:
325 raise Error("No matching PIF \"%s\"" % str(record))
326 elif len(pifs) > 1:
327 raise Error("Multiple matching PIFs \"%s\"" % str(record))
328
329 return pifs[0]
330
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\"")
337
338 answer = None
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:
344 continue
345 if answer:
346 raise Error("Multiple PIFs on %s for network %s" % (host, bridge))
347 answer = pif
348 if not answer:
349 raise Error("No PIF on %s for network %s" % (host, bridge))
350 return answer
351
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):
357 return self.__pifs
358 def pif_exists(self, pif):
359 return self.__pifs.has_key(pif)
360
361 def get_management_pif(self, host):
362 """ Returns the management pif on host
363 """
364 all = self.get_all_pifs()
365 for pif in all:
366 pifrec = self.get_pif_record(pif)
367 if pifrec['management'] and pifrec['host'] == host :
368 return pif
369 return None
370
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
377
378 def get_bond_record(self, bond):
379 if self.__bonds.has_key(bond):
380 return self.__bonds[bond]
381 else:
382 return None
383
384 def get_vlan_record(self, vlan):
385 if self.__vlans.has_key(vlan):
386 return self.__vlans[vlan]
387 else:
388 return None
389
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'])
394
395 if nwrec['bridge']:
396 # TODO: sanity check that nwrec['bridgeless'] != 'true'
397 return nwrec['bridge']
398 else:
399 # TODO: sanity check that nwrec['bridgeless'] == 'true'
400 return None
401
402 def interface_name(pif):
403 """Construct an interface name from the given PIF record."""
404
405 pifrec = db.get_pif_record(pif)
406
407 if pifrec['VLAN'] == '-1':
408 return pifrec['device']
409 else:
410 return "%(device)s.%(VLAN)s" % pifrec
411
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
417 use it.)
418 """
419
420 pifrec = db.get_pif_record(pif)
421
422 if pifrec['VLAN'] == '-1':
423 return bridge_name(pif)
424 else:
425 return bridge_name(get_vlan_slave_of_pif(pif))
426
427 def ipdev_name(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.
431 """
432
433 pifrec = db.get_pif_record(pif)
434 return bridge_name(pif)
435
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.
441 """
442
443 pifrec = db.get_pif_record(pif)
444
445 if pifrec['VLAN'] != '-1':
446 return physdev_names(get_vlan_slave_of_pif(pif))
447 elif len(pifrec['bond_master_of']) != 0:
448 physdevs = []
449 for slave in get_bond_slaves_of_pif(pif):
450 physdevs += physdev_names(slave)
451 return physdevs
452 else:
453 return [pifrec['device']]
454
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
461 else:
462 pifrec['message'] = "Bring %(action)s PIF %(uuid)s" % pifrec
463 log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % pifrec)
464
465 def get_bond_masters_of_pif(pif):
466 """Returns a list of PIFs which are bond masters of this PIF"""
467
468 pifrec = db.get_pif_record(pif)
469
470 bso = pifrec['bond_slave_of']
471
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":
476 bso = []
477 elif not type(bso) == list:
478 bso = [bso]
479
480 bondrecs = [db.get_bond_record(bond) for bond in bso]
481 bondrecs = [rec for rec in bondrecs if rec]
482
483 return [bond['master'] for bond in bondrecs]
484
485 def get_bond_slaves_of_pif(pif):
486 """Returns a list of PIFs which make up the given bonded pif."""
487
488 pifrec = db.get_pif_record(pif)
489 host = pifrec['host']
490
491 bmo = pifrec['bond_master_of']
492 if len(bmo) > 1:
493 raise Error("Bond-master-of contains too many elements")
494
495 if len(bmo) == 0:
496 return []
497
498 bondrec = db.get_bond_record(bmo[0])
499 if not bondrec:
500 raise Error("No bond record for bond master PIF")
501
502 return bondrec['slaves']
503
504 def get_vlan_slave_of_pif(pif):
505 """Find the PIF which is the VLAN slave of pif.
506
507 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
508
509 pifrec = db.get_pif_record(pif)
510
511 vlan = pifrec['VLAN_master_of']
512 if not vlan or vlan == "OpaqueRef:NULL":
513 raise Error("PIF is not a VLAN master")
514
515 vlanrec = db.get_vlan_record(vlan)
516 if not vlanrec:
517 raise Error("No VLAN record found for PIF")
518
519 return vlanrec['tagged_PIF']
520
521 def get_vlan_masters_of_pif(pif):
522 """Returns a list of PIFs which are VLANs on top of the given pif."""
523
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'])]
527
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
531 # interfaces.
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]
538
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))
543 return False
544 return True
545
546 def down_netdev(interface, deconfigure=True):
547 if not interface_exists(interface):
548 log("down_netdev: interface %s does not exist, ignoring" % interface)
549 return
550 argv = ["/sbin/ifconfig", interface, 'down']
551 if deconfigure:
552 argv += ['0.0.0.0']
553
554 # Kill dhclient.
555 pidfile_name = '/var/run/dhclient-%s.pid' % interface
556 pidfile = None
557 try:
558 pidfile = open(pidfile_name, 'r')
559 os.kill(int(pidfile.readline()), signal.SIGTERM)
560 except:
561 pass
562 if pidfile != None:
563 pidfile.close()
564
565 # Remove dhclient pidfile.
566 try:
567 os.remove(pidfile_name)
568 except:
569 pass
570 run_command(argv)
571
572 def up_netdev(interface):
573 run_command(["/sbin/ifconfig", interface, 'up'])
574
575 def find_distinguished_pifs(pif):
576 """Returns the PIFs on host that own DNS and the default route.
577 The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
578 The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
579
580 Note: we prune out the bond master pif (if it exists).
581 This is because when we are called to bring up an interface with a bond master, it is implicit that
582 we should bring down that master."""
583
584 pifrec = db.get_pif_record(pif)
585 host = pifrec['host']
586
587 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
588 db.get_pif_record(__pif)['host'] == host and
589 (not __pif in get_bond_masters_of_pif(pif)) ]
590
591 peerdns_pif = None
592 defaultroute_pif = None
593
594 # loop through all the pifs on this host looking for one with
595 # other-config:peerdns = true, and one with
596 # other-config:default-route=true
597 for __pif in pifs_on_host:
598 __pifrec = db.get_pif_record(__pif)
599 __oc = __pifrec['other_config']
600 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
601 if peerdns_pif == None:
602 peerdns_pif = __pif
603 else:
604 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
605 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
606 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
607 if defaultroute_pif == None:
608 defaultroute_pif = __pif
609 else:
610 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
611 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
612
613 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
614 if peerdns_pif == None:
615 peerdns_pif = management_pif
616 if defaultroute_pif == None:
617 defaultroute_pif = management_pif
618
619 return peerdns_pif, defaultroute_pif
620
621 def ethtool_settings(oc):
622 # Options for "ethtool -s"
623 settings = []
624 if oc.has_key('ethtool-speed'):
625 val = oc['ethtool-speed']
626 if val in ["10", "100", "1000"]:
627 settings += ['speed', val]
628 else:
629 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
630 if oc.has_key('ethtool-duplex'):
631 val = oc['ethtool-duplex']
632 if val in ["10", "100", "1000"]:
633 settings += ['duplex', 'val']
634 else:
635 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
636 if oc.has_key('ethtool-autoneg'):
637 val = oc['ethtool-autoneg']
638 if val in ["true", "on"]:
639 settings += ['autoneg', 'on']
640 elif val in ["false", "off"]:
641 settings += ['autoneg', 'off']
642 else:
643 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
644
645 # Options for "ethtool -K"
646 offload = []
647 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
648 if oc.has_key("ethtool-" + opt):
649 val = oc["ethtool-" + opt]
650 if val in ["true", "on"]:
651 offload += [opt, 'on']
652 elif val in ["false", "off"]:
653 offload += [opt, 'off']
654 else:
655 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
656
657 return settings, offload
658
659 def configure_netdev(pif):
660 pifrec = db.get_pif_record(pif)
661 datapath = datapath_name(pif)
662 ipdev = ipdev_name(pif)
663
664 host = pifrec['host']
665 nw = pifrec['network']
666 nwrec = db.get_network_record(nw)
667
668 ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up']
669 gateway = ''
670 if pifrec['ip_configuration_mode'] == "DHCP":
671 pass
672 elif pifrec['ip_configuration_mode'] == "Static":
673 ifconfig_argv += [pifrec['IP']]
674 ifconfig_argv += ['netmask', pifrec['netmask']]
675 gateway = pifrec['gateway']
676 elif pifrec['ip_configuration_mode'] == "None":
677 # Nothing to do.
678 pass
679 else:
680 raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode'])
681
682 oc = {}
683 if pifrec.has_key('other_config'):
684 oc = pifrec['other_config']
685 if oc.has_key('mtu'):
686 int(oc['mtu']) # Check that the value is an integer
687 ifconfig_argv += ['mtu', oc['mtu']]
688
689 run_command(ifconfig_argv)
690
691 (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif)
692
693 if peerdns_pif == pif:
694 f = ConfigurationFile('resolv.conf', "/etc")
695 if oc.has_key('domain'):
696 f.write("search %s\n" % oc['domain'])
697 for dns in pifrec['DNS'].split(","):
698 f.write("nameserver %s\n" % dns)
699 f.close()
700 f.apply()
701 f.commit()
702
703 if defaultroute_pif == pif and gateway != '':
704 run_command(['/sbin/ip', 'route', 'replace', 'default',
705 'via', gateway, 'dev', ipdev])
706
707 if oc.has_key('static-routes'):
708 for line in oc['static-routes'].split(','):
709 network, masklen, gateway = line.split('/')
710 run_command(['/sbin/ip', 'route', 'add',
711 '%s/%s' % (netmask, masklen), 'via', gateway,
712 'dev', ipdev])
713
714 settings, offload = ethtool_settings(oc)
715 if settings:
716 run_command(['/sbin/ethtool', '-s', ipdev] + settings)
717 if offload:
718 run_command(['/sbin/ethtool', '-K', ipdev] + offload)
719
720 if pifrec['ip_configuration_mode'] == "DHCP":
721 print
722 print "Determining IP information for %s..." % ipdev,
723 argv = ['/sbin/dhclient', '-q',
724 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev,
725 '-pf', '/var/run/dhclient-%s.pid' % ipdev,
726 ipdev]
727 if run_command(argv):
728 print 'done.'
729 else:
730 print 'failed.'
731
732 def modify_config(commands):
733 run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer',
734 '-F', '/etc/ovs-vswitchd.conf']
735 + commands + ['-c'])
736 run_command(['/sbin/service', 'vswitch', 'reload'])
737
738 def is_bond_pif(pif):
739 pifrec = db.get_pif_record(pif)
740 return len(pifrec['bond_master_of']) != 0
741
742 def configure_bond(pif):
743 pifrec = db.get_pif_record(pif)
744 interface = interface_name(pif)
745 ipdev = ipdev_name(pif)
746 datapath = datapath_name(pif)
747 physdevs = physdev_names(pif)
748
749 argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
750 argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
751 for slave in physdevs]
752
753 # Bonding options.
754 bond_options = {
755 "mode": "balance-slb",
756 "miimon": "100",
757 "downdelay": "200",
758 "updelay": "31000",
759 "use_carrier": "1",
760 }
761 # override defaults with values from other-config whose keys
762 # being with "bond-"
763 oc = pifrec['other_config']
764 overrides = filter(lambda (key,val):
765 key.startswith("bond-"), oc.items())
766 overrides = map(lambda (key,val): (key[5:], val), overrides)
767 bond_options.update(overrides)
768 for (name,val) in bond_options.items():
769 argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
770 return argv
771
772 def action_up(pif):
773 pifrec = db.get_pif_record(pif)
774
775 bridge = bridge_name(pif)
776 interface = interface_name(pif)
777 ipdev = ipdev_name(pif)
778 datapath = datapath_name(pif)
779 physdevs = physdev_names(pif)
780 vlan_slave = None
781 if pifrec['VLAN'] != '-1':
782 vlan_slave = get_vlan_slave_of_pif(pif)
783 if vlan_slave and is_bond_pif(vlan_slave):
784 bond_master = vlan_slave
785 elif is_bond_pif(pif):
786 bond_master = pif
787 else:
788 bond_master = None
789 if bond_master:
790 bond_slaves = get_bond_slaves_of_pif(bond_master)
791 else:
792 bond_slaves = []
793 bond_masters = get_bond_masters_of_pif(pif)
794
795 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
796 # files up-to-date, even though we don't use them or need them.
797 f = configure_pif(pif)
798 mode = pifrec['ip_configuration_mode']
799 if bridge:
800 log("Configuring %s using %s configuration" % (bridge, mode))
801 br = open_network_ifcfg(pif)
802 configure_network(pif, br)
803 br.close()
804 f.attach_child(br)
805 else:
806 log("Configuring %s using %s configuration" % (interface, mode))
807 configure_network(pif, f)
808 f.close()
809 for master in bond_masters:
810 master_bridge = bridge_name(master)
811 removed = unconfigure_pif(master)
812 f.attach_child(removed)
813 if master_bridge:
814 removed = open_network_ifcfg(master)
815 log("Unlinking stale file %s" % removed.path())
816 removed.unlink()
817 f.attach_child(removed)
818
819 # /etc/xensource/scripts/vif needs to know where to add VIFs.
820 if vlan_slave:
821 if not os.path.exists(vswitch_config_dir):
822 os.mkdir(vswitch_config_dir)
823 br = ConfigurationFile("br-%s" % bridge, vswitch_config_dir)
824 br.write("VLAN_SLAVE=%s\n" % datapath)
825 br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
826 br.close()
827 f.attach_child(br)
828
829 # Update all configuration files (both ours and Centos's).
830 f.apply()
831 f.commit()
832
833 # "ifconfig down" the network device and delete its IP address, etc.
834 down_netdev(ipdev)
835 for physdev in physdevs:
836 down_netdev(physdev)
837
838 # Remove all keys related to pif and any bond masters linked to PIF.
839 del_ports = [ipdev] + physdevs + bond_masters
840 if vlan_slave and bond_master:
841 del_ports += [interface_name(bond_master)]
842
843 # What ports do we need to add to the datapath?
844 #
845 # We definitely need the ipdev, and ordinarily we want the
846 # physical devices too, but for bonds we need the bond as bridge
847 # port.
848 add_ports = [ipdev, datapath]
849 if not bond_master:
850 add_ports += physdevs
851 else:
852 add_ports += [interface_name(bond_master)]
853
854 # What ports do we need to delete?
855 #
856 # - All the ports that we add, to avoid duplication and to drop
857 # them from another datapath in case they're misassigned.
858 #
859 # - The physical devices, since they will either be in add_ports
860 # or added to the bonding device (see below).
861 #
862 # - The bond masters for pif. (Ordinarily pif shouldn't have any
863 # bond masters. If it does then interface-reconfigure is
864 # implicitly being asked to take them down.)
865 del_ports = add_ports + physdevs + bond_masters
866
867 # What networks does this datapath carry?
868 #
869 # - The network corresponding to the datapath's PIF.
870 #
871 # - The networks corresponding to any VLANs attached to the
872 # datapath's PIF.
873 network_uuids = []
874 for nwpif in db.get_pifs_by_record({'device': pifrec['device'],
875 'host': pifrec['host']}):
876 net = db.get_pif_record(nwpif)['network']
877 network_uuids += [db.get_network_record(net)['uuid']]
878
879 # Bring up bond slaves early, because ovs-vswitchd initially
880 # enables or disables bond slaves based on whether carrier is
881 # detected when they are added, and a network device that is down
882 # always reports "no carrier".
883 bond_slave_physdevs = []
884 for slave in bond_slaves:
885 bond_slave_physdevs += physdev_names(slave)
886 for slave_physdev in bond_slave_physdevs:
887 up_netdev(slave_physdev)
888
889 # Now modify the ovs-vswitchd config file.
890 argv = []
891 for port in set(del_ports):
892 argv += interface_deconfigure_commands(port)
893 for port in set(add_ports):
894 argv += ['--add=bridge.%s.port=%s' % (datapath, port)]
895 if vlan_slave:
896 argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
897 argv += ['--add=iface.%s.internal=true' % (ipdev)]
898
899 # xapi creates a bridge by the name of the ipdev and requires
900 # that the IP address will be on it. We need to delete this
901 # bridge because we need that device to be a member of our
902 # datapath.
903 argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev]
904
905 # xapi insists that its attempts to create the bridge succeed,
906 # so force that to happen.
907 argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
908 else:
909 try:
910 os.unlink("%s/br-%s" % (vswitch_config_dir, bridge))
911 except OSError:
912 pass
913 argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath]
914 argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid)
915 for uuid in set(network_uuids)]
916 if bond_master:
917 argv += configure_bond(bond_master)
918 modify_config(argv)
919
920 # Configure network devices.
921 configure_netdev(pif)
922
923 # Bring up VLAN slave, plus physical devices other than bond
924 # slaves (which we brought up earlier).
925 if vlan_slave:
926 up_netdev(ipdev_name(vlan_slave))
927 for physdev in set(physdevs) - set(bond_slave_physdevs):
928 up_netdev(physdev)
929
930 # Update /etc/issue (which contains the IP address of the management interface)
931 os.system("/sbin/update-issue")
932
933 def action_down(pif):
934 rec = db.get_pif_record(pif)
935 interface = interface_name(pif)
936 bridge = bridge_name(pif)
937 ipdev = ipdev_name(pif)
938
939 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
940 # files up-to-date, even though we don't use them or need them.
941 f = unconfigure_pif(pif)
942 if bridge:
943 br = open_network_ifcfg(pif)
944 log("Unlinking stale file %s" % br.path())
945 br.unlink()
946 f.attach_child(br)
947 try:
948 f.apply()
949 f.commit()
950 except Error, e:
951 log("action_down failed to apply changes: %s" % e.msg)
952 f.revert()
953 raise
954
955 argv = []
956 if rec['VLAN'] != '-1':
957 # Get rid of the VLAN device itself.
958 down_netdev(ipdev)
959 argv += interface_deconfigure_commands(ipdev)
960
961 # If the VLAN's slave is attached, stop here.
962 slave = get_vlan_slave_of_pif(pif)
963 if db.get_pif_record(slave)['currently_attached']:
964 log("VLAN slave is currently attached")
965 modify_config(argv)
966 return
967
968 # If the VLAN's slave has other VLANs that are attached, stop here.
969 masters = get_vlan_masters_of_pif(slave)
970 for m in masters:
971 if m != pif and db.get_pif_record(m)['currently_attached']:
972 log("VLAN slave has other master %s" % interface_naem(m))
973 modify_config(argv)
974 return
975
976 # Otherwise, take down the VLAN's slave too.
977 log("No more masters, bring down vlan slave %s" % interface_name(slave))
978 pif = slave
979 else:
980 # Stop here if this PIF has attached VLAN masters.
981 vlan_masters = get_vlan_masters_of_pif(pif)
982 log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters]))
983 for m in vlan_masters:
984 if db.get_pif_record(m)['currently_attached']:
985 log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m)))
986 return
987
988 # pif is now either a bond or a physical device which needs to be
989 # brought down. pif might have changed so re-check all its attributes.
990 rec = db.get_pif_record(pif)
991 interface = interface_name(pif)
992 bridge = bridge_name(pif)
993 ipdev = ipdev_name(pif)
994
995
996 bond_slaves = get_bond_slaves_of_pif(pif)
997 log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves]))
998 for slave in bond_slaves:
999 slave_interface = interface_name(slave)
1000 log("bring down bond slave %s" % slave_interface)
1001 argv += interface_deconfigure_commands(slave_interface)
1002 down_netdev(slave_interface)
1003
1004 argv += interface_deconfigure_commands(ipdev)
1005 down_netdev(ipdev)
1006
1007 argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)]
1008 argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface]
1009 modify_config(argv)
1010
1011 def action_rewrite(pif):
1012 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1013 # files up-to-date, even though we don't use them or need them.
1014 pifrec = db.get_pif_record(pif)
1015 f = configure_pif(pif)
1016 interface = interface_name(pif)
1017 bridge = bridge_name(pif)
1018 mode = pifrec['ip_configuration_mode']
1019 if bridge:
1020 log("Configuring %s using %s configuration" % (bridge, mode))
1021 br = open_network_ifcfg(pif)
1022 configure_network(pif, br)
1023 br.close()
1024 f.attach_child(br)
1025 else:
1026 log("Configuring %s using %s configuration" % (interface, mode))
1027 configure_network(pif, f)
1028 f.close()
1029 try:
1030 f.apply()
1031 f.commit()
1032 except Error, e:
1033 log("failed to apply changes: %s" % e.msg)
1034 f.revert()
1035 raise
1036
1037 # We have no code of our own to run here.
1038 pass
1039
1040 def main(argv=None):
1041 global output_directory, management_pif
1042
1043 session = None
1044 pif_uuid = None
1045 pif = None
1046
1047 force_interface = None
1048 force_management = False
1049
1050 if argv is None:
1051 argv = sys.argv
1052
1053 try:
1054 try:
1055 shortops = "h"
1056 longops = [ "output-directory=",
1057 "pif=", "pif-uuid=",
1058 "session=",
1059 "force=",
1060 "force-interface=",
1061 "management",
1062 "test-mode",
1063 "device=", "mode=", "ip=", "netmask=", "gateway=",
1064 "help" ]
1065 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1066 except getopt.GetoptError, msg:
1067 raise Usage(msg)
1068
1069 force_rewrite_config = {}
1070
1071 for o,a in arglist:
1072 if o == "--output-directory":
1073 output_directory = a
1074 elif o == "--pif":
1075 pif = a
1076 elif o == "--pif-uuid":
1077 pif_uuid = a
1078 elif o == "--session":
1079 session = a
1080 elif o == "--force-interface" or o == "--force":
1081 force_interface = a
1082 elif o == "--management":
1083 force_management = True
1084 elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1085 force_rewrite_config[o[2:]] = a
1086 elif o == "-h" or o == "--help":
1087 print __doc__ % {'command-name': os.path.basename(argv[0])}
1088 return 0
1089
1090 if not debug_mode():
1091 syslog.openlog(os.path.basename(argv[0]))
1092 log("Called as " + str.join(" ", argv))
1093 if len(args) < 1:
1094 raise Usage("Required option <action> not present")
1095 if len(args) > 1:
1096 raise Usage("Too many arguments")
1097
1098 action = args[0]
1099 # backwards compatibility
1100 if action == "rewrite-configuration": action = "rewrite"
1101
1102 if output_directory and ( session or pif ):
1103 raise Usage("--session/--pif cannot be used with --output-directory")
1104 if ( session or pif ) and pif_uuid:
1105 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1106 if ( session and not pif ) or ( not session and pif ):
1107 raise Usage("--session and --pif must be used together.")
1108 if force_interface and ( session or pif or pif_uuid ):
1109 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1110 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1111 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1112
1113 global db
1114 if force_interface:
1115 log("Force interface %s %s" % (force_interface, action))
1116
1117 if action == "rewrite":
1118 action_force_rewrite(force_interface, force_rewrite_config)
1119 else:
1120 db = DatabaseCache(cache_file=dbcache_file)
1121 host = db.extras['host']
1122 pif = db.get_pif_by_bridge(host, force_interface)
1123 management_pif = db.get_management_pif(host)
1124
1125 if action == "up":
1126 action_up(pif)
1127 elif action == "down":
1128 action_down(pif)
1129 else:
1130 raise Usage("Unknown action %s" % action)
1131 else:
1132 db = DatabaseCache(session_ref=session)
1133
1134 if pif_uuid:
1135 pif = db.get_pif_by_uuid(pif_uuid)
1136
1137 if not pif:
1138 raise Usage("No PIF given")
1139
1140 if force_management:
1141 # pif is going to be the management pif
1142 management_pif = pif
1143 else:
1144 # pif is not going to be the management pif.
1145 # Search DB cache for pif on same host with management=true
1146 pifrec = db.get_pif_record(pif)
1147 host = pifrec['host']
1148 management_pif = db.get_management_pif(host)
1149
1150 log_pif_action(action, pif)
1151
1152 if not check_allowed(pif):
1153 return 0
1154
1155 if action == "up":
1156 action_up(pif)
1157 elif action == "down":
1158 action_down(pif)
1159 elif action == "rewrite":
1160 action_rewrite(pif)
1161 else:
1162 raise Usage("Unknown action %s" % action)
1163
1164 # Save cache.
1165 pifrec = db.get_pif_record(pif)
1166 db.save(dbcache_file, {'host': pifrec['host']})
1167
1168 except Usage, err:
1169 print >>sys.stderr, err.msg
1170 print >>sys.stderr, "For help use --help."
1171 return 2
1172 except Error, err:
1173 log(err.msg)
1174 return 1
1175
1176 return 0
1177 \f
1178 # The following code allows interface-reconfigure to keep Centos
1179 # network configuration files up-to-date, even though the vswitch
1180 # never uses them. In turn, that means that "rpm -e vswitch" does not
1181 # have to update any configuration files.
1182
1183 def configure_ethtool(oc, f):
1184 # Options for "ethtool -s"
1185 settings = None
1186 setting_opts = ["autoneg", "speed", "duplex"]
1187 # Options for "ethtool -K"
1188 offload = None
1189 offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
1190
1191 for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
1192 val = oc["ethtool-" + opt]
1193
1194 if opt in ["speed"]:
1195 if val in ["10", "100", "1000"]:
1196 val = "speed " + val
1197 else:
1198 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
1199 val = None
1200 elif opt in ["duplex"]:
1201 if val in ["half", "full"]:
1202 val = "duplex " + val
1203 else:
1204 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
1205 val = None
1206 elif opt in ["autoneg"] + offload_opts:
1207 if val in ["true", "on"]:
1208 val = opt + " on"
1209 elif val in ["false", "off"]:
1210 val = opt + " off"
1211 else:
1212 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
1213 val = None
1214
1215 if opt in setting_opts:
1216 if val and settings:
1217 settings = settings + " " + val
1218 else:
1219 settings = val
1220 elif opt in offload_opts:
1221 if val and offload:
1222 offload = offload + " " + val
1223 else:
1224 offload = val
1225
1226 if settings:
1227 f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
1228 if offload:
1229 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
1230
1231 def configure_mtu(oc, f):
1232 if not oc.has_key('mtu'):
1233 return
1234
1235 try:
1236 mtu = int(oc['mtu'])
1237 f.write("MTU=%d\n" % mtu)
1238 except ValueError, x:
1239 log("Invalid value for mtu = %s" % mtu)
1240
1241 def configure_static_routes(interface, oc, f):
1242 """Open a route-<interface> file for static routes.
1243
1244 Opens the static routes configuration file for interface and writes one
1245 line for each route specified in the network's other config "static-routes" value.
1246 E.g. if
1247 interface ( RO): xenbr1
1248 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1249
1250 Then route-xenbr1 should be
1251 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1252 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1253 """
1254 fname = "route-%s" % interface
1255 if oc.has_key('static-routes'):
1256 # The key is present - extract comma seperates entries
1257 lines = oc['static-routes'].split(',')
1258 else:
1259 # The key is not present, i.e. there are no static routes
1260 lines = []
1261
1262 child = ConfigurationFile(fname)
1263 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1264 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
1265
1266 try:
1267 for l in lines:
1268 network, masklen, gateway = l.split('/')
1269 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1270
1271 f.attach_child(child)
1272 child.close()
1273
1274 except ValueError, e:
1275 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1276
1277 def __open_ifcfg(interface):
1278 """Open a network interface configuration file.
1279
1280 Opens the configuration file for interface, writes a header and
1281 common options and returns the file object.
1282 """
1283 fname = "ifcfg-%s" % interface
1284 f = ConfigurationFile(fname)
1285
1286 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1287 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1288 f.write("XEMANAGED=yes\n")
1289 f.write("DEVICE=%s\n" % interface)
1290 f.write("ONBOOT=no\n")
1291
1292 return f
1293
1294 def open_network_ifcfg(pif):
1295 bridge = bridge_name(pif)
1296 interface = interface_name(pif)
1297 if bridge:
1298 return __open_ifcfg(bridge)
1299 else:
1300 return __open_ifcfg(interface)
1301
1302
1303 def open_pif_ifcfg(pif):
1304 pifrec = db.get_pif_record(pif)
1305
1306 log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
1307
1308 f = __open_ifcfg(interface_name(pif))
1309
1310 if pifrec.has_key('other_config'):
1311 configure_ethtool(pifrec['other_config'], f)
1312 configure_mtu(pifrec['other_config'], f)
1313
1314 return f
1315
1316 def configure_network(pif, f):
1317 """Write the configuration file for a network.
1318
1319 Writes configuration derived from the network object into the relevant
1320 ifcfg file. The configuration file is passed in, but if the network is
1321 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1322
1323 This routine may also write ifcfg files of the networks corresponding to other PIFs
1324 in order to maintain consistency.
1325
1326 params:
1327 pif: Opaque_ref of pif
1328 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1329 """
1330
1331 pifrec = db.get_pif_record(pif)
1332 host = pifrec['host']
1333 nw = pifrec['network']
1334 nwrec = db.get_network_record(nw)
1335 oc = None
1336 bridge = bridge_name(pif)
1337 interface = interface_name(pif)
1338 if bridge:
1339 device = bridge
1340 else:
1341 device = interface
1342
1343 if nwrec.has_key('other_config'):
1344 configure_ethtool(nwrec['other_config'], f)
1345 configure_mtu(nwrec['other_config'], f)
1346 configure_static_routes(device, nwrec['other_config'], f)
1347
1348
1349 if pifrec.has_key('other_config'):
1350 oc = pifrec['other_config']
1351
1352 if device == bridge:
1353 f.write("TYPE=Bridge\n")
1354 f.write("DELAY=0\n")
1355 f.write("STP=off\n")
1356 f.write("PIFDEV=%s\n" % interface_name(pif))
1357
1358 if pifrec['ip_configuration_mode'] == "DHCP":
1359 f.write("BOOTPROTO=dhcp\n")
1360 f.write("PERSISTENT_DHCLIENT=yes\n")
1361 elif pifrec['ip_configuration_mode'] == "Static":
1362 f.write("BOOTPROTO=none\n")
1363 f.write("NETMASK=%(netmask)s\n" % pifrec)
1364 f.write("IPADDR=%(IP)s\n" % pifrec)
1365 f.write("GATEWAY=%(gateway)s\n" % pifrec)
1366 elif pifrec['ip_configuration_mode'] == "None":
1367 f.write("BOOTPROTO=none\n")
1368 else:
1369 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1370
1371 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1372 ServerList = pifrec['DNS'].split(",")
1373 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1374 if oc and oc.has_key('domain'):
1375 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1376
1377 # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1378 # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1379 # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1380
1381 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1382 #
1383 # Note: we prune out the bond master pif (if it exists).
1384 # This is because when we are called to bring up an interface with a bond master, it is implicit that
1385 # we should bring down that master.
1386 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1387 db.get_pif_record(__pif)['host'] == host and
1388 (not __pif in get_bond_masters_of_pif(pif)) ]
1389 other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1390
1391 peerdns_pif = None
1392 defaultroute_pif = None
1393
1394 # loop through all the pifs on this host looking for one with
1395 # other-config:peerdns = true, and one with
1396 # other-config:default-route=true
1397 for __pif in pifs_on_host:
1398 __pifrec = db.get_pif_record(__pif)
1399 __oc = __pifrec['other_config']
1400 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1401 if peerdns_pif == None:
1402 peerdns_pif = __pif
1403 else:
1404 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1405 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1406 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1407 if defaultroute_pif == None:
1408 defaultroute_pif = __pif
1409 else:
1410 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1411 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1412
1413 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1414 if peerdns_pif == None:
1415 peerdns_pif = management_pif
1416 if defaultroute_pif == None:
1417 defaultroute_pif = management_pif
1418
1419 # Update all the other network's ifcfg files and ensure consistency
1420 for __pif in other_pifs_on_host:
1421 __f = open_network_ifcfg(__pif)
1422 peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1423 lines = __f.readlines()
1424
1425 if not peerdns_line_wanted in lines:
1426 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1427 for line in lines:
1428 if not line.lstrip().startswith('PEERDNS'):
1429 __f.write(line)
1430 log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1431 __f.write(peerdns_line_wanted)
1432 __f.close()
1433 f.attach_child(__f)
1434
1435 else:
1436 # There is no need to change this ifcfg file. So don't attach_child.
1437 pass
1438
1439 # ... and for this pif too
1440 f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1441
1442 # Update gatewaydev
1443 fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1444 for line in fnetwork.readlines():
1445 if line.lstrip().startswith('GATEWAY') :
1446 continue
1447 fnetwork.write(line)
1448 if defaultroute_pif:
1449 gatewaydev = bridge_name(defaultroute_pif)
1450 if not gatewaydev:
1451 gatewaydev = interface_name(defaultroute_pif)
1452 fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1453 fnetwork.close()
1454 f.attach_child(fnetwork)
1455
1456 return
1457
1458
1459 def configure_physical_interface(pif):
1460 """Write the configuration for a physical interface.
1461
1462 Writes the configuration file for the physical interface described by
1463 the pif object.
1464
1465 Returns the open file handle for the interface configuration file.
1466 """
1467
1468 pifrec = db.get_pif_record(pif)
1469
1470 f = open_pif_ifcfg(pif)
1471
1472 f.write("TYPE=Ethernet\n")
1473 f.write("HWADDR=%(MAC)s\n" % pifrec)
1474
1475 return f
1476
1477 def configure_bond_interface(pif):
1478 """Write the configuration for a bond interface.
1479
1480 Writes the configuration file for the bond interface described by
1481 the pif object. Handles writing the configuration for the slave
1482 interfaces.
1483
1484 Returns the open file handle for the bond interface configuration
1485 file.
1486 """
1487
1488 pifrec = db.get_pif_record(pif)
1489 oc = pifrec['other_config']
1490 f = open_pif_ifcfg(pif)
1491
1492 if pifrec['MAC'] != "":
1493 f.write("MACADDR=%s\n" % pifrec['MAC'])
1494
1495 for slave in get_bond_slaves_of_pif(pif):
1496 s = configure_physical_interface(slave)
1497 s.write("MASTER=%(device)s\n" % pifrec)
1498 s.write("SLAVE=yes\n")
1499 s.close()
1500 f.attach_child(s)
1501
1502 # The bond option defaults
1503 bond_options = {
1504 "mode": "balance-slb",
1505 "miimon": "100",
1506 "downdelay": "200",
1507 "updelay": "31000",
1508 "use_carrier": "1",
1509 }
1510
1511 # override defaults with values from other-config whose keys being with "bond-"
1512 overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
1513 overrides = map(lambda (key,val): (key[5:], val), overrides)
1514 bond_options.update(overrides)
1515
1516 # write the bond options to ifcfg-bondX
1517 f.write('BONDING_OPTS="')
1518 for (name,val) in bond_options.items():
1519 f.write("%s=%s " % (name,val))
1520 f.write('"\n')
1521 return f
1522
1523 def configure_vlan_interface(pif):
1524 """Write the configuration for a VLAN interface.
1525
1526 Writes the configuration file for the VLAN interface described by
1527 the pif object. Handles writing the configuration for the master
1528 interface if necessary.
1529
1530 Returns the open file handle for the VLAN interface configuration
1531 file.
1532 """
1533
1534 slave = configure_pif(get_vlan_slave_of_pif(pif))
1535 slave.close()
1536
1537 f = open_pif_ifcfg(pif)
1538 f.write("VLAN=yes\n")
1539 f.attach_child(slave)
1540
1541 return f
1542
1543 def configure_pif(pif):
1544 """Write the configuration for a PIF object.
1545
1546 Writes the configuration file the PIF and all dependent
1547 interfaces (bond slaves and VLAN masters etc).
1548
1549 Returns the open file handle for the interface configuration file.
1550 """
1551
1552 pifrec = db.get_pif_record(pif)
1553
1554 if pifrec['VLAN'] != '-1':
1555 f = configure_vlan_interface(pif)
1556 elif len(pifrec['bond_master_of']) != 0:
1557 f = configure_bond_interface(pif)
1558 else:
1559 f = configure_physical_interface(pif)
1560
1561 bridge = bridge_name(pif)
1562 if bridge:
1563 f.write("BRIDGE=%s\n" % bridge)
1564
1565 return f
1566
1567 def unconfigure_pif(pif):
1568 """Clear up the files created by configure_pif"""
1569 f = open_pif_ifcfg(pif)
1570 log("Unlinking stale file %s" % f.path())
1571 f.unlink()
1572 return f
1573 \f
1574 if __name__ == "__main__":
1575 rc = 1
1576 try:
1577 rc = main()
1578 except:
1579 ex = sys.exc_info()
1580 err = traceback.format_exception(*ex)
1581 for exline in err:
1582 log(exline)
1583
1584 if not debug_mode():
1585 syslog.closelog()
1586
1587 sys.exit(rc)