1 # Copyright (c) 2008,2009 Citrix Systems, Inc.
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.
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.
17 from xml.dom.minidom import getDOMImplementation
18 from xml.dom.minidom import parse as parseXML
22 """Returns a string to prefix to all file name references, which
23 is useful for testing."""
24 return the_root_prefix
25 def set_root_prefix(prefix):
26 global the_root_prefix
27 the_root_prefix = prefix
29 log_destination = "syslog"
30 def get_log_destination():
31 """Returns the current log destination.
32 'syslog' means "log to syslog".
33 'stderr' means "log to stderr"."""
34 return log_destination
35 def set_log_destination(dest):
36 global log_destination
37 log_destination = dest
44 if get_log_destination() == 'syslog':
53 class Error(Exception):
54 def __init__(self, msg):
55 Exception.__init__(self)
59 # Run external utilities
62 def run_command(command):
63 log("Running command: " + ' '.join(command))
64 rc = os.spawnl(os.P_WAIT, root_prefix() + command[0], *command)
66 log("Command failed %d: " % rc + ' '.join(command))
71 # Configuration File Handling.
74 class ConfigurationFile(object):
75 """Write a file, tracking old and new versions.
77 Supports writing a new version of a file and applying and
78 reverting those changes.
81 __STATE = {"OPEN":"OPEN",
82 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
83 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
85 def __init__(self, path):
86 dirname,basename = os.path.split(path)
88 self.__state = self.__STATE['OPEN']
91 self.__path = os.path.join(dirname, basename)
92 self.__oldpath = os.path.join(dirname, "." + basename + ".xapi-old")
93 self.__newpath = os.path.join(dirname, "." + basename + ".xapi-new")
95 self.__f = open(self.__newpath, "w")
97 def attach_child(self, child):
98 self.__children.append(child)
105 return open(self.path()).readlines()
109 def write(self, args):
110 if self.__state != self.__STATE['OPEN']:
111 raise Error("Attempt to write to file in state %s" % self.__state)
115 if self.__state != self.__STATE['OPEN']:
116 raise Error("Attempt to close file in state %s" % self.__state)
119 self.__state = self.__STATE['NOT-APPLIED']
122 if self.__state != self.__STATE['NOT-APPLIED']:
123 raise Error("Attempt to compare file in state %s" % self.__state)
128 if self.__state != self.__STATE['NOT-APPLIED']:
129 raise Error("Attempt to apply configuration from state %s" % self.__state)
131 for child in self.__children:
134 log("Applying changes to %s configuration" % self.__path)
136 # Remove previous backup.
137 if os.access(self.__oldpath, os.F_OK):
138 os.unlink(self.__oldpath)
140 # Save current configuration.
141 if os.access(self.__path, os.F_OK):
142 os.link(self.__path, self.__oldpath)
143 os.unlink(self.__path)
145 # Apply new configuration.
146 assert(os.path.exists(self.__newpath))
147 os.link(self.__newpath, self.__path)
149 # Remove temporary file.
150 os.unlink(self.__newpath)
152 self.__state = self.__STATE['APPLIED']
155 if self.__state != self.__STATE['APPLIED']:
156 raise Error("Attempt to revert configuration from state %s" % self.__state)
158 for child in self.__children:
161 log("Reverting changes to %s configuration" % self.__path)
163 # Remove existing new configuration
164 if os.access(self.__newpath, os.F_OK):
165 os.unlink(self.__newpath)
167 # Revert new configuration.
168 if os.access(self.__path, os.F_OK):
169 os.link(self.__path, self.__newpath)
170 os.unlink(self.__path)
172 # Revert to old configuration.
173 if os.access(self.__oldpath, os.F_OK):
174 os.link(self.__oldpath, self.__path)
175 os.unlink(self.__oldpath)
177 # Leave .*.xapi-new as an aid to debugging.
179 self.__state = self.__STATE['REVERTED']
182 if self.__state != self.__STATE['APPLIED']:
183 raise Error("Attempt to commit configuration from state %s" % self.__state)
185 for child in self.__children:
188 log("Committing changes to %s configuration" % self.__path)
190 if os.access(self.__oldpath, os.F_OK):
191 os.unlink(self.__oldpath)
192 if os.access(self.__newpath, os.F_OK):
193 os.unlink(self.__newpath)
195 self.__state = self.__STATE['COMMITTED']
198 # Helper functions for encoding/decoding database attributes to/from XML.
201 def _str_to_xml(xml, parent, tag, val):
202 e = xml.createElement(tag)
203 parent.appendChild(e)
204 v = xml.createTextNode(val)
206 def _str_from_xml(n):
207 def getText(nodelist):
209 for node in nodelist:
210 if node.nodeType == node.TEXT_NODE:
213 return getText(n.childNodes).strip()
215 def _bool_to_xml(xml, parent, tag, val):
217 _str_to_xml(xml, parent, tag, "True")
219 _str_to_xml(xml, parent, tag, "False")
220 def _bool_from_xml(n):
227 raise Error("Unknown boolean value %s" % s)
229 def _strlist_to_xml(xml, parent, ltag, itag, val):
230 e = xml.createElement(ltag)
231 parent.appendChild(e)
233 c = xml.createElement(itag)
235 cv = xml.createTextNode(v)
237 def _strlist_from_xml(n, ltag, itag):
239 for n in n.childNodes:
240 if n.nodeName == itag:
241 ret.append(_str_from_xml(n))
244 def _map_to_xml(xml, parent, tag, val, attrs):
245 e = xml.createElement(tag)
246 parent.appendChild(e)
247 for n,v in val.items():
249 _str_to_xml(xml, e, n, v)
251 log("Unknown other-config attribute: %s" % n)
253 def _map_from_xml(n, attrs):
255 for n in n.childNodes:
256 if n.nodeName in attrs:
257 ret[n.nodeName] = _str_from_xml(n)
260 def _otherconfig_to_xml(xml, parent, val, attrs):
261 return _map_to_xml(xml, parent, "other_config", val, attrs)
262 def _otherconfig_from_xml(n, attrs):
263 return _map_from_xml(n, attrs)
266 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
268 # Each object is defined by a dictionary mapping an attribute name in
269 # the xapi database to a tuple containing two items:
270 # - a function which takes this attribute and encodes it as XML.
271 # - a function which takes XML and decocdes it into a value.
273 # other-config attributes are specified as a simple array of strings
276 _VLAN_XML_TAG = "vlan"
277 _TUNNEL_XML_TAG = "tunnel"
278 _BOND_XML_TAG = "bond"
279 _NETWORK_XML_TAG = "network"
281 _ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
283 _PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
284 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
285 _ETHTOOL_OTHERCONFIG_ATTRS
287 _PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
288 'management': (_bool_to_xml,_bool_from_xml),
289 'network': (_str_to_xml,_str_from_xml),
290 'device': (_str_to_xml,_str_from_xml),
291 'bond_master_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
292 lambda n: _strlist_from_xml(n, 'bond_master_of', 'slave')),
293 'bond_slave_of': (_str_to_xml,_str_from_xml),
294 'VLAN': (_str_to_xml,_str_from_xml),
295 'VLAN_master_of': (_str_to_xml,_str_from_xml),
296 'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
297 lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
298 'tunnel_access_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_access_PIF_of', 'pif', v),
299 lambda n: _strlist_from_xml(n, 'tunnel_access_PIF_of', 'pif')),
300 'tunnel_transport_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_transport_PIF_of', 'pif', v),
301 lambda n: _strlist_from_xml(n, 'tunnel_transport_PIF_of', 'pif')),
302 'ip_configuration_mode': (_str_to_xml,_str_from_xml),
303 'IP': (_str_to_xml,_str_from_xml),
304 'netmask': (_str_to_xml,_str_from_xml),
305 'gateway': (_str_to_xml,_str_from_xml),
306 'DNS': (_str_to_xml,_str_from_xml),
307 'MAC': (_str_to_xml,_str_from_xml),
308 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _PIF_OTHERCONFIG_ATTRS),
309 lambda n: _otherconfig_from_xml(n, _PIF_OTHERCONFIG_ATTRS)),
311 # Special case: We write the current value
312 # PIF.currently-attached to the cache but since it will
313 # not be valid when we come to use the cache later
314 # (i.e. after a reboot) we always read it as False.
315 'currently_attached': (_bool_to_xml, lambda n: False),
318 _VLAN_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
319 'tagged_PIF': (_str_to_xml,_str_from_xml),
320 'untagged_PIF': (_str_to_xml,_str_from_xml),
323 _TUNNEL_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
324 'access_PIF': (_str_to_xml,_str_from_xml),
325 'transport_PIF': (_str_to_xml,_str_from_xml),
327 _BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
328 'master': (_str_to_xml,_str_from_xml),
329 'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
330 lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
333 _NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + _ETHTOOL_OTHERCONFIG_ATTRS
335 _NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
336 'bridge': (_str_to_xml,_str_from_xml),
337 'MTU': (_str_to_xml,_str_from_xml),
338 'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
339 lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
340 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
341 lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
345 # Database Cache object
351 assert(_db is not None)
354 def db_init_from_cache(cache):
357 _db = DatabaseCache(cache_file=cache)
359 def db_init_from_xenapi(session):
362 _db = DatabaseCache(session_ref=session)
364 class DatabaseCache(object):
365 def __read_xensource_inventory(self):
366 filename = root_prefix() + "/etc/xensource-inventory"
367 f = open(filename, "r")
368 lines = [x.strip("\n") for x in f.readlines()]
371 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
372 defs = [ (a, b.strip("'")) for (a,b) in defs ]
376 def __pif_on_host(self,pif):
377 return self.__pifs.has_key(pif)
379 def __get_pif_records_from_xapi(self, session, host):
381 for (p,rec) in session.xenapi.PIF.get_all_records().items():
382 if rec['host'] != host:
386 if f in [ "tunnel_access_PIF_of", "tunnel_transport_PIF_of" ] and f not in rec:
387 # XenServer 5.5 network records did not have
388 # these fields, so allow them to be missing.
391 self.__pifs[p][f] = rec[f]
392 self.__pifs[p]['other_config'] = {}
393 for f in _PIF_OTHERCONFIG_ATTRS:
394 if not rec['other_config'].has_key(f): continue
395 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
397 def __get_vlan_records_from_xapi(self, session):
399 for v in session.xenapi.VLAN.get_all():
400 rec = session.xenapi.VLAN.get_record(v)
401 if not self.__pif_on_host(rec['untagged_PIF']):
404 for f in _VLAN_ATTRS:
405 self.__vlans[v][f] = rec[f]
407 def __get_tunnel_records_from_xapi(self, session):
409 for t in session.xenapi.tunnel.get_all():
410 rec = session.xenapi.tunnel.get_record(t)
411 if not self.__pif_on_host(rec['transport_PIF']):
413 self.__tunnels[t] = {}
414 for f in _TUNNEL_ATTRS:
415 self.__tunnels[t][f] = rec[f]
417 def __get_bond_records_from_xapi(self, session):
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']):
424 for f in _BOND_ATTRS:
425 self.__bonds[b][f] = rec[f]
427 def __get_network_records_from_xapi(self, session):
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:
434 # drop PIFs on other hosts
435 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
436 elif f == "MTU" and f not in rec:
437 # XenServer 5.5 network records did not have an
438 # MTU field, so allow this to be missing.
441 self.__networks[n][f] = rec[f]
442 self.__networks[n]['other_config'] = {}
443 for f in _NETWORK_OTHERCONFIG_ATTRS:
444 if not rec['other_config'].has_key(f): continue
445 self.__networks[n]['other_config'][f] = rec['other_config'][f]
447 def __to_xml(self, xml, parent, key, ref, rec, attrs):
448 """Encode a database object as XML"""
449 e = xml.createElement(key)
450 parent.appendChild(e)
452 e.setAttribute('ref', ref)
454 for n,v in rec.items():
459 raise Error("Unknown attribute %s" % n)
460 def __from_xml(self, e, attrs):
461 """Decode a database object from XML"""
462 ref = e.attributes['ref'].value
464 for n in e.childNodes:
465 if n.nodeName in attrs:
466 _,h = attrs[n.nodeName]
467 rec[n.nodeName] = h(n)
470 def __init__(self, session_ref=None, cache_file=None):
471 if session_ref and cache_file:
472 raise Error("can't specify session reference and cache file")
473 if cache_file == None:
475 session = XenAPI.xapi_local()
478 log("No session ref given on command line, logging in.")
479 session.xenapi.login_with_password("root", "")
481 session._session = session_ref
485 inventory = self.__read_xensource_inventory()
486 assert(inventory.has_key('INSTALLATION_UUID'))
487 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
489 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
491 self.__get_pif_records_from_xapi(session, host)
494 self.__get_tunnel_records_from_xapi(session)
495 except XenAPI.Failure, e:
496 error,details = e.details
497 if error == "MESSAGE_METHOD_UNKNOWN" and details == "tunnel.get_all":
500 self.__get_vlan_records_from_xapi(session)
501 self.__get_bond_records_from_xapi(session)
502 self.__get_network_records_from_xapi(session)
505 session.xenapi.session.logout()
507 log("Loading xapi database cache from %s" % cache_file)
509 xml = parseXML(root_prefix() + cache_file)
517 assert(len(xml.childNodes) == 1)
518 toplevel = xml.childNodes[0]
520 assert(toplevel.nodeName == "xenserver-network-configuration")
522 for n in toplevel.childNodes:
523 if n.nodeName == "#text":
525 elif n.nodeName == _PIF_XML_TAG:
526 (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
527 self.__pifs[ref] = rec
528 elif n.nodeName == _BOND_XML_TAG:
529 (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
530 self.__bonds[ref] = rec
531 elif n.nodeName == _VLAN_XML_TAG:
532 (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
533 self.__vlans[ref] = rec
534 elif n.nodeName == _TUNNEL_XML_TAG:
535 (ref,rec) = self.__from_xml(n, _TUNNEL_ATTRS)
536 self.__vlans[ref] = rec
537 elif n.nodeName == _NETWORK_XML_TAG:
538 (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
539 self.__networks[ref] = rec
541 raise Error("Unknown XML element %s" % n.nodeName)
543 def save(self, cache_file):
545 xml = getDOMImplementation().createDocument(
546 None, "xenserver-network-configuration", None)
547 for (ref,rec) in self.__pifs.items():
548 self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
549 for (ref,rec) in self.__bonds.items():
550 self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
551 for (ref,rec) in self.__vlans.items():
552 self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
553 for (ref,rec) in self.__tunnels.items():
554 self.__to_xml(xml, xml.documentElement, _TUNNEL_XML_TAG, ref, rec, _TUNNEL_ATTRS)
555 for (ref,rec) in self.__networks.items():
556 self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
559 f = open(cache_file, 'w')
560 f.write(xml.toprettyxml())
563 def get_pif_by_uuid(self, uuid):
564 pifs = map(lambda (ref,rec): ref,
565 filter(lambda (ref,rec): uuid == rec['uuid'],
566 self.__pifs.items()))
568 raise Error("Unknown PIF \"%s\"" % uuid)
570 raise Error("Non-unique PIF \"%s\"" % uuid)
574 def get_pifs_by_device(self, device):
575 return map(lambda (ref,rec): ref,
576 filter(lambda (ref,rec): rec['device'] == device,
577 self.__pifs.items()))
579 def get_pif_by_bridge(self, bridge):
580 networks = map(lambda (ref,rec): ref,
581 filter(lambda (ref,rec): rec['bridge'] == bridge,
582 self.__networks.items()))
583 if len(networks) == 0:
584 raise Error("No matching network \"%s\"" % bridge)
587 for network in networks:
588 nwrec = self.get_network_record(network)
589 for pif in nwrec['PIFs']:
590 pifrec = self.get_pif_record(pif)
592 raise Error("Multiple PIFs on host for network %s" % (bridge))
595 raise Error("No PIF on host for network %s" % (bridge))
598 def get_pif_record(self, pif):
599 if self.__pifs.has_key(pif):
600 return self.__pifs[pif]
601 raise Error("Unknown PIF \"%s\"" % pif)
602 def get_all_pifs(self):
604 def pif_exists(self, pif):
605 return self.__pifs.has_key(pif)
607 def get_management_pif(self):
608 """ Returns the management pif on host
610 all = self.get_all_pifs()
612 pifrec = self.get_pif_record(pif)
613 if pifrec['management']: return pif
616 def get_network_record(self, network):
617 if self.__networks.has_key(network):
618 return self.__networks[network]
619 raise Error("Unknown network \"%s\"" % network)
621 def get_bond_record(self, bond):
622 if self.__bonds.has_key(bond):
623 return self.__bonds[bond]
627 def get_vlan_record(self, vlan):
628 if self.__vlans.has_key(vlan):
629 return self.__vlans[vlan]
637 def ethtool_settings(oc):
639 if oc.has_key('ethtool-speed'):
640 val = oc['ethtool-speed']
641 if val in ["10", "100", "1000"]:
642 settings += ['speed', val]
644 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
645 if oc.has_key('ethtool-duplex'):
646 val = oc['ethtool-duplex']
647 if val in ["10", "100", "1000"]:
648 settings += ['duplex', 'val']
650 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
651 if oc.has_key('ethtool-autoneg'):
652 val = oc['ethtool-autoneg']
653 if val in ["true", "on"]:
654 settings += ['autoneg', 'on']
655 elif val in ["false", "off"]:
656 settings += ['autoneg', 'off']
658 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
660 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
661 if oc.has_key("ethtool-" + opt):
662 val = oc["ethtool-" + opt]
663 if val in ["true", "on"]:
664 offload += [opt, 'on']
665 elif val in ["false", "off"]:
666 offload += [opt, 'off']
668 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
669 return settings,offload
671 # By default the MTU is taken from the Network.MTU setting for VIF,
672 # PIF and Bridge. However it is possible to override this by using
673 # {VIF,PIF,Network}.other-config:mtu.
675 # type parameter is a string describing the object that the oc parameter
676 # is from. e.g. "PIF", "Network"
677 def mtu_setting(nw, type, oc):
680 nwrec = db().get_network_record(nw)
681 if nwrec.has_key('MTU'):
686 if oc.has_key('mtu'):
687 log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
688 (nwrec['bridge'], type, mtu))
693 int(mtu) # Check that the value is an integer
695 except ValueError, x:
696 log("Invalid value for mtu = %s" % mtu)
701 # IP Network Devices -- network devices with IP configuration
703 def pif_ipdev_name(pif):
704 """Return the ipdev name associated with pif"""
705 pifrec = db().get_pif_record(pif)
706 nwrec = db().get_network_record(pifrec['network'])
709 # TODO: sanity check that nwrec['bridgeless'] != 'true'
710 return nwrec['bridge']
712 # TODO: sanity check that nwrec['bridgeless'] == 'true'
713 return pif_netdev_name(pif)
716 # Bare Network Devices -- network devices without IP configuration
719 def netdev_exists(netdev):
720 return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
722 def pif_netdev_name(pif):
723 """Get the netdev name for a PIF."""
725 pifrec = db().get_pif_record(pif)
728 return "%(device)s.%(VLAN)s" % pifrec
730 return pifrec['device']
736 def pif_is_bridged(pif):
737 pifrec = db().get_pif_record(pif)
738 nwrec = db().get_network_record(pifrec['network'])
741 # TODO: sanity check that nwrec['bridgeless'] != 'true'
744 # TODO: sanity check that nwrec['bridgeless'] == 'true'
747 def pif_bridge_name(pif):
748 """Return the bridge name of a pif.
750 PIF must be a bridged PIF."""
751 pifrec = db().get_pif_record(pif)
753 nwrec = db().get_network_record(pifrec['network'])
756 return nwrec['bridge']
758 raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
763 def pif_is_bond(pif):
764 pifrec = db().get_pif_record(pif)
766 return len(pifrec['bond_master_of']) > 0
768 def pif_get_bond_masters(pif):
769 """Returns a list of PIFs which are bond masters of this PIF"""
771 pifrec = db().get_pif_record(pif)
773 bso = pifrec['bond_slave_of']
775 # bond-slave-of is currently a single reference but in principle a
776 # PIF could be a member of several bonds which are not
777 # concurrently attached. Be robust to this possibility.
778 if not bso or bso == "OpaqueRef:NULL":
780 elif not type(bso) == list:
783 bondrecs = [db().get_bond_record(bond) for bond in bso]
784 bondrecs = [rec for rec in bondrecs if rec]
786 return [bond['master'] for bond in bondrecs]
788 def pif_get_bond_slaves(pif):
789 """Returns a list of PIFs which make up the given bonded pif."""
791 pifrec = db().get_pif_record(pif)
793 bmo = pifrec['bond_master_of']
795 raise Error("Bond-master-of contains too many elements")
800 bondrec = db().get_bond_record(bmo[0])
802 raise Error("No bond record for bond master PIF")
804 return bondrec['slaves']
810 def pif_is_vlan(pif):
811 return db().get_pif_record(pif)['VLAN'] != '-1'
813 def pif_get_vlan_slave(pif):
814 """Find the PIF which is the VLAN slave of pif.
816 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
818 pifrec = db().get_pif_record(pif)
820 vlan = pifrec['VLAN_master_of']
821 if not vlan or vlan == "OpaqueRef:NULL":
822 raise Error("PIF is not a VLAN master")
824 vlanrec = db().get_vlan_record(vlan)
826 raise Error("No VLAN record found for PIF")
828 return vlanrec['tagged_PIF']
830 def pif_get_vlan_masters(pif):
831 """Returns a list of PIFs which are VLANs on top of the given pif."""
833 pifrec = db().get_pif_record(pif)
834 vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
835 return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
840 def pif_is_tunnel(pif):
841 rec = db().get_pif_record(pif)
842 return rec.has_key('tunnel_access_PIF_of') and len(rec['tunnel_access_PIF_of']) > 0
845 # Datapath base class
848 class Datapath(object):
849 """Object encapsulating the actions necessary to (de)configure the
850 datapath for a given PIF. Does not include configuration of the
851 IP address on the ipdev.
854 def __init__(self, pif):
859 """Class method called when write action is called. Can be used
860 to update any backend specific configuration."""
863 def configure_ipdev(self, cfg):
864 """Write ifcfg TYPE field for an IPdev, plus any type specific
867 raise NotImplementedError
869 def preconfigure(self, parent):
870 """Prepare datapath configuration for PIF, but do not actually
873 Any configuration files should be attached to parent.
875 raise NotImplementedError
877 def bring_down_existing(self):
878 """Tear down any existing network device configuration which
879 needs to be undone in order to bring this PIF up.
881 raise NotImplementedError
884 """Apply the configuration prepared in the preconfigure stage.
886 Should assume any configuration files changed attached in
887 the preconfigure stage are applied and bring up the
888 necesary devices to provide the datapath for the
891 Should not bring up the IPdev.
893 raise NotImplementedError
896 """Called after the IPdev has been brought up.
898 Should do any final setup, including reinstating any
899 devices which were taken down in the bring_down_existing
902 raise NotImplementedError
904 def bring_down(self):
905 """Tear down and deconfigure the datapath. Should assume the
906 IPdev has already been brought down.
908 raise NotImplementedError
910 def DatapathFactory():
911 # XXX Need a datapath object for bridgeless PIFs
914 network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
915 network_backend = network_conf.readline().strip()
918 raise Error("failed to determine network backend:" + e)
920 if network_backend == "bridge":
921 from InterfaceReconfigureBridge import DatapathBridge
922 return DatapathBridge
923 elif network_backend in ["openvswitch", "vswitch"]:
924 from InterfaceReconfigureVswitch import DatapathVswitch
925 return DatapathVswitch
927 raise Error("unknown network backend %s" % network_backend)