]> git.proxmox.com Git - mirror_ovs.git/blob - xenserver/opt_xensource_libexec_interface-reconfigure
xenserver: Honor the MAC address specified in xapi database for bonds.
[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 if pifrec['MAC'] != "":
754 argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
755
756 # Bonding options.
757 bond_options = {
758 "mode": "balance-slb",
759 "miimon": "100",
760 "downdelay": "200",
761 "updelay": "31000",
762 "use_carrier": "1",
763 }
764 # override defaults with values from other-config whose keys
765 # being with "bond-"
766 oc = pifrec['other_config']
767 overrides = filter(lambda (key,val):
768 key.startswith("bond-"), oc.items())
769 overrides = map(lambda (key,val): (key[5:], val), overrides)
770 bond_options.update(overrides)
771 for (name,val) in bond_options.items():
772 argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
773 return argv
774
775 def action_up(pif):
776 pifrec = db.get_pif_record(pif)
777
778 bridge = bridge_name(pif)
779 interface = interface_name(pif)
780 ipdev = ipdev_name(pif)
781 datapath = datapath_name(pif)
782 physdevs = physdev_names(pif)
783 vlan_slave = None
784 if pifrec['VLAN'] != '-1':
785 vlan_slave = get_vlan_slave_of_pif(pif)
786 if vlan_slave and is_bond_pif(vlan_slave):
787 bond_master = vlan_slave
788 elif is_bond_pif(pif):
789 bond_master = pif
790 else:
791 bond_master = None
792 if bond_master:
793 bond_slaves = get_bond_slaves_of_pif(bond_master)
794 else:
795 bond_slaves = []
796 bond_masters = get_bond_masters_of_pif(pif)
797
798 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
799 # files up-to-date, even though we don't use them or need them.
800 f = configure_pif(pif)
801 mode = pifrec['ip_configuration_mode']
802 if bridge:
803 log("Configuring %s using %s configuration" % (bridge, mode))
804 br = open_network_ifcfg(pif)
805 configure_network(pif, br)
806 br.close()
807 f.attach_child(br)
808 else:
809 log("Configuring %s using %s configuration" % (interface, mode))
810 configure_network(pif, f)
811 f.close()
812 for master in bond_masters:
813 master_bridge = bridge_name(master)
814 removed = unconfigure_pif(master)
815 f.attach_child(removed)
816 if master_bridge:
817 removed = open_network_ifcfg(master)
818 log("Unlinking stale file %s" % removed.path())
819 removed.unlink()
820 f.attach_child(removed)
821
822 # /etc/xensource/scripts/vif needs to know where to add VIFs.
823 if vlan_slave:
824 if not os.path.exists(vswitch_config_dir):
825 os.mkdir(vswitch_config_dir)
826 br = ConfigurationFile("br-%s" % bridge, vswitch_config_dir)
827 br.write("VLAN_SLAVE=%s\n" % datapath)
828 br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
829 br.close()
830 f.attach_child(br)
831
832 # Update all configuration files (both ours and Centos's).
833 f.apply()
834 f.commit()
835
836 # "ifconfig down" the network device and delete its IP address, etc.
837 down_netdev(ipdev)
838 for physdev in physdevs:
839 down_netdev(physdev)
840
841 # If we are bringing up a bond, remove IP addresses from the
842 # slaves (because we are implicitly being asked to take them down).
843 #
844 # Conversely, if we are bringing up an interface that has bond
845 # masters, remove IP addresses from the bond master (because we
846 # are implicitly being asked to take it down).
847 for bond_pif in bond_slaves + bond_masters:
848 run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0'])
849
850 # Remove all keys related to pif and any bond masters linked to PIF.
851 del_ports = [ipdev] + physdevs + bond_masters
852 if vlan_slave and bond_master:
853 del_ports += [interface_name(bond_master)]
854
855 # What ports do we need to add to the datapath?
856 #
857 # We definitely need the ipdev, and ordinarily we want the
858 # physical devices too, but for bonds we need the bond as bridge
859 # port.
860 add_ports = [ipdev, datapath]
861 if not bond_master:
862 add_ports += physdevs
863 else:
864 add_ports += [interface_name(bond_master)]
865
866 # What ports do we need to delete?
867 #
868 # - All the ports that we add, to avoid duplication and to drop
869 # them from another datapath in case they're misassigned.
870 #
871 # - The physical devices, since they will either be in add_ports
872 # or added to the bonding device (see below).
873 #
874 # - The bond masters for pif. (Ordinarily pif shouldn't have any
875 # bond masters. If it does then interface-reconfigure is
876 # implicitly being asked to take them down.)
877 del_ports = add_ports + physdevs + bond_masters
878
879 # What networks does this datapath carry?
880 #
881 # - The network corresponding to the datapath's PIF.
882 #
883 # - The networks corresponding to any VLANs attached to the
884 # datapath's PIF.
885 network_uuids = []
886 for nwpif in db.get_pifs_by_record({'device': pifrec['device'],
887 'host': pifrec['host']}):
888 net = db.get_pif_record(nwpif)['network']
889 network_uuids += [db.get_network_record(net)['uuid']]
890
891 # Bring up bond slaves early, because ovs-vswitchd initially
892 # enables or disables bond slaves based on whether carrier is
893 # detected when they are added, and a network device that is down
894 # always reports "no carrier".
895 bond_slave_physdevs = []
896 for slave in bond_slaves:
897 bond_slave_physdevs += physdev_names(slave)
898 for slave_physdev in bond_slave_physdevs:
899 up_netdev(slave_physdev)
900
901 # Now modify the ovs-vswitchd config file.
902 argv = []
903 for port in set(del_ports):
904 argv += interface_deconfigure_commands(port)
905 for port in set(add_ports):
906 argv += ['--add=bridge.%s.port=%s' % (datapath, port)]
907 if vlan_slave:
908 argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
909 argv += ['--add=iface.%s.internal=true' % (ipdev)]
910
911 # xapi creates a bridge by the name of the ipdev and requires
912 # that the IP address will be on it. We need to delete this
913 # bridge because we need that device to be a member of our
914 # datapath.
915 argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev]
916
917 # xapi insists that its attempts to create the bridge succeed,
918 # so force that to happen.
919 argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
920 else:
921 try:
922 os.unlink("%s/br-%s" % (vswitch_config_dir, bridge))
923 except OSError:
924 pass
925 argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath]
926 argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid)
927 for uuid in set(network_uuids)]
928 if bond_master:
929 argv += configure_bond(bond_master)
930 modify_config(argv)
931
932 # Configure network devices.
933 configure_netdev(pif)
934
935 # Bring up VLAN slave, plus physical devices other than bond
936 # slaves (which we brought up earlier).
937 if vlan_slave:
938 up_netdev(ipdev_name(vlan_slave))
939 for physdev in set(physdevs) - set(bond_slave_physdevs):
940 up_netdev(physdev)
941
942 # Update /etc/issue (which contains the IP address of the management interface)
943 os.system("/sbin/update-issue")
944
945 if bond_slaves:
946 # There seems to be a race somewhere: without this sleep, using
947 # XenCenter to create a bond that becomes the management interface
948 # fails with "The underlying connection was closed: A connection that
949 # was expected to be kept alive was closed by the server." on every
950 # second or third try, even though /var/log/messages doesn't show
951 # anything unusual.
952 #
953 # The race is probably present even without vswitch, but bringing up a
954 # bond without vswitch involves a built-in pause of 10 seconds or more
955 # to wait for the bond to transition from learning to forwarding state.
956 time.sleep(5)
957
958 def action_down(pif):
959 rec = db.get_pif_record(pif)
960 interface = interface_name(pif)
961 bridge = bridge_name(pif)
962 ipdev = ipdev_name(pif)
963
964 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
965 # files up-to-date, even though we don't use them or need them.
966 f = unconfigure_pif(pif)
967 if bridge:
968 br = open_network_ifcfg(pif)
969 log("Unlinking stale file %s" % br.path())
970 br.unlink()
971 f.attach_child(br)
972 try:
973 f.apply()
974 f.commit()
975 except Error, e:
976 log("action_down failed to apply changes: %s" % e.msg)
977 f.revert()
978 raise
979
980 argv = []
981 if rec['VLAN'] != '-1':
982 # Get rid of the VLAN device itself.
983 down_netdev(ipdev)
984 argv += interface_deconfigure_commands(ipdev)
985
986 # If the VLAN's slave is attached, stop here.
987 slave = get_vlan_slave_of_pif(pif)
988 if db.get_pif_record(slave)['currently_attached']:
989 log("VLAN slave is currently attached")
990 modify_config(argv)
991 return
992
993 # If the VLAN's slave has other VLANs that are attached, stop here.
994 masters = get_vlan_masters_of_pif(slave)
995 for m in masters:
996 if m != pif and db.get_pif_record(m)['currently_attached']:
997 log("VLAN slave has other master %s" % interface_naem(m))
998 modify_config(argv)
999 return
1000
1001 # Otherwise, take down the VLAN's slave too.
1002 log("No more masters, bring down vlan slave %s" % interface_name(slave))
1003 pif = slave
1004 else:
1005 # Stop here if this PIF has attached VLAN masters.
1006 vlan_masters = get_vlan_masters_of_pif(pif)
1007 log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters]))
1008 for m in vlan_masters:
1009 if db.get_pif_record(m)['currently_attached']:
1010 log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m)))
1011 return
1012
1013 # pif is now either a bond or a physical device which needs to be
1014 # brought down. pif might have changed so re-check all its attributes.
1015 rec = db.get_pif_record(pif)
1016 interface = interface_name(pif)
1017 bridge = bridge_name(pif)
1018 ipdev = ipdev_name(pif)
1019
1020
1021 bond_slaves = get_bond_slaves_of_pif(pif)
1022 log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves]))
1023 for slave in bond_slaves:
1024 slave_interface = interface_name(slave)
1025 log("bring down bond slave %s" % slave_interface)
1026 argv += interface_deconfigure_commands(slave_interface)
1027 down_netdev(slave_interface)
1028
1029 argv += interface_deconfigure_commands(ipdev)
1030 down_netdev(ipdev)
1031
1032 argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)]
1033 argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface]
1034 modify_config(argv)
1035
1036 def action_rewrite(pif):
1037 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1038 # files up-to-date, even though we don't use them or need them.
1039 pifrec = db.get_pif_record(pif)
1040 f = configure_pif(pif)
1041 interface = interface_name(pif)
1042 bridge = bridge_name(pif)
1043 mode = pifrec['ip_configuration_mode']
1044 if bridge:
1045 log("Configuring %s using %s configuration" % (bridge, mode))
1046 br = open_network_ifcfg(pif)
1047 configure_network(pif, br)
1048 br.close()
1049 f.attach_child(br)
1050 else:
1051 log("Configuring %s using %s configuration" % (interface, mode))
1052 configure_network(pif, f)
1053 f.close()
1054 try:
1055 f.apply()
1056 f.commit()
1057 except Error, e:
1058 log("failed to apply changes: %s" % e.msg)
1059 f.revert()
1060 raise
1061
1062 # We have no code of our own to run here.
1063 pass
1064
1065 def main(argv=None):
1066 global output_directory, management_pif
1067
1068 session = None
1069 pif_uuid = None
1070 pif = None
1071
1072 force_interface = None
1073 force_management = False
1074
1075 if argv is None:
1076 argv = sys.argv
1077
1078 try:
1079 try:
1080 shortops = "h"
1081 longops = [ "output-directory=",
1082 "pif=", "pif-uuid=",
1083 "session=",
1084 "force=",
1085 "force-interface=",
1086 "management",
1087 "test-mode",
1088 "device=", "mode=", "ip=", "netmask=", "gateway=",
1089 "help" ]
1090 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1091 except getopt.GetoptError, msg:
1092 raise Usage(msg)
1093
1094 force_rewrite_config = {}
1095
1096 for o,a in arglist:
1097 if o == "--output-directory":
1098 output_directory = a
1099 elif o == "--pif":
1100 pif = a
1101 elif o == "--pif-uuid":
1102 pif_uuid = a
1103 elif o == "--session":
1104 session = a
1105 elif o == "--force-interface" or o == "--force":
1106 force_interface = a
1107 elif o == "--management":
1108 force_management = True
1109 elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1110 force_rewrite_config[o[2:]] = a
1111 elif o == "-h" or o == "--help":
1112 print __doc__ % {'command-name': os.path.basename(argv[0])}
1113 return 0
1114
1115 if not debug_mode():
1116 syslog.openlog(os.path.basename(argv[0]))
1117 log("Called as " + str.join(" ", argv))
1118 if len(args) < 1:
1119 raise Usage("Required option <action> not present")
1120 if len(args) > 1:
1121 raise Usage("Too many arguments")
1122
1123 action = args[0]
1124 # backwards compatibility
1125 if action == "rewrite-configuration": action = "rewrite"
1126
1127 if output_directory and ( session or pif ):
1128 raise Usage("--session/--pif cannot be used with --output-directory")
1129 if ( session or pif ) and pif_uuid:
1130 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1131 if ( session and not pif ) or ( not session and pif ):
1132 raise Usage("--session and --pif must be used together.")
1133 if force_interface and ( session or pif or pif_uuid ):
1134 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1135 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1136 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1137
1138 global db
1139 if force_interface:
1140 log("Force interface %s %s" % (force_interface, action))
1141
1142 if action == "rewrite":
1143 action_force_rewrite(force_interface, force_rewrite_config)
1144 else:
1145 db = DatabaseCache(cache_file=dbcache_file)
1146 host = db.extras['host']
1147 pif = db.get_pif_by_bridge(host, force_interface)
1148 management_pif = db.get_management_pif(host)
1149
1150 if action == "up":
1151 action_up(pif)
1152 elif action == "down":
1153 action_down(pif)
1154 else:
1155 raise Usage("Unknown action %s" % action)
1156 else:
1157 db = DatabaseCache(session_ref=session)
1158
1159 if pif_uuid:
1160 pif = db.get_pif_by_uuid(pif_uuid)
1161
1162 if not pif:
1163 raise Usage("No PIF given")
1164
1165 if force_management:
1166 # pif is going to be the management pif
1167 management_pif = pif
1168 else:
1169 # pif is not going to be the management pif.
1170 # Search DB cache for pif on same host with management=true
1171 pifrec = db.get_pif_record(pif)
1172 host = pifrec['host']
1173 management_pif = db.get_management_pif(host)
1174
1175 log_pif_action(action, pif)
1176
1177 if not check_allowed(pif):
1178 return 0
1179
1180 if action == "up":
1181 action_up(pif)
1182 elif action == "down":
1183 action_down(pif)
1184 elif action == "rewrite":
1185 action_rewrite(pif)
1186 else:
1187 raise Usage("Unknown action %s" % action)
1188
1189 # Save cache.
1190 pifrec = db.get_pif_record(pif)
1191 db.save(dbcache_file, {'host': pifrec['host']})
1192
1193 except Usage, err:
1194 print >>sys.stderr, err.msg
1195 print >>sys.stderr, "For help use --help."
1196 return 2
1197 except Error, err:
1198 log(err.msg)
1199 return 1
1200
1201 return 0
1202 \f
1203 # The following code allows interface-reconfigure to keep Centos
1204 # network configuration files up-to-date, even though the vswitch
1205 # never uses them. In turn, that means that "rpm -e vswitch" does not
1206 # have to update any configuration files.
1207
1208 def configure_ethtool(oc, f):
1209 # Options for "ethtool -s"
1210 settings = None
1211 setting_opts = ["autoneg", "speed", "duplex"]
1212 # Options for "ethtool -K"
1213 offload = None
1214 offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
1215
1216 for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
1217 val = oc["ethtool-" + opt]
1218
1219 if opt in ["speed"]:
1220 if val in ["10", "100", "1000"]:
1221 val = "speed " + val
1222 else:
1223 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
1224 val = None
1225 elif opt in ["duplex"]:
1226 if val in ["half", "full"]:
1227 val = "duplex " + val
1228 else:
1229 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
1230 val = None
1231 elif opt in ["autoneg"] + offload_opts:
1232 if val in ["true", "on"]:
1233 val = opt + " on"
1234 elif val in ["false", "off"]:
1235 val = opt + " off"
1236 else:
1237 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
1238 val = None
1239
1240 if opt in setting_opts:
1241 if val and settings:
1242 settings = settings + " " + val
1243 else:
1244 settings = val
1245 elif opt in offload_opts:
1246 if val and offload:
1247 offload = offload + " " + val
1248 else:
1249 offload = val
1250
1251 if settings:
1252 f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
1253 if offload:
1254 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
1255
1256 def configure_mtu(oc, f):
1257 if not oc.has_key('mtu'):
1258 return
1259
1260 try:
1261 mtu = int(oc['mtu'])
1262 f.write("MTU=%d\n" % mtu)
1263 except ValueError, x:
1264 log("Invalid value for mtu = %s" % mtu)
1265
1266 def configure_static_routes(interface, oc, f):
1267 """Open a route-<interface> file for static routes.
1268
1269 Opens the static routes configuration file for interface and writes one
1270 line for each route specified in the network's other config "static-routes" value.
1271 E.g. if
1272 interface ( RO): xenbr1
1273 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1274
1275 Then route-xenbr1 should be
1276 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1277 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1278 """
1279 fname = "route-%s" % interface
1280 if oc.has_key('static-routes'):
1281 # The key is present - extract comma seperates entries
1282 lines = oc['static-routes'].split(',')
1283 else:
1284 # The key is not present, i.e. there are no static routes
1285 lines = []
1286
1287 child = ConfigurationFile(fname)
1288 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1289 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
1290
1291 try:
1292 for l in lines:
1293 network, masklen, gateway = l.split('/')
1294 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1295
1296 f.attach_child(child)
1297 child.close()
1298
1299 except ValueError, e:
1300 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1301
1302 def __open_ifcfg(interface):
1303 """Open a network interface configuration file.
1304
1305 Opens the configuration file for interface, writes a header and
1306 common options and returns the file object.
1307 """
1308 fname = "ifcfg-%s" % interface
1309 f = ConfigurationFile(fname)
1310
1311 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1312 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1313 f.write("XEMANAGED=yes\n")
1314 f.write("DEVICE=%s\n" % interface)
1315 f.write("ONBOOT=no\n")
1316
1317 return f
1318
1319 def open_network_ifcfg(pif):
1320 bridge = bridge_name(pif)
1321 interface = interface_name(pif)
1322 if bridge:
1323 return __open_ifcfg(bridge)
1324 else:
1325 return __open_ifcfg(interface)
1326
1327
1328 def open_pif_ifcfg(pif):
1329 pifrec = db.get_pif_record(pif)
1330
1331 log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
1332
1333 f = __open_ifcfg(interface_name(pif))
1334
1335 if pifrec.has_key('other_config'):
1336 configure_ethtool(pifrec['other_config'], f)
1337 configure_mtu(pifrec['other_config'], f)
1338
1339 return f
1340
1341 def configure_network(pif, f):
1342 """Write the configuration file for a network.
1343
1344 Writes configuration derived from the network object into the relevant
1345 ifcfg file. The configuration file is passed in, but if the network is
1346 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1347
1348 This routine may also write ifcfg files of the networks corresponding to other PIFs
1349 in order to maintain consistency.
1350
1351 params:
1352 pif: Opaque_ref of pif
1353 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1354 """
1355
1356 pifrec = db.get_pif_record(pif)
1357 host = pifrec['host']
1358 nw = pifrec['network']
1359 nwrec = db.get_network_record(nw)
1360 oc = None
1361 bridge = bridge_name(pif)
1362 interface = interface_name(pif)
1363 if bridge:
1364 device = bridge
1365 else:
1366 device = interface
1367
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)
1372
1373
1374 if pifrec.has_key('other_config'):
1375 oc = pifrec['other_config']
1376
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))
1382
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")
1393 else:
1394 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1395
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(',', ' '))
1401
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.
1405
1406 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1407 #
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 db.get_pif_record(__pif)['host'] == host and
1413 (not __pif in get_bond_masters_of_pif(pif)) ]
1414 other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1415
1416 peerdns_pif = None
1417 defaultroute_pif = None
1418
1419 # loop through all the pifs on this host looking for one with
1420 # other-config:peerdns = true, and one with
1421 # other-config:default-route=true
1422 for __pif in pifs_on_host:
1423 __pifrec = db.get_pif_record(__pif)
1424 __oc = __pifrec['other_config']
1425 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1426 if peerdns_pif == None:
1427 peerdns_pif = __pif
1428 else:
1429 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1430 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1431 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1432 if defaultroute_pif == None:
1433 defaultroute_pif = __pif
1434 else:
1435 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1436 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1437
1438 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1439 if peerdns_pif == None:
1440 peerdns_pif = management_pif
1441 if defaultroute_pif == None:
1442 defaultroute_pif = management_pif
1443
1444 # Update all the other network's ifcfg files and ensure consistency
1445 for __pif in other_pifs_on_host:
1446 __f = open_network_ifcfg(__pif)
1447 peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1448 lines = __f.readlines()
1449
1450 if not peerdns_line_wanted in lines:
1451 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1452 for line in lines:
1453 if not line.lstrip().startswith('PEERDNS'):
1454 __f.write(line)
1455 log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1456 __f.write(peerdns_line_wanted)
1457 __f.close()
1458 f.attach_child(__f)
1459
1460 else:
1461 # There is no need to change this ifcfg file. So don't attach_child.
1462 pass
1463
1464 # ... and for this pif too
1465 f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1466
1467 # Update gatewaydev
1468 fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1469 for line in fnetwork.readlines():
1470 if line.lstrip().startswith('GATEWAY') :
1471 continue
1472 fnetwork.write(line)
1473 if defaultroute_pif:
1474 gatewaydev = bridge_name(defaultroute_pif)
1475 if not gatewaydev:
1476 gatewaydev = interface_name(defaultroute_pif)
1477 fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1478 fnetwork.close()
1479 f.attach_child(fnetwork)
1480
1481 return
1482
1483
1484 def configure_physical_interface(pif):
1485 """Write the configuration for a physical interface.
1486
1487 Writes the configuration file for the physical interface described by
1488 the pif object.
1489
1490 Returns the open file handle for the interface configuration file.
1491 """
1492
1493 pifrec = db.get_pif_record(pif)
1494
1495 f = open_pif_ifcfg(pif)
1496
1497 f.write("TYPE=Ethernet\n")
1498 f.write("HWADDR=%(MAC)s\n" % pifrec)
1499
1500 return f
1501
1502 def configure_bond_interface(pif):
1503 """Write the configuration for a bond interface.
1504
1505 Writes the configuration file for the bond interface described by
1506 the pif object. Handles writing the configuration for the slave
1507 interfaces.
1508
1509 Returns the open file handle for the bond interface configuration
1510 file.
1511 """
1512
1513 pifrec = db.get_pif_record(pif)
1514 oc = pifrec['other_config']
1515 f = open_pif_ifcfg(pif)
1516
1517 if pifrec['MAC'] != "":
1518 f.write("MACADDR=%s\n" % pifrec['MAC'])
1519
1520 for slave in get_bond_slaves_of_pif(pif):
1521 s = configure_physical_interface(slave)
1522 s.write("MASTER=%(device)s\n" % pifrec)
1523 s.write("SLAVE=yes\n")
1524 s.close()
1525 f.attach_child(s)
1526
1527 # The bond option defaults
1528 bond_options = {
1529 "mode": "balance-slb",
1530 "miimon": "100",
1531 "downdelay": "200",
1532 "updelay": "31000",
1533 "use_carrier": "1",
1534 }
1535
1536 # override defaults with values from other-config whose keys being with "bond-"
1537 overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
1538 overrides = map(lambda (key,val): (key[5:], val), overrides)
1539 bond_options.update(overrides)
1540
1541 # write the bond options to ifcfg-bondX
1542 f.write('BONDING_OPTS="')
1543 for (name,val) in bond_options.items():
1544 f.write("%s=%s " % (name,val))
1545 f.write('"\n')
1546 return f
1547
1548 def configure_vlan_interface(pif):
1549 """Write the configuration for a VLAN interface.
1550
1551 Writes the configuration file for the VLAN interface described by
1552 the pif object. Handles writing the configuration for the master
1553 interface if necessary.
1554
1555 Returns the open file handle for the VLAN interface configuration
1556 file.
1557 """
1558
1559 slave = configure_pif(get_vlan_slave_of_pif(pif))
1560 slave.close()
1561
1562 f = open_pif_ifcfg(pif)
1563 f.write("VLAN=yes\n")
1564 f.attach_child(slave)
1565
1566 return f
1567
1568 def configure_pif(pif):
1569 """Write the configuration for a PIF object.
1570
1571 Writes the configuration file the PIF and all dependent
1572 interfaces (bond slaves and VLAN masters etc).
1573
1574 Returns the open file handle for the interface configuration file.
1575 """
1576
1577 pifrec = db.get_pif_record(pif)
1578
1579 if pifrec['VLAN'] != '-1':
1580 f = configure_vlan_interface(pif)
1581 elif len(pifrec['bond_master_of']) != 0:
1582 f = configure_bond_interface(pif)
1583 else:
1584 f = configure_physical_interface(pif)
1585
1586 bridge = bridge_name(pif)
1587 if bridge:
1588 f.write("BRIDGE=%s\n" % bridge)
1589
1590 return f
1591
1592 def unconfigure_pif(pif):
1593 """Clear up the files created by configure_pif"""
1594 f = open_pif_ifcfg(pif)
1595 log("Unlinking stale file %s" % f.path())
1596 f.unlink()
1597 return f
1598 \f
1599 if __name__ == "__main__":
1600 rc = 1
1601 try:
1602 rc = main()
1603 except:
1604 ex = sys.exc_info()
1605 err = traceback.format_exception(*ex)
1606 for exline in err:
1607 log(exline)
1608
1609 if not debug_mode():
1610 syslog.closelog()
1611
1612 sys.exit(rc)