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