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.
16 from xml.dom.minidom import getDOMImplementation
17 from xml.dom.minidom import parse as parseXML
21 """Returns a string to prefix to all file name references, which
22 is useful for testing."""
23 return the_root_prefix
24 def set_root_prefix(prefix):
25 global the_root_prefix
26 the_root_prefix = prefix
39 class Error(Exception):
40 def __init__(self, msg):
41 Exception.__init__(self)
45 # Run external utilities
48 def run_command(command):
49 log("Running command: " + ' '.join(command))
50 rc = os.spawnl(os.P_WAIT, root_prefix() + command[0], *command)
52 log("Command failed %d: " % rc + ' '.join(command))
57 # Configuration File Handling.
60 class ConfigurationFile(object):
61 """Write a file, tracking old and new versions.
63 Supports writing a new version of a file and applying and
64 reverting those changes.
67 __STATE = {"OPEN":"OPEN",
68 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
69 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
71 def __init__(self, path):
72 dirname,basename = os.path.split(path)
74 self.__state = self.__STATE['OPEN']
77 self.__path = os.path.join(dirname, basename)
78 self.__oldpath = os.path.join(dirname, "." + basename + ".xapi-old")
79 self.__newpath = os.path.join(dirname, "." + basename + ".xapi-new")
81 self.__f = open(self.__newpath, "w")
83 def attach_child(self, child):
84 self.__children.append(child)
91 return open(self.path()).readlines()
95 def write(self, args):
96 if self.__state != self.__STATE['OPEN']:
97 raise Error("Attempt to write to file in state %s" % self.__state)
101 if self.__state != self.__STATE['OPEN']:
102 raise Error("Attempt to close file in state %s" % self.__state)
105 self.__state = self.__STATE['NOT-APPLIED']
108 if self.__state != self.__STATE['NOT-APPLIED']:
109 raise Error("Attempt to compare file in state %s" % self.__state)
114 if self.__state != self.__STATE['NOT-APPLIED']:
115 raise Error("Attempt to apply configuration from state %s" % self.__state)
117 for child in self.__children:
120 log("Applying changes to %s configuration" % self.__path)
122 # Remove previous backup.
123 if os.access(self.__oldpath, os.F_OK):
124 os.unlink(self.__oldpath)
126 # Save current configuration.
127 if os.access(self.__path, os.F_OK):
128 os.link(self.__path, self.__oldpath)
129 os.unlink(self.__path)
131 # Apply new configuration.
132 assert(os.path.exists(self.__newpath))
133 os.link(self.__newpath, self.__path)
135 # Remove temporary file.
136 os.unlink(self.__newpath)
138 self.__state = self.__STATE['APPLIED']
141 if self.__state != self.__STATE['APPLIED']:
142 raise Error("Attempt to revert configuration from state %s" % self.__state)
144 for child in self.__children:
147 log("Reverting changes to %s configuration" % self.__path)
149 # Remove existing new configuration
150 if os.access(self.__newpath, os.F_OK):
151 os.unlink(self.__newpath)
153 # Revert new configuration.
154 if os.access(self.__path, os.F_OK):
155 os.link(self.__path, self.__newpath)
156 os.unlink(self.__path)
158 # Revert to old configuration.
159 if os.access(self.__oldpath, os.F_OK):
160 os.link(self.__oldpath, self.__path)
161 os.unlink(self.__oldpath)
163 # Leave .*.xapi-new as an aid to debugging.
165 self.__state = self.__STATE['REVERTED']
168 if self.__state != self.__STATE['APPLIED']:
169 raise Error("Attempt to commit configuration from state %s" % self.__state)
171 for child in self.__children:
174 log("Committing changes to %s configuration" % self.__path)
176 if os.access(self.__oldpath, os.F_OK):
177 os.unlink(self.__oldpath)
178 if os.access(self.__newpath, os.F_OK):
179 os.unlink(self.__newpath)
181 self.__state = self.__STATE['COMMITTED']
184 # Helper functions for encoding/decoding database attributes to/from XML.
187 def _str_to_xml(xml, parent, tag, val):
188 e = xml.createElement(tag)
189 parent.appendChild(e)
190 v = xml.createTextNode(val)
192 def _str_from_xml(n):
193 def getText(nodelist):
195 for node in nodelist:
196 if node.nodeType == node.TEXT_NODE:
199 return getText(n.childNodes).strip()
201 def _bool_to_xml(xml, parent, tag, val):
203 _str_to_xml(xml, parent, tag, "True")
205 _str_to_xml(xml, parent, tag, "False")
206 def _bool_from_xml(n):
213 raise Error("Unknown boolean value %s" % s)
215 def _strlist_to_xml(xml, parent, ltag, itag, val):
216 e = xml.createElement(ltag)
217 parent.appendChild(e)
219 c = xml.createElement(itag)
221 cv = xml.createTextNode(v)
223 def _strlist_from_xml(n, ltag, itag):
225 for n in n.childNodes:
226 if n.nodeName == itag:
227 ret.append(_str_from_xml(n))
230 def _otherconfig_to_xml(xml, parent, val, attrs):
231 otherconfig = xml.createElement("other_config")
232 parent.appendChild(otherconfig)
233 for n,v in val.items():
235 raise Error("Unknown other-config attribute: %s" % n)
236 _str_to_xml(xml, otherconfig, n, v)
237 def _otherconfig_from_xml(n, attrs):
239 for n in n.childNodes:
240 if n.nodeName in attrs:
241 ret[n.nodeName] = _str_from_xml(n)
245 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
247 # Each object is defined by a dictionary mapping an attribute name in
248 # the xapi database to a tuple containing two items:
249 # - a function which takes this attribute and encodes it as XML.
250 # - a function which takes XML and decocdes it into a value.
252 # other-config attributes are specified as a simple array of strings
255 _VLAN_XML_TAG = "vlan"
256 _BOND_XML_TAG = "bond"
257 _NETWORK_XML_TAG = "network"
259 _ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
261 _PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
262 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
263 _ETHTOOL_OTHERCONFIG_ATTRS
265 _PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
266 'management': (_bool_to_xml,_bool_from_xml),
267 'network': (_str_to_xml,_str_from_xml),
268 'device': (_str_to_xml,_str_from_xml),
269 'bond_master_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
270 lambda n: _strlist_from_xml(n, 'bond_master_of', 'slave')),
271 'bond_slave_of': (_str_to_xml,_str_from_xml),
272 'VLAN': (_str_to_xml,_str_from_xml),
273 'VLAN_master_of': (_str_to_xml,_str_from_xml),
274 'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
275 lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
276 'ip_configuration_mode': (_str_to_xml,_str_from_xml),
277 'IP': (_str_to_xml,_str_from_xml),
278 'netmask': (_str_to_xml,_str_from_xml),
279 'gateway': (_str_to_xml,_str_from_xml),
280 'DNS': (_str_to_xml,_str_from_xml),
281 'MAC': (_str_to_xml,_str_from_xml),
282 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _PIF_OTHERCONFIG_ATTRS),
283 lambda n: _otherconfig_from_xml(n, _PIF_OTHERCONFIG_ATTRS)),
285 # Special case: We write the current value
286 # PIF.currently-attached to the cache but since it will
287 # not be valid when we come to use the cache later
288 # (i.e. after a reboot) we always read it as False.
289 'currently_attached': (_bool_to_xml, lambda n: False),
292 _VLAN_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
293 'tagged_PIF': (_str_to_xml,_str_from_xml),
294 'untagged_PIF': (_str_to_xml,_str_from_xml),
297 _BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
298 'master': (_str_to_xml,_str_from_xml),
299 'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
300 lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
303 _NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + _ETHTOOL_OTHERCONFIG_ATTRS
305 _NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
306 'bridge': (_str_to_xml,_str_from_xml),
307 'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
308 lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
309 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
310 lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
314 # Database Cache object
320 assert(_db is not None)
323 def db_init_from_cache(cache):
326 _db = DatabaseCache(cache_file=cache)
328 def db_init_from_xenapi(session):
331 _db = DatabaseCache(session_ref=session)
333 class DatabaseCache(object):
334 def __read_xensource_inventory(self):
335 filename = root_prefix() + "/etc/xensource-inventory"
336 f = open(filename, "r")
337 lines = [x.strip("\n") for x in f.readlines()]
340 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
341 defs = [ (a, b.strip("'")) for (a,b) in defs ]
345 def __pif_on_host(self,pif):
346 return self.__pifs.has_key(pif)
348 def __get_pif_records_from_xapi(self, session, host):
350 for (p,rec) in session.xenapi.PIF.get_all_records().items():
351 if rec['host'] != host:
355 self.__pifs[p][f] = rec[f]
356 self.__pifs[p]['other_config'] = {}
357 for f in _PIF_OTHERCONFIG_ATTRS:
358 if not rec['other_config'].has_key(f): continue
359 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
361 def __get_vlan_records_from_xapi(self, session):
363 for v in session.xenapi.VLAN.get_all():
364 rec = session.xenapi.VLAN.get_record(v)
365 if not self.__pif_on_host(rec['untagged_PIF']):
368 for f in _VLAN_ATTRS:
369 self.__vlans[v][f] = rec[f]
371 def __get_bond_records_from_xapi(self, session):
373 for b in session.xenapi.Bond.get_all():
374 rec = session.xenapi.Bond.get_record(b)
375 if not self.__pif_on_host(rec['master']):
378 for f in _BOND_ATTRS:
379 self.__bonds[b][f] = rec[f]
381 def __get_network_records_from_xapi(self, session):
383 for n in session.xenapi.network.get_all():
384 rec = session.xenapi.network.get_record(n)
385 self.__networks[n] = {}
386 for f in _NETWORK_ATTRS:
388 # drop PIFs on other hosts
389 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
391 self.__networks[n][f] = rec[f]
392 self.__networks[n]['other_config'] = {}
393 for f in _NETWORK_OTHERCONFIG_ATTRS:
394 if not rec['other_config'].has_key(f): continue
395 self.__networks[n]['other_config'][f] = rec['other_config'][f]
397 def __to_xml(self, xml, parent, key, ref, rec, attrs):
398 """Encode a database object as XML"""
399 e = xml.createElement(key)
400 parent.appendChild(e)
402 e.setAttribute('ref', ref)
404 for n,v in rec.items():
409 raise Error("Unknown attribute %s" % n)
410 def __from_xml(self, e, attrs):
411 """Decode a database object from XML"""
412 ref = e.attributes['ref'].value
414 for n in e.childNodes:
415 if n.nodeName in attrs:
416 _,h = attrs[n.nodeName]
417 rec[n.nodeName] = h(n)
420 def __init__(self, session_ref=None, cache_file=None):
421 if session_ref and cache_file:
422 raise Error("can't specify session reference and cache file")
423 if cache_file == None:
425 session = XenAPI.xapi_local()
428 log("No session ref given on command line, logging in.")
429 session.xenapi.login_with_password("root", "")
431 session._session = session_ref
435 inventory = self.__read_xensource_inventory()
436 assert(inventory.has_key('INSTALLATION_UUID'))
437 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
439 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
441 self.__get_pif_records_from_xapi(session, host)
443 self.__get_vlan_records_from_xapi(session)
444 self.__get_bond_records_from_xapi(session)
445 self.__get_network_records_from_xapi(session)
448 session.xenapi.session.logout()
450 log("Loading xapi database cache from %s" % cache_file)
452 xml = parseXML(root_prefix() + cache_file)
459 assert(len(xml.childNodes) == 1)
460 toplevel = xml.childNodes[0]
462 assert(toplevel.nodeName == "xenserver-network-configuration")
464 for n in toplevel.childNodes:
465 if n.nodeName == "#text":
467 elif n.nodeName == _PIF_XML_TAG:
468 (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
469 self.__pifs[ref] = rec
470 elif n.nodeName == _BOND_XML_TAG:
471 (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
472 self.__bonds[ref] = rec
473 elif n.nodeName == _VLAN_XML_TAG:
474 (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
475 self.__vlans[ref] = rec
476 elif n.nodeName == _NETWORK_XML_TAG:
477 (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
478 self.__networks[ref] = rec
480 raise Error("Unknown XML element %s" % n.nodeName)
482 def save(self, cache_file):
484 xml = getDOMImplementation().createDocument(
485 None, "xenserver-network-configuration", None)
486 for (ref,rec) in self.__pifs.items():
487 self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
488 for (ref,rec) in self.__bonds.items():
489 self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
490 for (ref,rec) in self.__vlans.items():
491 self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
492 for (ref,rec) in self.__networks.items():
493 self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
496 f = open(cache_file, 'w')
497 f.write(xml.toprettyxml())
500 def get_pif_by_uuid(self, uuid):
501 pifs = map(lambda (ref,rec): ref,
502 filter(lambda (ref,rec): uuid == rec['uuid'],
503 self.__pifs.items()))
505 raise Error("Unknown PIF \"%s\"" % uuid)
507 raise Error("Non-unique PIF \"%s\"" % uuid)
511 def get_pifs_by_device(self, device):
512 return map(lambda (ref,rec): ref,
513 filter(lambda (ref,rec): rec['device'] == device,
514 self.__pifs.items()))
516 def get_pif_by_bridge(self, bridge):
517 networks = map(lambda (ref,rec): ref,
518 filter(lambda (ref,rec): rec['bridge'] == bridge,
519 self.__networks.items()))
520 if len(networks) == 0:
521 raise Error("No matching network \"%s\"" % bridge)
524 for network in networks:
525 nwrec = self.get_network_record(network)
526 for pif in nwrec['PIFs']:
527 pifrec = self.get_pif_record(pif)
529 raise Error("Multiple PIFs on host for network %s" % (bridge))
532 raise Error("No PIF on host for network %s" % (bridge))
535 def get_pif_record(self, pif):
536 if self.__pifs.has_key(pif):
537 return self.__pifs[pif]
538 raise Error("Unknown PIF \"%s\"" % pif)
539 def get_all_pifs(self):
541 def pif_exists(self, pif):
542 return self.__pifs.has_key(pif)
544 def get_management_pif(self):
545 """ Returns the management pif on host
547 all = self.get_all_pifs()
549 pifrec = self.get_pif_record(pif)
550 if pifrec['management']: return pif
553 def get_network_record(self, network):
554 if self.__networks.has_key(network):
555 return self.__networks[network]
556 raise Error("Unknown network \"%s\"" % network)
558 def get_bond_record(self, bond):
559 if self.__bonds.has_key(bond):
560 return self.__bonds[bond]
564 def get_vlan_record(self, vlan):
565 if self.__vlans.has_key(vlan):
566 return self.__vlans[vlan]
574 def ethtool_settings(oc):
576 if oc.has_key('ethtool-speed'):
577 val = oc['ethtool-speed']
578 if val in ["10", "100", "1000"]:
579 settings += ['speed', val]
581 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
582 if oc.has_key('ethtool-duplex'):
583 val = oc['ethtool-duplex']
584 if val in ["10", "100", "1000"]:
585 settings += ['duplex', 'val']
587 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
588 if oc.has_key('ethtool-autoneg'):
589 val = oc['ethtool-autoneg']
590 if val in ["true", "on"]:
591 settings += ['autoneg', 'on']
592 elif val in ["false", "off"]:
593 settings += ['autoneg', 'off']
595 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
597 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
598 if oc.has_key("ethtool-" + opt):
599 val = oc["ethtool-" + opt]
600 if val in ["true", "on"]:
601 offload += [opt, 'on']
602 elif val in ["false", "off"]:
603 offload += [opt, 'off']
605 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
606 return settings,offload
609 if oc.has_key('mtu'):
611 int(oc['mtu']) # Check that the value is an integer
613 except ValueError, x:
614 log("Invalid value for mtu = %s" % oc['mtu'])
618 # IP Network Devices -- network devices with IP configuration
620 def pif_ipdev_name(pif):
621 """Return the ipdev name associated with pif"""
622 pifrec = db().get_pif_record(pif)
623 nwrec = db().get_network_record(pifrec['network'])
626 # TODO: sanity check that nwrec['bridgeless'] != 'true'
627 return nwrec['bridge']
629 # TODO: sanity check that nwrec['bridgeless'] == 'true'
630 return pif_netdev_name(pif)
633 # Bare Network Devices -- network devices without IP configuration
636 def netdev_exists(netdev):
637 return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
639 def pif_netdev_name(pif):
640 """Get the netdev name for a PIF."""
642 pifrec = db().get_pif_record(pif)
645 return "%(device)s.%(VLAN)s" % pifrec
647 return pifrec['device']
653 def pif_is_bridged(pif):
654 pifrec = db().get_pif_record(pif)
655 nwrec = db().get_network_record(pifrec['network'])
658 # TODO: sanity check that nwrec['bridgeless'] != 'true'
661 # TODO: sanity check that nwrec['bridgeless'] == 'true'
664 def pif_bridge_name(pif):
665 """Return the bridge name of a pif.
667 PIF must be a bridged PIF."""
668 pifrec = db().get_pif_record(pif)
670 nwrec = db().get_network_record(pifrec['network'])
673 return nwrec['bridge']
675 raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
680 def pif_is_bond(pif):
681 pifrec = db().get_pif_record(pif)
683 return len(pifrec['bond_master_of']) > 0
685 def pif_get_bond_masters(pif):
686 """Returns a list of PIFs which are bond masters of this PIF"""
688 pifrec = db().get_pif_record(pif)
690 bso = pifrec['bond_slave_of']
692 # bond-slave-of is currently a single reference but in principle a
693 # PIF could be a member of several bonds which are not
694 # concurrently attached. Be robust to this possibility.
695 if not bso or bso == "OpaqueRef:NULL":
697 elif not type(bso) == list:
700 bondrecs = [db().get_bond_record(bond) for bond in bso]
701 bondrecs = [rec for rec in bondrecs if rec]
703 return [bond['master'] for bond in bondrecs]
705 def pif_get_bond_slaves(pif):
706 """Returns a list of PIFs which make up the given bonded pif."""
708 pifrec = db().get_pif_record(pif)
710 bmo = pifrec['bond_master_of']
712 raise Error("Bond-master-of contains too many elements")
717 bondrec = db().get_bond_record(bmo[0])
719 raise Error("No bond record for bond master PIF")
721 return bondrec['slaves']
727 def pif_is_vlan(pif):
728 return db().get_pif_record(pif)['VLAN'] != '-1'
730 def pif_get_vlan_slave(pif):
731 """Find the PIF which is the VLAN slave of pif.
733 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
735 pifrec = db().get_pif_record(pif)
737 vlan = pifrec['VLAN_master_of']
738 if not vlan or vlan == "OpaqueRef:NULL":
739 raise Error("PIF is not a VLAN master")
741 vlanrec = db().get_vlan_record(vlan)
743 raise Error("No VLAN record found for PIF")
745 return vlanrec['tagged_PIF']
747 def pif_get_vlan_masters(pif):
748 """Returns a list of PIFs which are VLANs on top of the given pif."""
750 pifrec = db().get_pif_record(pif)
751 vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
752 return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
755 # Datapath base class
758 class Datapath(object):
759 """Object encapsulating the actions necessary to (de)configure the
760 datapath for a given PIF. Does not include configuration of the
761 IP address on the ipdev.
764 def __init__(self, pif):
767 def configure_ipdev(self, cfg):
768 """Write ifcfg TYPE field for an IPdev, plus any type specific
771 raise NotImplementedError
773 def preconfigure(self, parent):
774 """Prepare datapath configuration for PIF, but do not actually
777 Any configuration files should be attached to parent.
779 raise NotImplementedError
781 def bring_down_existing(self):
782 """Tear down any existing network device configuration which
783 needs to be undone in order to bring this PIF up.
785 raise NotImplementedError
788 """Apply the configuration prepared in the preconfigure stage.
790 Should assume any configuration files changed attached in
791 the preconfigure stage are applied and bring up the
792 necesary devices to provide the datapath for the
795 Should not bring up the IPdev.
797 raise NotImplementedError
800 """Called after the IPdev has been brought up.
802 Should do any final setup, including reinstating any
803 devices which were taken down in the bring_down_existing
806 raise NotImplementedError
808 def bring_down(self):
809 """Tear down and deconfigure the datapath. Should assume the
810 IPdev has already been brought down.
812 raise NotImplementedError
814 def DatapathFactory(pif):
815 # XXX Need a datapath object for bridgeless PIFs
818 network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
819 network_backend = network_conf.readline().strip()
822 raise Error("failed to determine network backend:" + e)
824 if network_backend == "bridge":
825 from InterfaceReconfigureBridge import DatapathBridge
826 return DatapathBridge(pif)
827 elif network_backend == "vswitch":
828 from InterfaceReconfigureVswitch import DatapathVswitch
829 return DatapathVswitch(pif)
831 raise Error("unknown network backend %s" % network_backend)