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 _otherconfig_to_xml(xml, parent, val, attrs):
245 otherconfig = xml.createElement("other_config")
246 parent.appendChild(otherconfig)
247 for n,v in val.items():
249 raise Error("Unknown other-config attribute: %s" % n)
250 _str_to_xml(xml, otherconfig, n, v)
251 def _otherconfig_from_xml(n, attrs):
253 for n in n.childNodes:
254 if n.nodeName in attrs:
255 ret[n.nodeName] = _str_from_xml(n)
259 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
261 # Each object is defined by a dictionary mapping an attribute name in
262 # the xapi database to a tuple containing two items:
263 # - a function which takes this attribute and encodes it as XML.
264 # - a function which takes XML and decocdes it into a value.
266 # other-config attributes are specified as a simple array of strings
269 _VLAN_XML_TAG = "vlan"
270 _BOND_XML_TAG = "bond"
271 _NETWORK_XML_TAG = "network"
273 _ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
275 _PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
276 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
277 _ETHTOOL_OTHERCONFIG_ATTRS
279 _PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
280 'management': (_bool_to_xml,_bool_from_xml),
281 'network': (_str_to_xml,_str_from_xml),
282 'device': (_str_to_xml,_str_from_xml),
283 'bond_master_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
284 lambda n: _strlist_from_xml(n, 'bond_master_of', 'slave')),
285 'bond_slave_of': (_str_to_xml,_str_from_xml),
286 'VLAN': (_str_to_xml,_str_from_xml),
287 'VLAN_master_of': (_str_to_xml,_str_from_xml),
288 'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
289 lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
290 'ip_configuration_mode': (_str_to_xml,_str_from_xml),
291 'IP': (_str_to_xml,_str_from_xml),
292 'netmask': (_str_to_xml,_str_from_xml),
293 'gateway': (_str_to_xml,_str_from_xml),
294 'DNS': (_str_to_xml,_str_from_xml),
295 'MAC': (_str_to_xml,_str_from_xml),
296 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _PIF_OTHERCONFIG_ATTRS),
297 lambda n: _otherconfig_from_xml(n, _PIF_OTHERCONFIG_ATTRS)),
299 # Special case: We write the current value
300 # PIF.currently-attached to the cache but since it will
301 # not be valid when we come to use the cache later
302 # (i.e. after a reboot) we always read it as False.
303 'currently_attached': (_bool_to_xml, lambda n: False),
306 _VLAN_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
307 'tagged_PIF': (_str_to_xml,_str_from_xml),
308 'untagged_PIF': (_str_to_xml,_str_from_xml),
311 _BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
312 'master': (_str_to_xml,_str_from_xml),
313 'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
314 lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
317 _NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + _ETHTOOL_OTHERCONFIG_ATTRS
319 _NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
320 'bridge': (_str_to_xml,_str_from_xml),
321 'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
322 lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
323 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
324 lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
328 # Database Cache object
334 assert(_db is not None)
337 def db_init_from_cache(cache):
340 _db = DatabaseCache(cache_file=cache)
342 def db_init_from_xenapi(session):
345 _db = DatabaseCache(session_ref=session)
347 class DatabaseCache(object):
348 def __read_xensource_inventory(self):
349 filename = root_prefix() + "/etc/xensource-inventory"
350 f = open(filename, "r")
351 lines = [x.strip("\n") for x in f.readlines()]
354 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
355 defs = [ (a, b.strip("'")) for (a,b) in defs ]
359 def __pif_on_host(self,pif):
360 return self.__pifs.has_key(pif)
362 def __get_pif_records_from_xapi(self, session, host):
364 for (p,rec) in session.xenapi.PIF.get_all_records().items():
365 if rec['host'] != host:
369 self.__pifs[p][f] = rec[f]
370 self.__pifs[p]['other_config'] = {}
371 for f in _PIF_OTHERCONFIG_ATTRS:
372 if not rec['other_config'].has_key(f): continue
373 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
375 def __get_vlan_records_from_xapi(self, session):
377 for v in session.xenapi.VLAN.get_all():
378 rec = session.xenapi.VLAN.get_record(v)
379 if not self.__pif_on_host(rec['untagged_PIF']):
382 for f in _VLAN_ATTRS:
383 self.__vlans[v][f] = rec[f]
385 def __get_bond_records_from_xapi(self, session):
387 for b in session.xenapi.Bond.get_all():
388 rec = session.xenapi.Bond.get_record(b)
389 if not self.__pif_on_host(rec['master']):
392 for f in _BOND_ATTRS:
393 self.__bonds[b][f] = rec[f]
395 def __get_network_records_from_xapi(self, session):
397 for n in session.xenapi.network.get_all():
398 rec = session.xenapi.network.get_record(n)
399 self.__networks[n] = {}
400 for f in _NETWORK_ATTRS:
402 # drop PIFs on other hosts
403 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
405 self.__networks[n][f] = rec[f]
406 self.__networks[n]['other_config'] = {}
407 for f in _NETWORK_OTHERCONFIG_ATTRS:
408 if not rec['other_config'].has_key(f): continue
409 self.__networks[n]['other_config'][f] = rec['other_config'][f]
411 def __to_xml(self, xml, parent, key, ref, rec, attrs):
412 """Encode a database object as XML"""
413 e = xml.createElement(key)
414 parent.appendChild(e)
416 e.setAttribute('ref', ref)
418 for n,v in rec.items():
423 raise Error("Unknown attribute %s" % n)
424 def __from_xml(self, e, attrs):
425 """Decode a database object from XML"""
426 ref = e.attributes['ref'].value
428 for n in e.childNodes:
429 if n.nodeName in attrs:
430 _,h = attrs[n.nodeName]
431 rec[n.nodeName] = h(n)
434 def __init__(self, session_ref=None, cache_file=None):
435 if session_ref and cache_file:
436 raise Error("can't specify session reference and cache file")
437 if cache_file == None:
439 session = XenAPI.xapi_local()
442 log("No session ref given on command line, logging in.")
443 session.xenapi.login_with_password("root", "")
445 session._session = session_ref
449 inventory = self.__read_xensource_inventory()
450 assert(inventory.has_key('INSTALLATION_UUID'))
451 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
453 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
455 self.__get_pif_records_from_xapi(session, host)
457 self.__get_vlan_records_from_xapi(session)
458 self.__get_bond_records_from_xapi(session)
459 self.__get_network_records_from_xapi(session)
462 session.xenapi.session.logout()
464 log("Loading xapi database cache from %s" % cache_file)
466 xml = parseXML(root_prefix() + cache_file)
473 assert(len(xml.childNodes) == 1)
474 toplevel = xml.childNodes[0]
476 assert(toplevel.nodeName == "xenserver-network-configuration")
478 for n in toplevel.childNodes:
479 if n.nodeName == "#text":
481 elif n.nodeName == _PIF_XML_TAG:
482 (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
483 self.__pifs[ref] = rec
484 elif n.nodeName == _BOND_XML_TAG:
485 (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
486 self.__bonds[ref] = rec
487 elif n.nodeName == _VLAN_XML_TAG:
488 (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
489 self.__vlans[ref] = rec
490 elif n.nodeName == _NETWORK_XML_TAG:
491 (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
492 self.__networks[ref] = rec
494 raise Error("Unknown XML element %s" % n.nodeName)
496 def save(self, cache_file):
498 xml = getDOMImplementation().createDocument(
499 None, "xenserver-network-configuration", None)
500 for (ref,rec) in self.__pifs.items():
501 self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
502 for (ref,rec) in self.__bonds.items():
503 self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
504 for (ref,rec) in self.__vlans.items():
505 self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
506 for (ref,rec) in self.__networks.items():
507 self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
510 f = open(cache_file, 'w')
511 f.write(xml.toprettyxml())
514 def get_pif_by_uuid(self, uuid):
515 pifs = map(lambda (ref,rec): ref,
516 filter(lambda (ref,rec): uuid == rec['uuid'],
517 self.__pifs.items()))
519 raise Error("Unknown PIF \"%s\"" % uuid)
521 raise Error("Non-unique PIF \"%s\"" % uuid)
525 def get_pifs_by_device(self, device):
526 return map(lambda (ref,rec): ref,
527 filter(lambda (ref,rec): rec['device'] == device,
528 self.__pifs.items()))
530 def get_pif_by_bridge(self, bridge):
531 networks = map(lambda (ref,rec): ref,
532 filter(lambda (ref,rec): rec['bridge'] == bridge,
533 self.__networks.items()))
534 if len(networks) == 0:
535 raise Error("No matching network \"%s\"" % bridge)
538 for network in networks:
539 nwrec = self.get_network_record(network)
540 for pif in nwrec['PIFs']:
541 pifrec = self.get_pif_record(pif)
543 raise Error("Multiple PIFs on host for network %s" % (bridge))
546 raise Error("No PIF on host for network %s" % (bridge))
549 def get_pif_record(self, pif):
550 if self.__pifs.has_key(pif):
551 return self.__pifs[pif]
552 raise Error("Unknown PIF \"%s\"" % pif)
553 def get_all_pifs(self):
555 def pif_exists(self, pif):
556 return self.__pifs.has_key(pif)
558 def get_management_pif(self):
559 """ Returns the management pif on host
561 all = self.get_all_pifs()
563 pifrec = self.get_pif_record(pif)
564 if pifrec['management']: return pif
567 def get_network_record(self, network):
568 if self.__networks.has_key(network):
569 return self.__networks[network]
570 raise Error("Unknown network \"%s\"" % network)
572 def get_bond_record(self, bond):
573 if self.__bonds.has_key(bond):
574 return self.__bonds[bond]
578 def get_vlan_record(self, vlan):
579 if self.__vlans.has_key(vlan):
580 return self.__vlans[vlan]
588 def ethtool_settings(oc):
590 if oc.has_key('ethtool-speed'):
591 val = oc['ethtool-speed']
592 if val in ["10", "100", "1000"]:
593 settings += ['speed', val]
595 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
596 if oc.has_key('ethtool-duplex'):
597 val = oc['ethtool-duplex']
598 if val in ["10", "100", "1000"]:
599 settings += ['duplex', 'val']
601 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
602 if oc.has_key('ethtool-autoneg'):
603 val = oc['ethtool-autoneg']
604 if val in ["true", "on"]:
605 settings += ['autoneg', 'on']
606 elif val in ["false", "off"]:
607 settings += ['autoneg', 'off']
609 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
611 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
612 if oc.has_key("ethtool-" + opt):
613 val = oc["ethtool-" + opt]
614 if val in ["true", "on"]:
615 offload += [opt, 'on']
616 elif val in ["false", "off"]:
617 offload += [opt, 'off']
619 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
620 return settings,offload
623 if oc.has_key('mtu'):
625 int(oc['mtu']) # Check that the value is an integer
627 except ValueError, x:
628 log("Invalid value for mtu = %s" % oc['mtu'])
632 # IP Network Devices -- network devices with IP configuration
634 def pif_ipdev_name(pif):
635 """Return the ipdev name associated with pif"""
636 pifrec = db().get_pif_record(pif)
637 nwrec = db().get_network_record(pifrec['network'])
640 # TODO: sanity check that nwrec['bridgeless'] != 'true'
641 return nwrec['bridge']
643 # TODO: sanity check that nwrec['bridgeless'] == 'true'
644 return pif_netdev_name(pif)
647 # Bare Network Devices -- network devices without IP configuration
650 def netdev_exists(netdev):
651 return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
653 def pif_netdev_name(pif):
654 """Get the netdev name for a PIF."""
656 pifrec = db().get_pif_record(pif)
659 return "%(device)s.%(VLAN)s" % pifrec
661 return pifrec['device']
667 def pif_is_bridged(pif):
668 pifrec = db().get_pif_record(pif)
669 nwrec = db().get_network_record(pifrec['network'])
672 # TODO: sanity check that nwrec['bridgeless'] != 'true'
675 # TODO: sanity check that nwrec['bridgeless'] == 'true'
678 def pif_bridge_name(pif):
679 """Return the bridge name of a pif.
681 PIF must be a bridged PIF."""
682 pifrec = db().get_pif_record(pif)
684 nwrec = db().get_network_record(pifrec['network'])
687 return nwrec['bridge']
689 raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
694 def pif_is_bond(pif):
695 pifrec = db().get_pif_record(pif)
697 return len(pifrec['bond_master_of']) > 0
699 def pif_get_bond_masters(pif):
700 """Returns a list of PIFs which are bond masters of this PIF"""
702 pifrec = db().get_pif_record(pif)
704 bso = pifrec['bond_slave_of']
706 # bond-slave-of is currently a single reference but in principle a
707 # PIF could be a member of several bonds which are not
708 # concurrently attached. Be robust to this possibility.
709 if not bso or bso == "OpaqueRef:NULL":
711 elif not type(bso) == list:
714 bondrecs = [db().get_bond_record(bond) for bond in bso]
715 bondrecs = [rec for rec in bondrecs if rec]
717 return [bond['master'] for bond in bondrecs]
719 def pif_get_bond_slaves(pif):
720 """Returns a list of PIFs which make up the given bonded pif."""
722 pifrec = db().get_pif_record(pif)
724 bmo = pifrec['bond_master_of']
726 raise Error("Bond-master-of contains too many elements")
731 bondrec = db().get_bond_record(bmo[0])
733 raise Error("No bond record for bond master PIF")
735 return bondrec['slaves']
741 def pif_is_vlan(pif):
742 return db().get_pif_record(pif)['VLAN'] != '-1'
744 def pif_get_vlan_slave(pif):
745 """Find the PIF which is the VLAN slave of pif.
747 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
749 pifrec = db().get_pif_record(pif)
751 vlan = pifrec['VLAN_master_of']
752 if not vlan or vlan == "OpaqueRef:NULL":
753 raise Error("PIF is not a VLAN master")
755 vlanrec = db().get_vlan_record(vlan)
757 raise Error("No VLAN record found for PIF")
759 return vlanrec['tagged_PIF']
761 def pif_get_vlan_masters(pif):
762 """Returns a list of PIFs which are VLANs on top of the given pif."""
764 pifrec = db().get_pif_record(pif)
765 vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
766 return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
769 # Datapath base class
772 class Datapath(object):
773 """Object encapsulating the actions necessary to (de)configure the
774 datapath for a given PIF. Does not include configuration of the
775 IP address on the ipdev.
778 def __init__(self, pif):
781 def configure_ipdev(self, cfg):
782 """Write ifcfg TYPE field for an IPdev, plus any type specific
785 raise NotImplementedError
787 def preconfigure(self, parent):
788 """Prepare datapath configuration for PIF, but do not actually
791 Any configuration files should be attached to parent.
793 raise NotImplementedError
795 def bring_down_existing(self):
796 """Tear down any existing network device configuration which
797 needs to be undone in order to bring this PIF up.
799 raise NotImplementedError
802 """Apply the configuration prepared in the preconfigure stage.
804 Should assume any configuration files changed attached in
805 the preconfigure stage are applied and bring up the
806 necesary devices to provide the datapath for the
809 Should not bring up the IPdev.
811 raise NotImplementedError
814 """Called after the IPdev has been brought up.
816 Should do any final setup, including reinstating any
817 devices which were taken down in the bring_down_existing
820 raise NotImplementedError
822 def bring_down(self):
823 """Tear down and deconfigure the datapath. Should assume the
824 IPdev has already been brought down.
826 raise NotImplementedError
828 def DatapathFactory(pif):
829 # XXX Need a datapath object for bridgeless PIFs
832 network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
833 network_backend = network_conf.readline().strip()
836 raise Error("failed to determine network backend:" + e)
838 if network_backend == "bridge":
839 from InterfaceReconfigureBridge import DatapathBridge
840 return DatapathBridge(pif)
841 elif network_backend == "vswitch":
842 from InterfaceReconfigureVswitch import DatapathVswitch
843 return DatapathVswitch(pif)
845 raise Error("unknown network backend %s" % network_backend)