]> git.proxmox.com Git - ovs.git/blob - xenserver/opt_xensource_libexec_InterfaceReconfigure.py
Merge "master" into "next".
[ovs.git] / xenserver / opt_xensource_libexec_InterfaceReconfigure.py
1 # Copyright (c) 2008,2009 Citrix Systems, Inc.
2 #
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU Lesser General Public License as published
5 # by the Free Software Foundation; version 2.1 only. with the special
6 # exception on linking described in file LICENSE.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU Lesser General Public License for more details.
12 #
13 import syslog
14 import os
15
16 from xml.dom.minidom import getDOMImplementation
17 from xml.dom.minidom import parse as parseXML
18
19 #
20 # Logging.
21 #
22
23 def log(s):
24 syslog.syslog(s)
25
26 #
27 # Exceptions.
28 #
29
30 class Error(Exception):
31 def __init__(self, msg):
32 Exception.__init__(self)
33 self.msg = msg
34
35 #
36 # Run external utilities
37 #
38
39 def run_command(command):
40 log("Running command: " + ' '.join(command))
41 rc = os.spawnl(os.P_WAIT, command[0], *command)
42 if rc != 0:
43 log("Command failed %d: " % rc + ' '.join(command))
44 return False
45 return True
46
47 #
48 # Configuration File Handling.
49 #
50
51 class ConfigurationFile(object):
52 """Write a file, tracking old and new versions.
53
54 Supports writing a new version of a file and applying and
55 reverting those changes.
56 """
57
58 __STATE = {"OPEN":"OPEN",
59 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
60 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
61
62 def __init__(self, path):
63 dirname,basename = os.path.split(path)
64
65 self.__state = self.__STATE['OPEN']
66 self.__children = []
67
68 self.__path = os.path.join(dirname, basename)
69 self.__oldpath = os.path.join(dirname, "." + basename + ".xapi-old")
70 self.__newpath = os.path.join(dirname, "." + basename + ".xapi-new")
71
72 self.__f = open(self.__newpath, "w")
73
74 def attach_child(self, child):
75 self.__children.append(child)
76
77 def path(self):
78 return self.__path
79
80 def readlines(self):
81 try:
82 return open(self.path()).readlines()
83 except:
84 return ""
85
86 def write(self, args):
87 if self.__state != self.__STATE['OPEN']:
88 raise Error("Attempt to write to file in state %s" % self.__state)
89 self.__f.write(args)
90
91 def close(self):
92 if self.__state != self.__STATE['OPEN']:
93 raise Error("Attempt to close file in state %s" % self.__state)
94
95 self.__f.close()
96 self.__state = self.__STATE['NOT-APPLIED']
97
98 def changed(self):
99 if self.__state != self.__STATE['NOT-APPLIED']:
100 raise Error("Attempt to compare file in state %s" % self.__state)
101
102 return True
103
104 def apply(self):
105 if self.__state != self.__STATE['NOT-APPLIED']:
106 raise Error("Attempt to apply configuration from state %s" % self.__state)
107
108 for child in self.__children:
109 child.apply()
110
111 log("Applying changes to %s configuration" % self.__path)
112
113 # Remove previous backup.
114 if os.access(self.__oldpath, os.F_OK):
115 os.unlink(self.__oldpath)
116
117 # Save current configuration.
118 if os.access(self.__path, os.F_OK):
119 os.link(self.__path, self.__oldpath)
120 os.unlink(self.__path)
121
122 # Apply new configuration.
123 assert(os.path.exists(self.__newpath))
124 os.link(self.__newpath, self.__path)
125
126 # Remove temporary file.
127 os.unlink(self.__newpath)
128
129 self.__state = self.__STATE['APPLIED']
130
131 def revert(self):
132 if self.__state != self.__STATE['APPLIED']:
133 raise Error("Attempt to revert configuration from state %s" % self.__state)
134
135 for child in self.__children:
136 child.revert()
137
138 log("Reverting changes to %s configuration" % self.__path)
139
140 # Remove existing new configuration
141 if os.access(self.__newpath, os.F_OK):
142 os.unlink(self.__newpath)
143
144 # Revert new configuration.
145 if os.access(self.__path, os.F_OK):
146 os.link(self.__path, self.__newpath)
147 os.unlink(self.__path)
148
149 # Revert to old configuration.
150 if os.access(self.__oldpath, os.F_OK):
151 os.link(self.__oldpath, self.__path)
152 os.unlink(self.__oldpath)
153
154 # Leave .*.xapi-new as an aid to debugging.
155
156 self.__state = self.__STATE['REVERTED']
157
158 def commit(self):
159 if self.__state != self.__STATE['APPLIED']:
160 raise Error("Attempt to commit configuration from state %s" % self.__state)
161
162 for child in self.__children:
163 child.commit()
164
165 log("Committing changes to %s configuration" % self.__path)
166
167 if os.access(self.__oldpath, os.F_OK):
168 os.unlink(self.__oldpath)
169 if os.access(self.__newpath, os.F_OK):
170 os.unlink(self.__newpath)
171
172 self.__state = self.__STATE['COMMITTED']
173
174 #
175 # Helper functions for encoding/decoding database attributes to/from XML.
176 #
177
178 def _str_to_xml(xml, parent, tag, val):
179 e = xml.createElement(tag)
180 parent.appendChild(e)
181 v = xml.createTextNode(val)
182 e.appendChild(v)
183 def _str_from_xml(n):
184 def getText(nodelist):
185 rc = ""
186 for node in nodelist:
187 if node.nodeType == node.TEXT_NODE:
188 rc = rc + node.data
189 return rc
190 return getText(n.childNodes).strip()
191
192 def _bool_to_xml(xml, parent, tag, val):
193 if val:
194 _str_to_xml(xml, parent, tag, "True")
195 else:
196 _str_to_xml(xml, parent, tag, "False")
197 def _bool_from_xml(n):
198 s = _str_from_xml(n)
199 if s == "True":
200 return True
201 elif s == "False":
202 return False
203 else:
204 raise Error("Unknown boolean value %s" % s)
205
206 def _strlist_to_xml(xml, parent, ltag, itag, val):
207 e = xml.createElement(ltag)
208 parent.appendChild(e)
209 for v in val:
210 c = xml.createElement(itag)
211 e.appendChild(c)
212 cv = xml.createTextNode(v)
213 c.appendChild(cv)
214 def _strlist_from_xml(n, ltag, itag):
215 ret = []
216 for n in n.childNodes:
217 if n.nodeName == itag:
218 ret.append(_str_from_xml(n))
219 return ret
220
221 def _otherconfig_to_xml(xml, parent, val, attrs):
222 otherconfig = xml.createElement("other_config")
223 parent.appendChild(otherconfig)
224 for n,v in val.items():
225 if not n in attrs:
226 raise Error("Unknown other-config attribute: %s" % n)
227 _str_to_xml(xml, otherconfig, n, v)
228 def _otherconfig_from_xml(n, attrs):
229 ret = {}
230 for n in n.childNodes:
231 if n.nodeName in attrs:
232 ret[n.nodeName] = _str_from_xml(n)
233 return ret
234
235 #
236 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
237 #
238 # Each object is defined by a dictionary mapping an attribute name in
239 # the xapi database to a tuple containing two items:
240 # - a function which takes this attribute and encodes it as XML.
241 # - a function which takes XML and decocdes it into a value.
242 #
243 # other-config attributes are specified as a simple array of strings
244
245 _PIF_XML_TAG = "pif"
246 _VLAN_XML_TAG = "vlan"
247 _BOND_XML_TAG = "bond"
248 _NETWORK_XML_TAG = "network"
249
250 _ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
251
252 _PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
253 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
254 _ETHTOOL_OTHERCONFIG_ATTRS
255
256 _PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
257 'management': (_bool_to_xml,_bool_from_xml),
258 'network': (_str_to_xml,_str_from_xml),
259 'device': (_str_to_xml,_str_from_xml),
260 'bond_master_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
261 lambda n: _strlist_from_xml(n, 'bond_master_of', 'slave')),
262 'bond_slave_of': (_str_to_xml,_str_from_xml),
263 'VLAN': (_str_to_xml,_str_from_xml),
264 'VLAN_master_of': (_str_to_xml,_str_from_xml),
265 'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
266 lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
267 'ip_configuration_mode': (_str_to_xml,_str_from_xml),
268 'IP': (_str_to_xml,_str_from_xml),
269 'netmask': (_str_to_xml,_str_from_xml),
270 'gateway': (_str_to_xml,_str_from_xml),
271 'DNS': (_str_to_xml,_str_from_xml),
272 'MAC': (_str_to_xml,_str_from_xml),
273 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _PIF_OTHERCONFIG_ATTRS),
274 lambda n: _otherconfig_from_xml(n, _PIF_OTHERCONFIG_ATTRS)),
275
276 # Special case: We write the current value
277 # PIF.currently-attached to the cache but since it will
278 # not be valid when we come to use the cache later
279 # (i.e. after a reboot) we always read it as False.
280 'currently_attached': (_bool_to_xml, lambda n: False),
281 }
282
283 _VLAN_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
284 'tagged_PIF': (_str_to_xml,_str_from_xml),
285 'untagged_PIF': (_str_to_xml,_str_from_xml),
286 }
287
288 _BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
289 'master': (_str_to_xml,_str_from_xml),
290 'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
291 lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
292 }
293
294 _NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + _ETHTOOL_OTHERCONFIG_ATTRS
295
296 _NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
297 'bridge': (_str_to_xml,_str_from_xml),
298 'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
299 lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
300 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
301 lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
302 }
303
304 #
305 # Database Cache object
306 #
307
308 _db = None
309
310 def db():
311 assert(_db is not None)
312 return _db
313
314 def db_init_from_cache(cache):
315 global _db
316 assert(_db is None)
317 _db = DatabaseCache(cache_file=cache)
318
319 def db_init_from_xenapi(session):
320 global _db
321 assert(_db is None)
322 _db = DatabaseCache(session_ref=session)
323
324 class DatabaseCache(object):
325 def __read_xensource_inventory(self):
326 filename = "/etc/xensource-inventory"
327 f = open(filename, "r")
328 lines = [x.strip("\n") for x in f.readlines()]
329 f.close()
330
331 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
332 defs = [ (a, b.strip("'")) for (a,b) in defs ]
333
334 return dict(defs)
335
336 def __pif_on_host(self,pif):
337 return self.__pifs.has_key(pif)
338
339 def __get_pif_records_from_xapi(self, session, host):
340 self.__pifs = {}
341 for (p,rec) in session.xenapi.PIF.get_all_records().items():
342 if rec['host'] != host:
343 continue
344 self.__pifs[p] = {}
345 for f in _PIF_ATTRS:
346 self.__pifs[p][f] = rec[f]
347 self.__pifs[p]['other_config'] = {}
348 for f in _PIF_OTHERCONFIG_ATTRS:
349 if not rec['other_config'].has_key(f): continue
350 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
351
352 def __get_vlan_records_from_xapi(self, session):
353 self.__vlans = {}
354 for v in session.xenapi.VLAN.get_all():
355 rec = session.xenapi.VLAN.get_record(v)
356 if not self.__pif_on_host(rec['untagged_PIF']):
357 continue
358 self.__vlans[v] = {}
359 for f in _VLAN_ATTRS:
360 self.__vlans[v][f] = rec[f]
361
362 def __get_bond_records_from_xapi(self, session):
363 self.__bonds = {}
364 for b in session.xenapi.Bond.get_all():
365 rec = session.xenapi.Bond.get_record(b)
366 if not self.__pif_on_host(rec['master']):
367 continue
368 self.__bonds[b] = {}
369 for f in _BOND_ATTRS:
370 self.__bonds[b][f] = rec[f]
371
372 def __get_network_records_from_xapi(self, session):
373 self.__networks = {}
374 for n in session.xenapi.network.get_all():
375 rec = session.xenapi.network.get_record(n)
376 self.__networks[n] = {}
377 for f in _NETWORK_ATTRS:
378 if f == "PIFs":
379 # drop PIFs on other hosts
380 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
381 else:
382 self.__networks[n][f] = rec[f]
383 self.__networks[n]['other_config'] = {}
384 for f in _NETWORK_OTHERCONFIG_ATTRS:
385 if not rec['other_config'].has_key(f): continue
386 self.__networks[n]['other_config'][f] = rec['other_config'][f]
387
388 def __to_xml(self, xml, parent, key, ref, rec, attrs):
389 """Encode a database object as XML"""
390 e = xml.createElement(key)
391 parent.appendChild(e)
392 if ref:
393 e.setAttribute('ref', ref)
394
395 for n,v in rec.items():
396 if attrs.has_key(n):
397 h,_ = attrs[n]
398 h(xml, e, n, v)
399 else:
400 raise Error("Unknown attribute %s" % n)
401 def __from_xml(self, e, attrs):
402 """Decode a database object from XML"""
403 ref = e.attributes['ref'].value
404 rec = {}
405 for n in e.childNodes:
406 if n.nodeName in attrs:
407 _,h = attrs[n.nodeName]
408 rec[n.nodeName] = h(n)
409 return (ref,rec)
410
411 def __init__(self, session_ref=None, cache_file=None):
412 if session_ref and cache_file:
413 raise Error("can't specify session reference and cache file")
414 if cache_file == None:
415 import XenAPI
416 session = XenAPI.xapi_local()
417
418 if not session_ref:
419 log("No session ref given on command line, logging in.")
420 session.xenapi.login_with_password("root", "")
421 else:
422 session._session = session_ref
423
424 try:
425
426 inventory = self.__read_xensource_inventory()
427 assert(inventory.has_key('INSTALLATION_UUID'))
428 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
429
430 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
431
432 self.__get_pif_records_from_xapi(session, host)
433
434 self.__get_vlan_records_from_xapi(session)
435 self.__get_bond_records_from_xapi(session)
436 self.__get_network_records_from_xapi(session)
437 finally:
438 if not session_ref:
439 session.xenapi.session.logout()
440 else:
441 log("Loading xapi database cache from %s" % cache_file)
442
443 xml = parseXML(cache_file)
444
445 self.__pifs = {}
446 self.__bonds = {}
447 self.__vlans = {}
448 self.__networks = {}
449
450 assert(len(xml.childNodes) == 1)
451 toplevel = xml.childNodes[0]
452
453 assert(toplevel.nodeName == "xenserver-network-configuration")
454
455 for n in toplevel.childNodes:
456 if n.nodeName == "#text":
457 pass
458 elif n.nodeName == _PIF_XML_TAG:
459 (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
460 self.__pifs[ref] = rec
461 elif n.nodeName == _BOND_XML_TAG:
462 (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
463 self.__bonds[ref] = rec
464 elif n.nodeName == _VLAN_XML_TAG:
465 (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
466 self.__vlans[ref] = rec
467 elif n.nodeName == _NETWORK_XML_TAG:
468 (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
469 self.__networks[ref] = rec
470 else:
471 raise Error("Unknown XML element %s" % n.nodeName)
472
473 def save(self, cache_file):
474
475 xml = getDOMImplementation().createDocument(
476 None, "xenserver-network-configuration", None)
477 for (ref,rec) in self.__pifs.items():
478 self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
479 for (ref,rec) in self.__bonds.items():
480 self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
481 for (ref,rec) in self.__vlans.items():
482 self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
483 for (ref,rec) in self.__networks.items():
484 self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
485 _NETWORK_ATTRS)
486
487 f = open(cache_file, 'w')
488 f.write(xml.toprettyxml())
489 f.close()
490
491 def get_pif_by_uuid(self, uuid):
492 pifs = map(lambda (ref,rec): ref,
493 filter(lambda (ref,rec): uuid == rec['uuid'],
494 self.__pifs.items()))
495 if len(pifs) == 0:
496 raise Error("Unknown PIF \"%s\"" % uuid)
497 elif len(pifs) > 1:
498 raise Error("Non-unique PIF \"%s\"" % uuid)
499
500 return pifs[0]
501
502 def get_pifs_by_device(self, device):
503 return map(lambda (ref,rec): ref,
504 filter(lambda (ref,rec): rec['device'] == device,
505 self.__pifs.items()))
506
507 def get_pif_by_bridge(self, bridge):
508 networks = map(lambda (ref,rec): ref,
509 filter(lambda (ref,rec): rec['bridge'] == bridge,
510 self.__networks.items()))
511 if len(networks) == 0:
512 raise Error("No matching network \"%s\"" % bridge)
513
514 answer = None
515 for network in networks:
516 nwrec = self.get_network_record(network)
517 for pif in nwrec['PIFs']:
518 pifrec = self.get_pif_record(pif)
519 if answer:
520 raise Error("Multiple PIFs on host for network %s" % (bridge))
521 answer = pif
522 if not answer:
523 raise Error("No PIF on host for network %s" % (bridge))
524 return answer
525
526 def get_pif_record(self, pif):
527 if self.__pifs.has_key(pif):
528 return self.__pifs[pif]
529 raise Error("Unknown PIF \"%s\"" % pif)
530 def get_all_pifs(self):
531 return self.__pifs
532 def pif_exists(self, pif):
533 return self.__pifs.has_key(pif)
534
535 def get_management_pif(self):
536 """ Returns the management pif on host
537 """
538 all = self.get_all_pifs()
539 for pif in all:
540 pifrec = self.get_pif_record(pif)
541 if pifrec['management']: return pif
542 return None
543
544 def get_network_record(self, network):
545 if self.__networks.has_key(network):
546 return self.__networks[network]
547 raise Error("Unknown network \"%s\"" % network)
548
549 def get_bond_record(self, bond):
550 if self.__bonds.has_key(bond):
551 return self.__bonds[bond]
552 else:
553 return None
554
555 def get_vlan_record(self, vlan):
556 if self.__vlans.has_key(vlan):
557 return self.__vlans[vlan]
558 else:
559 return None
560
561 #
562 #
563 #
564
565 def ethtool_settings(oc):
566 settings = []
567 if oc.has_key('ethtool-speed'):
568 val = oc['ethtool-speed']
569 if val in ["10", "100", "1000"]:
570 settings += ['speed', val]
571 else:
572 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
573 if oc.has_key('ethtool-duplex'):
574 val = oc['ethtool-duplex']
575 if val in ["10", "100", "1000"]:
576 settings += ['duplex', 'val']
577 else:
578 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
579 if oc.has_key('ethtool-autoneg'):
580 val = oc['ethtool-autoneg']
581 if val in ["true", "on"]:
582 settings += ['autoneg', 'on']
583 elif val in ["false", "off"]:
584 settings += ['autoneg', 'off']
585 else:
586 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
587 offload = []
588 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
589 if oc.has_key("ethtool-" + opt):
590 val = oc["ethtool-" + opt]
591 if val in ["true", "on"]:
592 offload += [opt, 'on']
593 elif val in ["false", "off"]:
594 offload += [opt, 'off']
595 else:
596 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
597 return settings,offload
598
599 def mtu_setting(oc):
600 if oc.has_key('mtu'):
601 try:
602 int(oc['mtu']) # Check that the value is an integer
603 return oc['mtu']
604 except ValueError, x:
605 log("Invalid value for mtu = %s" % oc['mtu'])
606 return None
607
608 #
609 # IP Network Devices -- network devices with IP configuration
610 #
611 def pif_ipdev_name(pif):
612 """Return the ipdev name associated with pif"""
613 pifrec = db().get_pif_record(pif)
614 nwrec = db().get_network_record(pifrec['network'])
615
616 if nwrec['bridge']:
617 # TODO: sanity check that nwrec['bridgeless'] != 'true'
618 return nwrec['bridge']
619 else:
620 # TODO: sanity check that nwrec['bridgeless'] == 'true'
621 return pif_netdev_name(pif)
622
623 #
624 # Bare Network Devices -- network devices without IP configuration
625 #
626
627 def netdev_exists(netdev):
628 return os.path.exists("/sys/class/net/" + netdev)
629
630 def pif_netdev_name(pif):
631 """Get the netdev name for a PIF."""
632
633 pifrec = db().get_pif_record(pif)
634
635 if pif_is_vlan(pif):
636 return "%(device)s.%(VLAN)s" % pifrec
637 else:
638 return pifrec['device']
639
640 #
641 # Bonded PIFs
642 #
643 def pif_is_bond(pif):
644 pifrec = db().get_pif_record(pif)
645
646 return len(pifrec['bond_master_of']) > 0
647
648 def pif_get_bond_masters(pif):
649 """Returns a list of PIFs which are bond masters of this PIF"""
650
651 pifrec = db().get_pif_record(pif)
652
653 bso = pifrec['bond_slave_of']
654
655 # bond-slave-of is currently a single reference but in principle a
656 # PIF could be a member of several bonds which are not
657 # concurrently attached. Be robust to this possibility.
658 if not bso or bso == "OpaqueRef:NULL":
659 bso = []
660 elif not type(bso) == list:
661 bso = [bso]
662
663 bondrecs = [db().get_bond_record(bond) for bond in bso]
664 bondrecs = [rec for rec in bondrecs if rec]
665
666 return [bond['master'] for bond in bondrecs]
667
668 def pif_get_bond_slaves(pif):
669 """Returns a list of PIFs which make up the given bonded pif."""
670
671 pifrec = db().get_pif_record(pif)
672
673 bmo = pifrec['bond_master_of']
674 if len(bmo) > 1:
675 raise Error("Bond-master-of contains too many elements")
676
677 if len(bmo) == 0:
678 return []
679
680 bondrec = db().get_bond_record(bmo[0])
681 if not bondrec:
682 raise Error("No bond record for bond master PIF")
683
684 return bondrec['slaves']
685
686 #
687 # VLAN PIFs
688 #
689
690 def pif_is_vlan(pif):
691 return db().get_pif_record(pif)['VLAN'] != '-1'
692
693 def pif_get_vlan_slave(pif):
694 """Find the PIF which is the VLAN slave of pif.
695
696 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
697
698 pifrec = db().get_pif_record(pif)
699
700 vlan = pifrec['VLAN_master_of']
701 if not vlan or vlan == "OpaqueRef:NULL":
702 raise Error("PIF is not a VLAN master")
703
704 vlanrec = db().get_vlan_record(vlan)
705 if not vlanrec:
706 raise Error("No VLAN record found for PIF")
707
708 return vlanrec['tagged_PIF']
709
710 def pif_get_vlan_masters(pif):
711 """Returns a list of PIFs which are VLANs on top of the given pif."""
712
713 pifrec = db().get_pif_record(pif)
714 vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
715 return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
716
717 #
718 # Datapath base class
719 #
720
721 class Datapath(object):
722 """Object encapsulating the actions necessary to (de)configure the
723 datapath for a given PIF. Does not include configuration of the
724 IP address on the ipdev.
725 """
726
727 def __init__(self, pif):
728 self._pif = pif
729
730 def configure_ipdev(self, cfg):
731 """Write ifcfg TYPE field for an IPdev, plus any type specific
732 fields to cfg
733 """
734 raise NotImplementedError
735
736 def preconfigure(self, parent):
737 """Prepare datapath configuration for PIF, but do not actually
738 apply any changes.
739
740 Any configuration files should be attached to parent.
741 """
742 raise NotImplementedError
743
744 def bring_down_existing(self):
745 """Tear down any existing network device configuration which
746 needs to be undone in order to bring this PIF up.
747 """
748 raise NotImplementedError
749
750 def configure(self):
751 """Apply the configuration prepared in the preconfigure stage.
752
753 Should assume any configuration files changed attached in
754 the preconfigure stage are applied and bring up the
755 necesary devices to provide the datapath for the
756 PIF.
757
758 Should not bring up the IPdev.
759 """
760 raise NotImplementedError
761
762 def post(self):
763 """Called after the IPdev has been brought up.
764
765 Should do any final setup, including reinstating any
766 devices which were taken down in the bring_down_existing
767 hook.
768 """
769 raise NotImplementedError
770
771 def bring_down(self):
772 """Tear down and deconfigure the datapath. Should assume the
773 IPdev has already been brought down.
774 """
775 raise NotImplementedError
776
777 def DatapathFactory(pif):
778 # XXX Need a datapath object for bridgeless PIFs
779
780 try:
781 network_conf = open("/etc/xensource/network.conf", 'r')
782 network_backend = network_conf.readline().strip()
783 network_conf.close()
784 except Exception, e:
785 raise Error("failed to determine network backend:" + e)
786
787 if network_backend == "bridge":
788 from InterfaceReconfigureBridge import DatapathBridge
789 return DatapathBridge(pif)
790 elif network_backend == "vswitch":
791 from InterfaceReconfigureVswitch import DatapathVswitch
792 return DatapathVswitch(pif)
793 else:
794 raise Error("unknown network backend %s" % network_backend)