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 raise Error("Unknown other-config attribute: %s" % n)
250 _str_to_xml(xml, e, n, v)
252 def _map_from_xml(n, attrs):
254 for n in n.childNodes:
255 if n.nodeName in attrs:
256 ret[n.nodeName] = _str_from_xml(n)
259 def _otherconfig_to_xml(xml, parent, val, attrs):
260 return _map_to_xml(xml, parent, "other_config", val, attrs)
261 def _otherconfig_from_xml(n, attrs):
262 return _map_from_xml(n, attrs)
265 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
267 # Each object is defined by a dictionary mapping an attribute name in
268 # the xapi database to a tuple containing two items:
269 # - a function which takes this attribute and encodes it as XML.
270 # - a function which takes XML and decocdes it into a value.
272 # other-config attributes are specified as a simple array of strings
275 _VLAN_XML_TAG = "vlan"
276 _TUNNEL_XML_TAG = "tunnel"
277 _BOND_XML_TAG = "bond"
278 _NETWORK_XML_TAG = "network"
280 _ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
282 _PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
283 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
284 _ETHTOOL_OTHERCONFIG_ATTRS
286 _PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
287 'management': (_bool_to_xml,_bool_from_xml),
288 'network': (_str_to_xml,_str_from_xml),
289 'device': (_str_to_xml,_str_from_xml),
290 'bond_master_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
291 lambda n: _strlist_from_xml(n, 'bond_master_of', 'slave')),
292 'bond_slave_of': (_str_to_xml,_str_from_xml),
293 'VLAN': (_str_to_xml,_str_from_xml),
294 'VLAN_master_of': (_str_to_xml,_str_from_xml),
295 'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
296 lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
297 'tunnel_access_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_access_PIF_of', 'pif', v),
298 lambda n: _strlist_from_xml(n, 'tunnel_access_PIF_of', 'pif')),
299 'tunnel_transport_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_transport_PIF_of', 'pif', v),
300 lambda n: _strlist_from_xml(n, 'tunnel_transport_PIF_of', 'pif')),
301 'ip_configuration_mode': (_str_to_xml,_str_from_xml),
302 'IP': (_str_to_xml,_str_from_xml),
303 'netmask': (_str_to_xml,_str_from_xml),
304 'gateway': (_str_to_xml,_str_from_xml),
305 'DNS': (_str_to_xml,_str_from_xml),
306 'MAC': (_str_to_xml,_str_from_xml),
307 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _PIF_OTHERCONFIG_ATTRS),
308 lambda n: _otherconfig_from_xml(n, _PIF_OTHERCONFIG_ATTRS)),
310 # Special case: We write the current value
311 # PIF.currently-attached to the cache but since it will
312 # not be valid when we come to use the cache later
313 # (i.e. after a reboot) we always read it as False.
314 'currently_attached': (_bool_to_xml, lambda n: False),
317 _VLAN_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
318 'tagged_PIF': (_str_to_xml,_str_from_xml),
319 'untagged_PIF': (_str_to_xml,_str_from_xml),
322 _TUNNEL_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
323 'access_PIF': (_str_to_xml,_str_from_xml),
324 'transport_PIF': (_str_to_xml,_str_from_xml),
326 _BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
327 'master': (_str_to_xml,_str_from_xml),
328 'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
329 lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
332 _NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + _ETHTOOL_OTHERCONFIG_ATTRS
334 _NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
335 'bridge': (_str_to_xml,_str_from_xml),
336 'MTU': (_str_to_xml,_str_from_xml),
337 'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
338 lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
339 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
340 lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
344 # Database Cache object
350 assert(_db is not None)
353 def db_init_from_cache(cache):
356 _db = DatabaseCache(cache_file=cache)
358 def db_init_from_xenapi(session):
361 _db = DatabaseCache(session_ref=session)
363 class DatabaseCache(object):
364 def __read_xensource_inventory(self):
365 filename = root_prefix() + "/etc/xensource-inventory"
366 f = open(filename, "r")
367 lines = [x.strip("\n") for x in f.readlines()]
370 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
371 defs = [ (a, b.strip("'")) for (a,b) in defs ]
375 def __pif_on_host(self,pif):
376 return self.__pifs.has_key(pif)
378 def __get_pif_records_from_xapi(self, session, host):
380 for (p,rec) in session.xenapi.PIF.get_all_records().items():
381 if rec['host'] != host:
385 if f in [ "tunnel_access_PIF_of", "tunnel_transport_PIF_of" ] and f not in rec:
386 # XenServer 5.5 network records did not have
387 # these fields, so allow them to be missing.
390 self.__pifs[p][f] = rec[f]
391 self.__pifs[p]['other_config'] = {}
392 for f in _PIF_OTHERCONFIG_ATTRS:
393 if not rec['other_config'].has_key(f): continue
394 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
396 def __get_vlan_records_from_xapi(self, session):
398 for v in session.xenapi.VLAN.get_all():
399 rec = session.xenapi.VLAN.get_record(v)
400 if not self.__pif_on_host(rec['untagged_PIF']):
403 for f in _VLAN_ATTRS:
404 self.__vlans[v][f] = rec[f]
406 def __get_tunnel_records_from_xapi(self, session):
408 for t in session.xenapi.tunnel.get_all():
409 rec = session.xenapi.tunnel.get_record(t)
410 if not self.__pif_on_host(rec['transport_PIF']):
412 self.__tunnels[t] = {}
413 for f in _TUNNEL_ATTRS:
414 self.__tunnels[t][f] = rec[f]
416 def __get_bond_records_from_xapi(self, session):
418 for b in session.xenapi.Bond.get_all():
419 rec = session.xenapi.Bond.get_record(b)
420 if not self.__pif_on_host(rec['master']):
423 for f in _BOND_ATTRS:
424 self.__bonds[b][f] = rec[f]
426 def __get_network_records_from_xapi(self, session):
428 for n in session.xenapi.network.get_all():
429 rec = session.xenapi.network.get_record(n)
430 self.__networks[n] = {}
431 for f in _NETWORK_ATTRS:
433 # drop PIFs on other hosts
434 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
435 elif f == "MTU" and f not in rec:
436 # XenServer 5.5 network records did not have an
437 # MTU field, so allow this to be missing.
440 self.__networks[n][f] = rec[f]
441 self.__networks[n]['other_config'] = {}
442 for f in _NETWORK_OTHERCONFIG_ATTRS:
443 if not rec['other_config'].has_key(f): continue
444 self.__networks[n]['other_config'][f] = rec['other_config'][f]
446 def __to_xml(self, xml, parent, key, ref, rec, attrs):
447 """Encode a database object as XML"""
448 e = xml.createElement(key)
449 parent.appendChild(e)
451 e.setAttribute('ref', ref)
453 for n,v in rec.items():
458 raise Error("Unknown attribute %s" % n)
459 def __from_xml(self, e, attrs):
460 """Decode a database object from XML"""
461 ref = e.attributes['ref'].value
463 for n in e.childNodes:
464 if n.nodeName in attrs:
465 _,h = attrs[n.nodeName]
466 rec[n.nodeName] = h(n)
469 def __init__(self, session_ref=None, cache_file=None):
470 if session_ref and cache_file:
471 raise Error("can't specify session reference and cache file")
472 if cache_file == None:
474 session = XenAPI.xapi_local()
477 log("No session ref given on command line, logging in.")
478 session.xenapi.login_with_password("root", "")
480 session._session = session_ref
484 inventory = self.__read_xensource_inventory()
485 assert(inventory.has_key('INSTALLATION_UUID'))
486 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
488 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
490 self.__get_pif_records_from_xapi(session, host)
493 self.__get_tunnel_records_from_xapi(session)
494 except XenAPI.Failure, e:
495 error,details = e.details
496 if error == "MESSAGE_METHOD_UNKNOWN" and details == "tunnel.get_all":
499 self.__get_vlan_records_from_xapi(session)
500 self.__get_bond_records_from_xapi(session)
501 self.__get_network_records_from_xapi(session)
504 session.xenapi.session.logout()
506 log("Loading xapi database cache from %s" % cache_file)
508 xml = parseXML(root_prefix() + cache_file)
516 assert(len(xml.childNodes) == 1)
517 toplevel = xml.childNodes[0]
519 assert(toplevel.nodeName == "xenserver-network-configuration")
521 for n in toplevel.childNodes:
522 if n.nodeName == "#text":
524 elif n.nodeName == _PIF_XML_TAG:
525 (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
526 self.__pifs[ref] = rec
527 elif n.nodeName == _BOND_XML_TAG:
528 (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
529 self.__bonds[ref] = rec
530 elif n.nodeName == _VLAN_XML_TAG:
531 (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
532 self.__vlans[ref] = rec
533 elif n.nodeName == _TUNNEL_XML_TAG:
534 (ref,rec) = self.__from_xml(n, _TUNNEL_ATTRS)
535 self.__vlans[ref] = rec
536 elif n.nodeName == _NETWORK_XML_TAG:
537 (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
538 self.__networks[ref] = rec
540 raise Error("Unknown XML element %s" % n.nodeName)
542 def save(self, cache_file):
544 xml = getDOMImplementation().createDocument(
545 None, "xenserver-network-configuration", None)
546 for (ref,rec) in self.__pifs.items():
547 self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
548 for (ref,rec) in self.__bonds.items():
549 self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
550 for (ref,rec) in self.__vlans.items():
551 self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
552 for (ref,rec) in self.__tunnels.items():
553 self.__to_xml(xml, xml.documentElement, _TUNNEL_XML_TAG, ref, rec, _TUNNEL_ATTRS)
554 for (ref,rec) in self.__networks.items():
555 self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
558 f = open(cache_file, 'w')
559 f.write(xml.toprettyxml())
562 def get_pif_by_uuid(self, uuid):
563 pifs = map(lambda (ref,rec): ref,
564 filter(lambda (ref,rec): uuid == rec['uuid'],
565 self.__pifs.items()))
567 raise Error("Unknown PIF \"%s\"" % uuid)
569 raise Error("Non-unique PIF \"%s\"" % uuid)
573 def get_pifs_by_device(self, device):
574 return map(lambda (ref,rec): ref,
575 filter(lambda (ref,rec): rec['device'] == device,
576 self.__pifs.items()))
578 def get_pif_by_bridge(self, bridge):
579 networks = map(lambda (ref,rec): ref,
580 filter(lambda (ref,rec): rec['bridge'] == bridge,
581 self.__networks.items()))
582 if len(networks) == 0:
583 raise Error("No matching network \"%s\"" % bridge)
586 for network in networks:
587 nwrec = self.get_network_record(network)
588 for pif in nwrec['PIFs']:
589 pifrec = self.get_pif_record(pif)
591 raise Error("Multiple PIFs on host for network %s" % (bridge))
594 raise Error("No PIF on host for network %s" % (bridge))
597 def get_pif_record(self, pif):
598 if self.__pifs.has_key(pif):
599 return self.__pifs[pif]
600 raise Error("Unknown PIF \"%s\"" % pif)
601 def get_all_pifs(self):
603 def pif_exists(self, pif):
604 return self.__pifs.has_key(pif)
606 def get_management_pif(self):
607 """ Returns the management pif on host
609 all = self.get_all_pifs()
611 pifrec = self.get_pif_record(pif)
612 if pifrec['management']: return pif
615 def get_network_record(self, network):
616 if self.__networks.has_key(network):
617 return self.__networks[network]
618 raise Error("Unknown network \"%s\"" % network)
620 def get_bond_record(self, bond):
621 if self.__bonds.has_key(bond):
622 return self.__bonds[bond]
626 def get_vlan_record(self, vlan):
627 if self.__vlans.has_key(vlan):
628 return self.__vlans[vlan]
636 def ethtool_settings(oc):
638 if oc.has_key('ethtool-speed'):
639 val = oc['ethtool-speed']
640 if val in ["10", "100", "1000"]:
641 settings += ['speed', val]
643 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
644 if oc.has_key('ethtool-duplex'):
645 val = oc['ethtool-duplex']
646 if val in ["10", "100", "1000"]:
647 settings += ['duplex', 'val']
649 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
650 if oc.has_key('ethtool-autoneg'):
651 val = oc['ethtool-autoneg']
652 if val in ["true", "on"]:
653 settings += ['autoneg', 'on']
654 elif val in ["false", "off"]:
655 settings += ['autoneg', 'off']
657 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
659 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
660 if oc.has_key("ethtool-" + opt):
661 val = oc["ethtool-" + opt]
662 if val in ["true", "on"]:
663 offload += [opt, 'on']
664 elif val in ["false", "off"]:
665 offload += [opt, 'off']
667 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
668 return settings,offload
670 # By default the MTU is taken from the Network.MTU setting for VIF,
671 # PIF and Bridge. However it is possible to override this by using
672 # {VIF,PIF,Network}.other-config:mtu.
674 # type parameter is a string describing the object that the oc parameter
675 # is from. e.g. "PIF", "Network"
676 def mtu_setting(nw, type, oc):
679 nwrec = db().get_network_record(nw)
680 if nwrec.has_key('MTU'):
685 if oc.has_key('mtu'):
686 log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
687 (nwrec['bridge'], type, mtu))
692 int(mtu) # Check that the value is an integer
694 except ValueError, x:
695 log("Invalid value for mtu = %s" % mtu)
700 # IP Network Devices -- network devices with IP configuration
702 def pif_ipdev_name(pif):
703 """Return the ipdev name associated with pif"""
704 pifrec = db().get_pif_record(pif)
705 nwrec = db().get_network_record(pifrec['network'])
708 # TODO: sanity check that nwrec['bridgeless'] != 'true'
709 return nwrec['bridge']
711 # TODO: sanity check that nwrec['bridgeless'] == 'true'
712 return pif_netdev_name(pif)
715 # Bare Network Devices -- network devices without IP configuration
718 def netdev_exists(netdev):
719 return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
721 def pif_netdev_name(pif):
722 """Get the netdev name for a PIF."""
724 pifrec = db().get_pif_record(pif)
727 return "%(device)s.%(VLAN)s" % pifrec
729 return pifrec['device']
735 def pif_is_bridged(pif):
736 pifrec = db().get_pif_record(pif)
737 nwrec = db().get_network_record(pifrec['network'])
740 # TODO: sanity check that nwrec['bridgeless'] != 'true'
743 # TODO: sanity check that nwrec['bridgeless'] == 'true'
746 def pif_bridge_name(pif):
747 """Return the bridge name of a pif.
749 PIF must be a bridged PIF."""
750 pifrec = db().get_pif_record(pif)
752 nwrec = db().get_network_record(pifrec['network'])
755 return nwrec['bridge']
757 raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
762 def pif_is_bond(pif):
763 pifrec = db().get_pif_record(pif)
765 return len(pifrec['bond_master_of']) > 0
767 def pif_get_bond_masters(pif):
768 """Returns a list of PIFs which are bond masters of this PIF"""
770 pifrec = db().get_pif_record(pif)
772 bso = pifrec['bond_slave_of']
774 # bond-slave-of is currently a single reference but in principle a
775 # PIF could be a member of several bonds which are not
776 # concurrently attached. Be robust to this possibility.
777 if not bso or bso == "OpaqueRef:NULL":
779 elif not type(bso) == list:
782 bondrecs = [db().get_bond_record(bond) for bond in bso]
783 bondrecs = [rec for rec in bondrecs if rec]
785 return [bond['master'] for bond in bondrecs]
787 def pif_get_bond_slaves(pif):
788 """Returns a list of PIFs which make up the given bonded pif."""
790 pifrec = db().get_pif_record(pif)
792 bmo = pifrec['bond_master_of']
794 raise Error("Bond-master-of contains too many elements")
799 bondrec = db().get_bond_record(bmo[0])
801 raise Error("No bond record for bond master PIF")
803 return bondrec['slaves']
809 def pif_is_vlan(pif):
810 return db().get_pif_record(pif)['VLAN'] != '-1'
812 def pif_get_vlan_slave(pif):
813 """Find the PIF which is the VLAN slave of pif.
815 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
817 pifrec = db().get_pif_record(pif)
819 vlan = pifrec['VLAN_master_of']
820 if not vlan or vlan == "OpaqueRef:NULL":
821 raise Error("PIF is not a VLAN master")
823 vlanrec = db().get_vlan_record(vlan)
825 raise Error("No VLAN record found for PIF")
827 return vlanrec['tagged_PIF']
829 def pif_get_vlan_masters(pif):
830 """Returns a list of PIFs which are VLANs on top of the given pif."""
832 pifrec = db().get_pif_record(pif)
833 vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
834 return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
839 def pif_is_tunnel(pif):
840 rec = db().get_pif_record(pif)
841 return rec.has_key('tunnel_access_PIF_of') and len(rec['tunnel_access_PIF_of']) > 0
844 # Datapath base class
847 class Datapath(object):
848 """Object encapsulating the actions necessary to (de)configure the
849 datapath for a given PIF. Does not include configuration of the
850 IP address on the ipdev.
853 def __init__(self, pif):
858 """Class method called when write action is called. Can be used
859 to update any backend specific configuration."""
862 def configure_ipdev(self, cfg):
863 """Write ifcfg TYPE field for an IPdev, plus any type specific
866 raise NotImplementedError
868 def preconfigure(self, parent):
869 """Prepare datapath configuration for PIF, but do not actually
872 Any configuration files should be attached to parent.
874 raise NotImplementedError
876 def bring_down_existing(self):
877 """Tear down any existing network device configuration which
878 needs to be undone in order to bring this PIF up.
880 raise NotImplementedError
883 """Apply the configuration prepared in the preconfigure stage.
885 Should assume any configuration files changed attached in
886 the preconfigure stage are applied and bring up the
887 necesary devices to provide the datapath for the
890 Should not bring up the IPdev.
892 raise NotImplementedError
895 """Called after the IPdev has been brought up.
897 Should do any final setup, including reinstating any
898 devices which were taken down in the bring_down_existing
901 raise NotImplementedError
903 def bring_down(self):
904 """Tear down and deconfigure the datapath. Should assume the
905 IPdev has already been brought down.
907 raise NotImplementedError
909 def DatapathFactory():
910 # XXX Need a datapath object for bridgeless PIFs
913 network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
914 network_backend = network_conf.readline().strip()
917 raise Error("failed to determine network backend:" + e)
919 if network_backend == "bridge":
920 from InterfaceReconfigureBridge import DatapathBridge
921 return DatapathBridge
922 elif network_backend in ["openvswitch", "vswitch"]:
923 from InterfaceReconfigureVswitch import DatapathVswitch
924 return DatapathVswitch
926 raise Error("unknown network backend %s" % network_backend)