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