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