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':
47 sys.stderr.write(s + '\n')
54 class Error(Exception):
55 def __init__(self, msg):
56 Exception.__init__(self)
60 # Run external utilities
63 def run_command(command):
64 log("Running command: " + ' '.join(command))
65 rc = os.spawnl(os.P_WAIT, root_prefix() + command[0], *command)
67 log("Command failed %d: " % rc + ' '.join(command))
72 # Configuration File Handling.
75 class ConfigurationFile(object):
76 """Write a file, tracking old and new versions.
78 Supports writing a new version of a file and applying and
79 reverting those changes.
82 __STATE = {"OPEN":"OPEN",
83 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
84 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
86 def __init__(self, path):
87 dirname,basename = os.path.split(path)
89 self.__state = self.__STATE['OPEN']
92 self.__path = os.path.join(dirname, basename)
93 self.__oldpath = os.path.join(dirname, "." + basename + ".xapi-old")
94 self.__newpath = os.path.join(dirname, "." + basename + ".xapi-new")
96 self.__f = open(self.__newpath, "w")
98 def attach_child(self, child):
99 self.__children.append(child)
106 return open(self.path()).readlines()
110 def write(self, args):
111 if self.__state != self.__STATE['OPEN']:
112 raise Error("Attempt to write to file in state %s" % self.__state)
116 if self.__state != self.__STATE['OPEN']:
117 raise Error("Attempt to close file in state %s" % self.__state)
120 self.__state = self.__STATE['NOT-APPLIED']
123 if self.__state != self.__STATE['NOT-APPLIED']:
124 raise Error("Attempt to compare file in state %s" % self.__state)
129 if self.__state != self.__STATE['NOT-APPLIED']:
130 raise Error("Attempt to apply configuration from state %s" % self.__state)
132 for child in self.__children:
135 log("Applying changes to %s configuration" % self.__path)
137 # Remove previous backup.
138 if os.access(self.__oldpath, os.F_OK):
139 os.unlink(self.__oldpath)
141 # Save current configuration.
142 if os.access(self.__path, os.F_OK):
143 os.link(self.__path, self.__oldpath)
144 os.unlink(self.__path)
146 # Apply new configuration.
147 assert(os.path.exists(self.__newpath))
148 os.link(self.__newpath, self.__path)
150 # Remove temporary file.
151 os.unlink(self.__newpath)
153 self.__state = self.__STATE['APPLIED']
156 if self.__state != self.__STATE['APPLIED']:
157 raise Error("Attempt to revert configuration from state %s" % self.__state)
159 for child in self.__children:
162 log("Reverting changes to %s configuration" % self.__path)
164 # Remove existing new configuration
165 if os.access(self.__newpath, os.F_OK):
166 os.unlink(self.__newpath)
168 # Revert new configuration.
169 if os.access(self.__path, os.F_OK):
170 os.link(self.__path, self.__newpath)
171 os.unlink(self.__path)
173 # Revert to old configuration.
174 if os.access(self.__oldpath, os.F_OK):
175 os.link(self.__oldpath, self.__path)
176 os.unlink(self.__oldpath)
178 # Leave .*.xapi-new as an aid to debugging.
180 self.__state = self.__STATE['REVERTED']
183 if self.__state != self.__STATE['APPLIED']:
184 raise Error("Attempt to commit configuration from state %s" % self.__state)
186 for child in self.__children:
189 log("Committing changes to %s configuration" % self.__path)
191 if os.access(self.__oldpath, os.F_OK):
192 os.unlink(self.__oldpath)
193 if os.access(self.__newpath, os.F_OK):
194 os.unlink(self.__newpath)
196 self.__state = self.__STATE['COMMITTED']
199 # Helper functions for encoding/decoding database attributes to/from XML.
202 def _str_to_xml(xml, parent, tag, val):
203 e = xml.createElement(tag)
204 parent.appendChild(e)
205 v = xml.createTextNode(val)
207 def _str_from_xml(n):
208 def getText(nodelist):
210 for node in nodelist:
211 if node.nodeType == node.TEXT_NODE:
214 return getText(n.childNodes).strip()
216 def _bool_to_xml(xml, parent, tag, val):
218 _str_to_xml(xml, parent, tag, "True")
220 _str_to_xml(xml, parent, tag, "False")
221 def _bool_from_xml(n):
228 raise Error("Unknown boolean value %s" % s)
230 def _strlist_to_xml(xml, parent, ltag, itag, val):
231 e = xml.createElement(ltag)
232 parent.appendChild(e)
234 c = xml.createElement(itag)
236 cv = xml.createTextNode(v)
238 def _strlist_from_xml(n, ltag, itag):
240 for n in n.childNodes:
241 if n.nodeName == itag:
242 ret.append(_str_from_xml(n))
245 def _map_to_xml(xml, parent, tag, val, attrs):
246 e = xml.createElement(tag)
247 parent.appendChild(e)
248 for n,v in val.items():
250 _str_to_xml(xml, e, n, v)
252 log("Unknown other-config attribute: %s" % n)
254 def _map_from_xml(n, attrs):
256 for n in n.childNodes:
257 if n.nodeName in attrs:
258 ret[n.nodeName] = _str_from_xml(n)
261 def _otherconfig_to_xml(xml, parent, val, attrs):
262 return _map_to_xml(xml, parent, "other_config", val, attrs)
263 def _otherconfig_from_xml(n, attrs):
264 return _map_from_xml(n, attrs)
267 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
269 # Each object is defined by a dictionary mapping an attribute name in
270 # the xapi database to a tuple containing two items:
271 # - a function which takes this attribute and encodes it as XML.
272 # - a function which takes XML and decocdes it into a value.
274 # other-config attributes are specified as a simple array of strings
277 _VLAN_XML_TAG = "vlan"
278 _TUNNEL_XML_TAG = "tunnel"
279 _BOND_XML_TAG = "bond"
280 _NETWORK_XML_TAG = "network"
281 _POOL_XML_TAG = "pool"
283 _ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in ['autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso', 'gro', 'lro'] ]
285 _PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
286 [ 'bond-%s' % x for x in ['mode', 'miimon', 'downdelay', 'updelay', 'use_carrier', 'hashing-algorithm'] ] + \
287 [ 'vlan-bug-workaround' ] + \
288 _ETHTOOL_OTHERCONFIG_ATTRS
290 _PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
291 'management': (_bool_to_xml,_bool_from_xml),
292 'network': (_str_to_xml,_str_from_xml),
293 'device': (_str_to_xml,_str_from_xml),
294 'bond_master_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
295 lambda n: _strlist_from_xml(n, 'bond_master_of', 'slave')),
296 'bond_slave_of': (_str_to_xml,_str_from_xml),
297 'VLAN': (_str_to_xml,_str_from_xml),
298 'VLAN_master_of': (_str_to_xml,_str_from_xml),
299 'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
300 lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
301 'tunnel_access_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_access_PIF_of', 'pif', v),
302 lambda n: _strlist_from_xml(n, 'tunnel_access_PIF_of', 'pif')),
303 'tunnel_transport_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_transport_PIF_of', 'pif', v),
304 lambda n: _strlist_from_xml(n, 'tunnel_transport_PIF_of', 'pif')),
305 'ip_configuration_mode': (_str_to_xml,_str_from_xml),
306 'IP': (_str_to_xml,_str_from_xml),
307 'netmask': (_str_to_xml,_str_from_xml),
308 'gateway': (_str_to_xml,_str_from_xml),
309 'DNS': (_str_to_xml,_str_from_xml),
310 'MAC': (_str_to_xml,_str_from_xml),
311 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _PIF_OTHERCONFIG_ATTRS),
312 lambda n: _otherconfig_from_xml(n, _PIF_OTHERCONFIG_ATTRS)),
314 # Special case: We write the current value
315 # PIF.currently-attached to the cache but since it will
316 # not be valid when we come to use the cache later
317 # (i.e. after a reboot) we always read it as False.
318 'currently_attached': (_bool_to_xml, lambda n: False),
321 _VLAN_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
322 'tagged_PIF': (_str_to_xml,_str_from_xml),
323 'untagged_PIF': (_str_to_xml,_str_from_xml),
326 _TUNNEL_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
327 'access_PIF': (_str_to_xml,_str_from_xml),
328 'transport_PIF': (_str_to_xml,_str_from_xml),
330 _BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
331 'master': (_str_to_xml,_str_from_xml),
332 'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
333 lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
336 _NETWORK_OTHERCONFIG_ATTRS = [ 'mtu',
338 'vswitch-controller-fail-mode',
339 'vswitch-disable-in-band' ] \
340 + _ETHTOOL_OTHERCONFIG_ATTRS
342 _NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
343 'bridge': (_str_to_xml,_str_from_xml),
344 'MTU': (_str_to_xml,_str_from_xml),
345 'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
346 lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
347 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
348 lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
351 _POOL_OTHERCONFIG_ATTRS = ['vswitch-controller-fail-mode']
353 _POOL_ATTRS = { 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _POOL_OTHERCONFIG_ATTRS),
354 lambda n: _otherconfig_from_xml(n, _POOL_OTHERCONFIG_ATTRS)),
358 # Database Cache object
364 assert(_db is not None)
367 def db_init_from_cache(cache):
370 _db = DatabaseCache(cache_file=cache)
372 def db_init_from_xenapi(session):
375 _db = DatabaseCache(session_ref=session)
377 class DatabaseCache(object):
378 def __read_xensource_inventory(self):
379 filename = root_prefix() + "/etc/xensource-inventory"
380 f = open(filename, "r")
381 lines = [x.strip("\n") for x in f.readlines()]
384 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
385 defs = [ (a, b.strip("'")) for (a,b) in defs ]
389 def __pif_on_host(self,pif):
390 return pif in self.__pifs
392 def __get_pif_records_from_xapi(self, session, host):
394 for (p,rec) in session.xenapi.PIF.get_all_records().items():
395 if rec['host'] != host:
399 self.__pifs[p][f] = rec[f]
400 self.__pifs[p]['other_config'] = {}
401 for f in _PIF_OTHERCONFIG_ATTRS:
402 if f not in rec['other_config']: continue
403 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
405 def __get_vlan_records_from_xapi(self, session):
407 for (v,rec) in session.xenapi.VLAN.get_all_records().items():
408 if not self.__pif_on_host(rec['untagged_PIF']):
411 for f in _VLAN_ATTRS:
412 self.__vlans[v][f] = rec[f]
414 def __get_tunnel_records_from_xapi(self, session):
416 for t in session.xenapi.tunnel.get_all():
417 rec = session.xenapi.tunnel.get_record(t)
418 if not self.__pif_on_host(rec['transport_PIF']):
420 self.__tunnels[t] = {}
421 for f in _TUNNEL_ATTRS:
422 self.__tunnels[t][f] = rec[f]
424 def __get_bond_records_from_xapi(self, session):
426 for (b,rec) in session.xenapi.Bond.get_all_records().items():
427 if not self.__pif_on_host(rec['master']):
430 for f in _BOND_ATTRS:
431 self.__bonds[b][f] = rec[f]
433 def __get_network_records_from_xapi(self, session):
435 for (n,rec) in session.xenapi.network.get_all_records().items():
436 self.__networks[n] = {}
437 for f in _NETWORK_ATTRS:
439 # drop PIFs on other hosts
440 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
441 elif f == "MTU" and f not in rec:
442 # XenServer 5.5 network records did not have an
443 # MTU field, so allow this to be missing.
446 self.__networks[n][f] = rec[f]
447 self.__networks[n]['other_config'] = {}
448 for f in _NETWORK_OTHERCONFIG_ATTRS:
449 if f not in rec['other_config']: continue
450 self.__networks[n]['other_config'][f] = rec['other_config'][f]
452 def __get_pool_records_from_xapi(self, session):
454 for p in session.xenapi.pool.get_all():
455 rec = session.xenapi.pool.get_record(p)
459 for f in _POOL_ATTRS:
460 self.__pools[p][f] = rec[f]
462 for f in _POOL_OTHERCONFIG_ATTRS:
463 if f in rec['other_config']:
464 self.__pools[p]['other_config'][f] = rec['other_config'][f]
466 def __to_xml(self, xml, parent, key, ref, rec, attrs):
467 """Encode a database object as XML"""
468 e = xml.createElement(key)
469 parent.appendChild(e)
471 e.setAttribute('ref', ref)
473 for n,v in rec.items():
478 raise Error("Unknown attribute %s" % n)
479 def __from_xml(self, e, attrs):
480 """Decode a database object from XML"""
481 ref = e.attributes['ref'].value
483 for n in e.childNodes:
484 if n.nodeName in attrs:
485 _,h = attrs[n.nodeName]
486 rec[n.nodeName] = h(n)
489 def __init__(self, session_ref=None, cache_file=None):
490 if session_ref and cache_file:
491 raise Error("can't specify session reference and cache file")
492 if cache_file == None:
494 session = XenAPI.xapi_local()
497 log("No session ref given on command line, logging in.")
498 session.xenapi.login_with_password("root", "")
500 session._session = session_ref
504 inventory = self.__read_xensource_inventory()
505 assert('INSTALLATION_UUID' in inventory)
506 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
508 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
510 self.__get_pif_records_from_xapi(session, host)
511 self.__get_pool_records_from_xapi(session)
512 self.__get_tunnel_records_from_xapi(session)
513 self.__get_vlan_records_from_xapi(session)
514 self.__get_bond_records_from_xapi(session)
515 self.__get_network_records_from_xapi(session)
518 session.xenapi.session.logout()
520 log("Loading xapi database cache from %s" % cache_file)
522 xml = parseXML(root_prefix() + cache_file)
531 assert(len(xml.childNodes) == 1)
532 toplevel = xml.childNodes[0]
534 assert(toplevel.nodeName == "xenserver-network-configuration")
536 for n in toplevel.childNodes:
537 if n.nodeName == "#text":
539 elif n.nodeName == _PIF_XML_TAG:
540 (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
541 self.__pifs[ref] = rec
542 elif n.nodeName == _BOND_XML_TAG:
543 (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
544 self.__bonds[ref] = rec
545 elif n.nodeName == _VLAN_XML_TAG:
546 (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
547 self.__vlans[ref] = rec
548 elif n.nodeName == _TUNNEL_XML_TAG:
549 (ref,rec) = self.__from_xml(n, _TUNNEL_ATTRS)
550 self.__vlans[ref] = rec
551 elif n.nodeName == _NETWORK_XML_TAG:
552 (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
553 self.__networks[ref] = rec
554 elif n.nodeName == _POOL_XML_TAG:
555 (ref,rec) = self.__from_xml(n, _POOL_ATTRS)
556 self.__pools[ref] = rec
558 raise Error("Unknown XML element %s" % n.nodeName)
560 def save(self, cache_file):
562 xml = getDOMImplementation().createDocument(
563 None, "xenserver-network-configuration", None)
564 for (ref,rec) in self.__pifs.items():
565 self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
566 for (ref,rec) in self.__bonds.items():
567 self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
568 for (ref,rec) in self.__vlans.items():
569 self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
570 for (ref,rec) in self.__tunnels.items():
571 self.__to_xml(xml, xml.documentElement, _TUNNEL_XML_TAG, ref, rec, _TUNNEL_ATTRS)
572 for (ref,rec) in self.__networks.items():
573 self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
575 for (ref,rec) in self.__pools.items():
576 self.__to_xml(xml, xml.documentElement, _POOL_XML_TAG, ref, rec, _POOL_ATTRS)
578 temp_file = cache_file + ".%d" % os.getpid()
579 f = open(temp_file, 'w')
580 f.write(xml.toprettyxml())
582 os.rename(temp_file, cache_file)
584 def get_pif_by_uuid(self, uuid):
585 pifs = map(lambda ref_rec: ref_rec[0],
586 filter(lambda ref_rec: uuid == ref_rec[1]['uuid'],
587 self.__pifs.items()))
589 raise Error("Unknown PIF \"%s\"" % uuid)
591 raise Error("Non-unique PIF \"%s\"" % uuid)
595 def get_pifs_by_device(self, device):
596 return list(map(lambda ref_rec: ref_rec[0],
597 list(filter(lambda ref_rec: ref_rec[1]['device'] == device,
598 self.__pifs.items()))))
600 def get_networks_with_bridge(self, bridge):
601 return list(map(lambda ref_rec: ref_rec[0],
602 list(filter(lambda ref_rec: ref_rec[1]['bridge'] == bridge,
603 self.__networks.items()))))
605 def get_network_by_bridge(self, bridge):
606 #Assumes one network has bridge.
608 return self.get_networks_with_bridge(bridge)[0]
612 def get_pif_by_bridge(self, bridge):
613 networks = self.get_networks_with_bridge(bridge)
615 if len(networks) == 0:
616 raise Error("No matching network \"%s\"" % bridge)
619 for network in networks:
620 nwrec = self.get_network_record(network)
621 for pif in nwrec['PIFs']:
622 pifrec = self.get_pif_record(pif)
624 raise Error("Multiple PIFs on host for network %s" % (bridge))
627 raise Error("No PIF on host for network %s" % (bridge))
630 def get_pif_record(self, pif):
631 if pif in self.__pifs:
632 return self.__pifs[pif]
633 raise Error("Unknown PIF \"%s\"" % pif)
634 def get_all_pifs(self):
636 def pif_exists(self, pif):
637 return pif in self.__pifs
639 def get_management_pif(self):
640 """ Returns the management pif on host
642 all = self.get_all_pifs()
644 pifrec = self.get_pif_record(pif)
645 if pifrec['management']: return pif
648 def get_network_record(self, network):
649 if network in self.__networks:
650 return self.__networks[network]
651 raise Error("Unknown network \"%s\"" % network)
653 def get_bond_record(self, bond):
654 if bond in self.__bonds:
655 return self.__bonds[bond]
659 def get_vlan_record(self, vlan):
660 if vlan in self.__vlans:
661 return self.__vlans[vlan]
665 def get_pool_record(self):
666 if len(self.__pools) > 0:
667 return list(self.__pools.values())[0]
672 PIF_OTHERCONFIG_DEFAULTS = {'gro': 'off', 'lro': 'off'}
674 def ethtool_settings(oc, defaults = {}):
676 if 'ethtool-speed' in oc:
677 val = oc['ethtool-speed']
678 if val in ["10", "100", "1000"]:
679 settings += ['speed', val]
681 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
682 if 'ethtool-duplex' in oc:
683 val = oc['ethtool-duplex']
684 if val in ["half", "full"]:
685 settings += ['duplex', val]
687 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
688 if 'ethtool-autoneg' in oc:
689 val = oc['ethtool-autoneg']
690 if val in ["true", "on"]:
691 settings += ['autoneg', 'on']
692 elif val in ["false", "off"]:
693 settings += ['autoneg', 'off']
695 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
697 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro"):
698 if "ethtool-" + opt in oc:
699 val = oc["ethtool-" + opt]
700 if val in ["true", "on"]:
701 offload += [opt, 'on']
702 elif val in ["false", "off"]:
703 offload += [opt, 'off']
705 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
706 elif opt in defaults:
707 offload += [opt, defaults[opt]]
708 return settings,offload
710 # By default the MTU is taken from the Network.MTU setting for VIF,
711 # PIF and Bridge. However it is possible to override this by using
712 # {VIF,PIF,Network}.other-config:mtu.
714 # type parameter is a string describing the object that the oc parameter
715 # is from. e.g. "PIF", "Network"
716 def mtu_setting(nw, type, oc):
719 nwrec = db().get_network_record(nw)
726 log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
727 (nwrec['bridge'], type, mtu))
732 int(mtu) # Check that the value is an integer
734 except ValueError as x:
735 log("Invalid value for mtu = %s" % mtu)
740 # IP Network Devices -- network devices with IP configuration
742 def pif_ipdev_name(pif):
743 """Return the ipdev name associated with pif"""
744 pifrec = db().get_pif_record(pif)
745 nwrec = db().get_network_record(pifrec['network'])
748 # TODO: sanity check that nwrec['bridgeless'] != 'true'
749 return nwrec['bridge']
751 # TODO: sanity check that nwrec['bridgeless'] == 'true'
752 return pif_netdev_name(pif)
755 # Bare Network Devices -- network devices without IP configuration
758 def netdev_exists(netdev):
759 return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
762 def unicode_2to3(string):
763 if sys.version_info < (3,):
764 return string.encode()
768 def pif_netdev_name(pif):
769 """Get the netdev name for a PIF."""
771 pifrec = db().get_pif_record(pif)
774 return unicode_2to3("%(device)s.%(VLAN)s" % pifrec)
776 return unicode_2to3(pifrec['device'])
782 def pif_is_bridged(pif):
783 pifrec = db().get_pif_record(pif)
784 nwrec = db().get_network_record(pifrec['network'])
787 # TODO: sanity check that nwrec['bridgeless'] != 'true'
790 # TODO: sanity check that nwrec['bridgeless'] == 'true'
793 def pif_bridge_name(pif):
794 """Return the bridge name of a pif.
796 PIF must be a bridged PIF."""
797 pifrec = db().get_pif_record(pif)
799 nwrec = db().get_network_record(pifrec['network'])
802 return nwrec['bridge']
804 raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
809 def pif_is_bond(pif):
810 pifrec = db().get_pif_record(pif)
812 return len(pifrec['bond_master_of']) > 0
814 def pif_get_bond_masters(pif):
815 """Returns a list of PIFs which are bond masters of this PIF"""
817 pifrec = db().get_pif_record(pif)
819 bso = pifrec['bond_slave_of']
821 # bond-slave-of is currently a single reference but in principle a
822 # PIF could be a member of several bonds which are not
823 # concurrently attached. Be robust to this possibility.
824 if not bso or bso == "OpaqueRef:NULL":
826 elif not type(bso) == list:
829 bondrecs = [db().get_bond_record(bond) for bond in bso]
830 bondrecs = [rec for rec in bondrecs if rec]
832 return [bond['master'] for bond in bondrecs]
834 def pif_get_bond_slaves(pif):
835 """Returns a list of PIFs which make up the given bonded pif."""
837 pifrec = db().get_pif_record(pif)
839 bmo = pifrec['bond_master_of']
841 raise Error("Bond-master-of contains too many elements")
846 bondrec = db().get_bond_record(bmo[0])
848 raise Error("No bond record for bond master PIF")
850 return bondrec['slaves']
856 def pif_is_vlan(pif):
857 return db().get_pif_record(pif)['VLAN'] != '-1'
859 def pif_get_vlan_slave(pif):
860 """Find the PIF which is the VLAN slave of pif.
862 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
864 pifrec = db().get_pif_record(pif)
866 vlan = pifrec['VLAN_master_of']
867 if not vlan or vlan == "OpaqueRef:NULL":
868 raise Error("PIF is not a VLAN master")
870 vlanrec = db().get_vlan_record(vlan)
872 raise Error("No VLAN record found for PIF")
874 return vlanrec['tagged_PIF']
876 def pif_get_vlan_masters(pif):
877 """Returns a list of PIFs which are VLANs on top of the given pif."""
879 pifrec = db().get_pif_record(pif)
880 vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
881 return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
886 def pif_is_tunnel(pif):
887 return len(db().get_pif_record(pif)['tunnel_access_PIF_of']) > 0
890 # Datapath base class
893 class Datapath(object):
894 """Object encapsulating the actions necessary to (de)configure the
895 datapath for a given PIF. Does not include configuration of the
896 IP address on the ipdev.
899 def __init__(self, pif):
904 """Class method called when write action is called. Can be used
905 to update any backend specific configuration."""
908 def configure_ipdev(self, cfg):
909 """Write ifcfg TYPE field for an IPdev, plus any type specific
912 raise NotImplementedError
914 def preconfigure(self, parent):
915 """Prepare datapath configuration for PIF, but do not actually
918 Any configuration files should be attached to parent.
920 raise NotImplementedError
922 def bring_down_existing(self):
923 """Tear down any existing network device configuration which
924 needs to be undone in order to bring this PIF up.
926 raise NotImplementedError
929 """Apply the configuration prepared in the preconfigure stage.
931 Should assume any configuration files changed attached in
932 the preconfigure stage are applied and bring up the
933 necessary devices to provide the datapath for the
936 Should not bring up the IPdev.
938 raise NotImplementedError
941 """Called after the IPdev has been brought up.
943 Should do any final setup, including reinstating any
944 devices which were taken down in the bring_down_existing
947 raise NotImplementedError
949 def bring_down(self):
950 """Tear down and deconfigure the datapath. Should assume the
951 IPdev has already been brought down.
953 raise NotImplementedError
955 def DatapathFactory():
956 # XXX Need a datapath object for bridgeless PIFs
959 network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
960 network_backend = network_conf.readline().strip()
962 except Exception as e:
963 raise Error("failed to determine network backend:" + e)
965 if network_backend == "bridge":
966 from InterfaceReconfigureBridge import DatapathBridge
967 return DatapathBridge
968 elif network_backend in ["openvswitch", "vswitch"]:
969 from InterfaceReconfigureVswitch import DatapathVswitch
970 return DatapathVswitch
972 raise Error("unknown network backend %s" % network_backend)