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