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