1 # Copyright (c) 2008,2009 Citrix Systems, Inc.
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU Lesser General Public License as published
5 # by the Free Software Foundation; version 2.1 only. with the special
6 # exception on linking described in file LICENSE.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU Lesser General Public License for more details.
17 from xml.dom.minidom import getDOMImplementation
18 from xml.dom.minidom import parse as parseXML
22 """Returns a string to prefix to all file name references, which
23 is useful for testing."""
24 return the_root_prefix
25 def set_root_prefix(prefix):
26 global the_root_prefix
27 the_root_prefix = prefix
29 log_destination = "syslog"
30 def get_log_destination():
31 """Returns the current log destination.
32 'syslog' means "log to syslog".
33 'stderr' means "log to stderr"."""
34 return log_destination
35 def set_log_destination(dest):
36 global log_destination
37 log_destination = dest
44 if get_log_destination() == 'syslog':
53 class Error(Exception):
54 def __init__(self, msg):
55 Exception.__init__(self)
59 # Run external utilities
62 def run_command(command):
63 log("Running command: " + ' '.join(command))
64 rc = os.spawnl(os.P_WAIT, root_prefix() + command[0], *command)
66 log("Command failed %d: " % rc + ' '.join(command))
71 # Configuration File Handling.
74 class ConfigurationFile(object):
75 """Write a file, tracking old and new versions.
77 Supports writing a new version of a file and applying and
78 reverting those changes.
81 __STATE = {"OPEN":"OPEN",
82 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
83 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
85 def __init__(self, path):
86 dirname,basename = os.path.split(path)
88 self.__state = self.__STATE['OPEN']
91 self.__path = os.path.join(dirname, basename)
92 self.__oldpath = os.path.join(dirname, "." + basename + ".xapi-old")
93 self.__newpath = os.path.join(dirname, "." + basename + ".xapi-new")
95 self.__f = open(self.__newpath, "w")
97 def attach_child(self, child):
98 self.__children.append(child)
105 return open(self.path()).readlines()
109 def write(self, args):
110 if self.__state != self.__STATE['OPEN']:
111 raise Error("Attempt to write to file in state %s" % self.__state)
115 if self.__state != self.__STATE['OPEN']:
116 raise Error("Attempt to close file in state %s" % self.__state)
119 self.__state = self.__STATE['NOT-APPLIED']
122 if self.__state != self.__STATE['NOT-APPLIED']:
123 raise Error("Attempt to compare file in state %s" % self.__state)
128 if self.__state != self.__STATE['NOT-APPLIED']:
129 raise Error("Attempt to apply configuration from state %s" % self.__state)
131 for child in self.__children:
134 log("Applying changes to %s configuration" % self.__path)
136 # Remove previous backup.
137 if os.access(self.__oldpath, os.F_OK):
138 os.unlink(self.__oldpath)
140 # Save current configuration.
141 if os.access(self.__path, os.F_OK):
142 os.link(self.__path, self.__oldpath)
143 os.unlink(self.__path)
145 # Apply new configuration.
146 assert(os.path.exists(self.__newpath))
147 os.link(self.__newpath, self.__path)
149 # Remove temporary file.
150 os.unlink(self.__newpath)
152 self.__state = self.__STATE['APPLIED']
155 if self.__state != self.__STATE['APPLIED']:
156 raise Error("Attempt to revert configuration from state %s" % self.__state)
158 for child in self.__children:
161 log("Reverting changes to %s configuration" % self.__path)
163 # Remove existing new configuration
164 if os.access(self.__newpath, os.F_OK):
165 os.unlink(self.__newpath)
167 # Revert new configuration.
168 if os.access(self.__path, os.F_OK):
169 os.link(self.__path, self.__newpath)
170 os.unlink(self.__path)
172 # Revert to old configuration.
173 if os.access(self.__oldpath, os.F_OK):
174 os.link(self.__oldpath, self.__path)
175 os.unlink(self.__oldpath)
177 # Leave .*.xapi-new as an aid to debugging.
179 self.__state = self.__STATE['REVERTED']
182 if self.__state != self.__STATE['APPLIED']:
183 raise Error("Attempt to commit configuration from state %s" % self.__state)
185 for child in self.__children:
188 log("Committing changes to %s configuration" % self.__path)
190 if os.access(self.__oldpath, os.F_OK):
191 os.unlink(self.__oldpath)
192 if os.access(self.__newpath, os.F_OK):
193 os.unlink(self.__newpath)
195 self.__state = self.__STATE['COMMITTED']
198 # Helper functions for encoding/decoding database attributes to/from XML.
201 def _str_to_xml(xml, parent, tag, val):
202 e = xml.createElement(tag)
203 parent.appendChild(e)
204 v = xml.createTextNode(val)
206 def _str_from_xml(n):
207 def getText(nodelist):
209 for node in nodelist:
210 if node.nodeType == node.TEXT_NODE:
213 return getText(n.childNodes).strip()
215 def _bool_to_xml(xml, parent, tag, val):
217 _str_to_xml(xml, parent, tag, "True")
219 _str_to_xml(xml, parent, tag, "False")
220 def _bool_from_xml(n):
227 raise Error("Unknown boolean value %s" % s)
229 def _strlist_to_xml(xml, parent, ltag, itag, val):
230 e = xml.createElement(ltag)
231 parent.appendChild(e)
233 c = xml.createElement(itag)
235 cv = xml.createTextNode(v)
237 def _strlist_from_xml(n, ltag, itag):
239 for n in n.childNodes:
240 if n.nodeName == itag:
241 ret.append(_str_from_xml(n))
244 def _map_to_xml(xml, parent, tag, val, attrs):
245 e = xml.createElement(tag)
246 parent.appendChild(e)
247 for n,v in val.items():
249 _str_to_xml(xml, e, n, v)
251 log("Unknown other-config attribute: %s" % n)
253 def _map_from_xml(n, attrs):
255 for n in n.childNodes:
256 if n.nodeName in attrs:
257 ret[n.nodeName] = _str_from_xml(n)
260 def _otherconfig_to_xml(xml, parent, val, attrs):
261 return _map_to_xml(xml, parent, "other_config", val, attrs)
262 def _otherconfig_from_xml(n, attrs):
263 return _map_from_xml(n, attrs)
266 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
268 # Each object is defined by a dictionary mapping an attribute name in
269 # the xapi database to a tuple containing two items:
270 # - a function which takes this attribute and encodes it as XML.
271 # - a function which takes XML and decocdes it into a value.
273 # other-config attributes are specified as a simple array of strings
276 _VLAN_XML_TAG = "vlan"
277 _TUNNEL_XML_TAG = "tunnel"
278 _BOND_XML_TAG = "bond"
279 _NETWORK_XML_TAG = "network"
280 _POOL_XML_TAG = "pool"
282 _ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
284 _PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
285 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay',
286 'updelay', 'use_carrier', 'hashing-algorithm' ] + \
287 _ETHTOOL_OTHERCONFIG_ATTRS
289 _PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
290 'management': (_bool_to_xml,_bool_from_xml),
291 'network': (_str_to_xml,_str_from_xml),
292 'device': (_str_to_xml,_str_from_xml),
293 'bond_master_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
294 lambda n: _strlist_from_xml(n, 'bond_master_of', 'slave')),
295 'bond_slave_of': (_str_to_xml,_str_from_xml),
296 'VLAN': (_str_to_xml,_str_from_xml),
297 'VLAN_master_of': (_str_to_xml,_str_from_xml),
298 'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
299 lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
300 'tunnel_access_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_access_PIF_of', 'pif', v),
301 lambda n: _strlist_from_xml(n, 'tunnel_access_PIF_of', 'pif')),
302 'tunnel_transport_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_transport_PIF_of', 'pif', v),
303 lambda n: _strlist_from_xml(n, 'tunnel_transport_PIF_of', 'pif')),
304 'ip_configuration_mode': (_str_to_xml,_str_from_xml),
305 'IP': (_str_to_xml,_str_from_xml),
306 'netmask': (_str_to_xml,_str_from_xml),
307 'gateway': (_str_to_xml,_str_from_xml),
308 'DNS': (_str_to_xml,_str_from_xml),
309 'MAC': (_str_to_xml,_str_from_xml),
310 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _PIF_OTHERCONFIG_ATTRS),
311 lambda n: _otherconfig_from_xml(n, _PIF_OTHERCONFIG_ATTRS)),
313 # Special case: We write the current value
314 # PIF.currently-attached to the cache but since it will
315 # not be valid when we come to use the cache later
316 # (i.e. after a reboot) we always read it as False.
317 'currently_attached': (_bool_to_xml, lambda n: False),
320 _VLAN_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
321 'tagged_PIF': (_str_to_xml,_str_from_xml),
322 'untagged_PIF': (_str_to_xml,_str_from_xml),
325 _TUNNEL_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
326 'access_PIF': (_str_to_xml,_str_from_xml),
327 'transport_PIF': (_str_to_xml,_str_from_xml),
329 _BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
330 'master': (_str_to_xml,_str_from_xml),
331 'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
332 lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
335 _NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes', 'vswitch-controller-fail-mode' ] + _ETHTOOL_OTHERCONFIG_ATTRS
337 _NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
338 'bridge': (_str_to_xml,_str_from_xml),
339 'MTU': (_str_to_xml,_str_from_xml),
340 'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
341 lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
342 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
343 lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
346 _POOL_OTHERCONFIG_ATTRS = ['vswitch-controller-fail-mode']
348 _POOL_ATTRS = { 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _POOL_OTHERCONFIG_ATTRS),
349 lambda n: _otherconfig_from_xml(n, _POOL_OTHERCONFIG_ATTRS)),
353 # Database Cache object
359 assert(_db is not None)
362 def db_init_from_cache(cache):
365 _db = DatabaseCache(cache_file=cache)
367 def db_init_from_xenapi(session):
370 _db = DatabaseCache(session_ref=session)
372 class DatabaseCache(object):
373 def __read_xensource_inventory(self):
374 filename = root_prefix() + "/etc/xensource-inventory"
375 f = open(filename, "r")
376 lines = [x.strip("\n") for x in f.readlines()]
379 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
380 defs = [ (a, b.strip("'")) for (a,b) in defs ]
384 def __pif_on_host(self,pif):
385 return self.__pifs.has_key(pif)
387 def __get_pif_records_from_xapi(self, session, host):
389 for (p,rec) in session.xenapi.PIF.get_all_records().items():
390 if rec['host'] != host:
394 if f in [ "tunnel_access_PIF_of", "tunnel_transport_PIF_of" ] and f not in rec:
395 # XenServer 5.5 network records did not have
396 # these fields, so allow them to be missing.
399 self.__pifs[p][f] = rec[f]
400 self.__pifs[p]['other_config'] = {}
401 for f in _PIF_OTHERCONFIG_ATTRS:
402 if not rec['other_config'].has_key(f): continue
403 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
405 def __get_vlan_records_from_xapi(self, session):
407 for v in session.xenapi.VLAN.get_all():
408 rec = session.xenapi.VLAN.get_record(v)
409 if not self.__pif_on_host(rec['untagged_PIF']):
412 for f in _VLAN_ATTRS:
413 self.__vlans[v][f] = rec[f]
415 def __get_tunnel_records_from_xapi(self, session):
417 for t in session.xenapi.tunnel.get_all():
418 rec = session.xenapi.tunnel.get_record(t)
419 if not self.__pif_on_host(rec['transport_PIF']):
421 self.__tunnels[t] = {}
422 for f in _TUNNEL_ATTRS:
423 self.__tunnels[t][f] = rec[f]
425 def __get_bond_records_from_xapi(self, session):
427 for b in session.xenapi.Bond.get_all():
428 rec = session.xenapi.Bond.get_record(b)
429 if not self.__pif_on_host(rec['master']):
432 for f in _BOND_ATTRS:
433 self.__bonds[b][f] = rec[f]
435 def __get_network_records_from_xapi(self, session):
437 for n in session.xenapi.network.get_all():
438 rec = session.xenapi.network.get_record(n)
439 self.__networks[n] = {}
440 for f in _NETWORK_ATTRS:
442 # drop PIFs on other hosts
443 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
444 elif f == "MTU" and f not in rec:
445 # XenServer 5.5 network records did not have an
446 # MTU field, so allow this to be missing.
449 self.__networks[n][f] = rec[f]
450 self.__networks[n]['other_config'] = {}
451 for f in _NETWORK_OTHERCONFIG_ATTRS:
452 if not rec['other_config'].has_key(f): continue
453 self.__networks[n]['other_config'][f] = rec['other_config'][f]
455 def __get_pool_records_from_xapi(self, session):
457 for p in session.xenapi.pool.get_all():
458 rec = session.xenapi.pool.get_record(p)
462 for f in _POOL_ATTRS:
463 self.__pools[p][f] = rec[f]
465 for f in _POOL_OTHERCONFIG_ATTRS:
466 if rec['other_config'].has_key(f):
467 self.__pools[p]['other_config'][f] = rec['other_config'][f]
469 def __to_xml(self, xml, parent, key, ref, rec, attrs):
470 """Encode a database object as XML"""
471 e = xml.createElement(key)
472 parent.appendChild(e)
474 e.setAttribute('ref', ref)
476 for n,v in rec.items():
481 raise Error("Unknown attribute %s" % n)
482 def __from_xml(self, e, attrs):
483 """Decode a database object from XML"""
484 ref = e.attributes['ref'].value
486 for n in e.childNodes:
487 if n.nodeName in attrs:
488 _,h = attrs[n.nodeName]
489 rec[n.nodeName] = h(n)
492 def __init__(self, session_ref=None, cache_file=None):
493 if session_ref and cache_file:
494 raise Error("can't specify session reference and cache file")
495 if cache_file == None:
497 session = XenAPI.xapi_local()
500 log("No session ref given on command line, logging in.")
501 session.xenapi.login_with_password("root", "")
503 session._session = session_ref
507 inventory = self.__read_xensource_inventory()
508 assert(inventory.has_key('INSTALLATION_UUID'))
509 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
511 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
513 self.__get_pif_records_from_xapi(session, host)
516 self.__get_tunnel_records_from_xapi(session)
517 except XenAPI.Failure, e:
518 error,details = e.details
519 if error == "MESSAGE_METHOD_UNKNOWN" and details == "tunnel.get_all":
522 self.__get_pool_records_from_xapi(session)
523 self.__get_vlan_records_from_xapi(session)
524 self.__get_bond_records_from_xapi(session)
525 self.__get_network_records_from_xapi(session)
528 session.xenapi.session.logout()
530 log("Loading xapi database cache from %s" % cache_file)
532 xml = parseXML(root_prefix() + cache_file)
541 assert(len(xml.childNodes) == 1)
542 toplevel = xml.childNodes[0]
544 assert(toplevel.nodeName == "xenserver-network-configuration")
546 for n in toplevel.childNodes:
547 if n.nodeName == "#text":
549 elif n.nodeName == _PIF_XML_TAG:
550 (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
551 self.__pifs[ref] = rec
552 elif n.nodeName == _BOND_XML_TAG:
553 (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
554 self.__bonds[ref] = rec
555 elif n.nodeName == _VLAN_XML_TAG:
556 (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
557 self.__vlans[ref] = rec
558 elif n.nodeName == _TUNNEL_XML_TAG:
559 (ref,rec) = self.__from_xml(n, _TUNNEL_ATTRS)
560 self.__vlans[ref] = rec
561 elif n.nodeName == _NETWORK_XML_TAG:
562 (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
563 self.__networks[ref] = rec
564 elif n.nodeName == _POOL_XML_TAG:
565 (ref,rec) = self.__from_xml(n, _POOL_ATTRS)
566 self.__pools[ref] = rec
568 raise Error("Unknown XML element %s" % n.nodeName)
570 def save(self, cache_file):
572 xml = getDOMImplementation().createDocument(
573 None, "xenserver-network-configuration", None)
574 for (ref,rec) in self.__pifs.items():
575 self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
576 for (ref,rec) in self.__bonds.items():
577 self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
578 for (ref,rec) in self.__vlans.items():
579 self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
580 for (ref,rec) in self.__tunnels.items():
581 self.__to_xml(xml, xml.documentElement, _TUNNEL_XML_TAG, ref, rec, _TUNNEL_ATTRS)
582 for (ref,rec) in self.__networks.items():
583 self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
585 for (ref,rec) in self.__pools.items():
586 self.__to_xml(xml, xml.documentElement, _POOL_XML_TAG, ref, rec, _POOL_ATTRS)
588 f = open(cache_file, 'w')
589 f.write(xml.toprettyxml())
592 def get_pif_by_uuid(self, uuid):
593 pifs = map(lambda (ref,rec): ref,
594 filter(lambda (ref,rec): uuid == rec['uuid'],
595 self.__pifs.items()))
597 raise Error("Unknown PIF \"%s\"" % uuid)
599 raise Error("Non-unique PIF \"%s\"" % uuid)
603 def get_pifs_by_device(self, device):
604 return map(lambda (ref,rec): ref,
605 filter(lambda (ref,rec): rec['device'] == device,
606 self.__pifs.items()))
608 def get_networks_with_bridge(self, bridge):
609 return map(lambda (ref,rec): ref,
610 filter(lambda (ref,rec): rec['bridge'] == bridge,
611 self.__networks.items()))
613 def get_network_by_bridge(self, bridge):
614 #Assumes one network has bridge.
616 return self.get_networks_with_bridge(bridge)[0]
620 def get_pif_by_bridge(self, bridge):
621 networks = self.get_networks_with_bridge(bridge)
623 if len(networks) == 0:
624 raise Error("No matching network \"%s\"" % bridge)
627 for network in networks:
628 nwrec = self.get_network_record(network)
629 for pif in nwrec['PIFs']:
630 pifrec = self.get_pif_record(pif)
632 raise Error("Multiple PIFs on host for network %s" % (bridge))
635 raise Error("No PIF on host for network %s" % (bridge))
638 def get_pif_record(self, pif):
639 if self.__pifs.has_key(pif):
640 return self.__pifs[pif]
641 raise Error("Unknown PIF \"%s\"" % pif)
642 def get_all_pifs(self):
644 def pif_exists(self, pif):
645 return self.__pifs.has_key(pif)
647 def get_management_pif(self):
648 """ Returns the management pif on host
650 all = self.get_all_pifs()
652 pifrec = self.get_pif_record(pif)
653 if pifrec['management']: return pif
656 def get_network_record(self, network):
657 if self.__networks.has_key(network):
658 return self.__networks[network]
659 raise Error("Unknown network \"%s\"" % network)
661 def get_bond_record(self, bond):
662 if self.__bonds.has_key(bond):
663 return self.__bonds[bond]
667 def get_vlan_record(self, vlan):
668 if self.__vlans.has_key(vlan):
669 return self.__vlans[vlan]
673 def get_pool_record(self):
674 if len(self.__pools) > 0:
675 return self.__pools.values()[0]
681 def ethtool_settings(oc):
683 if oc.has_key('ethtool-speed'):
684 val = oc['ethtool-speed']
685 if val in ["10", "100", "1000"]:
686 settings += ['speed', val]
688 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
689 if oc.has_key('ethtool-duplex'):
690 val = oc['ethtool-duplex']
691 if val in ["10", "100", "1000"]:
692 settings += ['duplex', 'val']
694 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
695 if oc.has_key('ethtool-autoneg'):
696 val = oc['ethtool-autoneg']
697 if val in ["true", "on"]:
698 settings += ['autoneg', 'on']
699 elif val in ["false", "off"]:
700 settings += ['autoneg', 'off']
702 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
704 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
705 if oc.has_key("ethtool-" + opt):
706 val = oc["ethtool-" + opt]
707 if val in ["true", "on"]:
708 offload += [opt, 'on']
709 elif val in ["false", "off"]:
710 offload += [opt, 'off']
712 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
713 return settings,offload
715 # By default the MTU is taken from the Network.MTU setting for VIF,
716 # PIF and Bridge. However it is possible to override this by using
717 # {VIF,PIF,Network}.other-config:mtu.
719 # type parameter is a string describing the object that the oc parameter
720 # is from. e.g. "PIF", "Network"
721 def mtu_setting(nw, type, oc):
724 nwrec = db().get_network_record(nw)
725 if nwrec.has_key('MTU'):
730 if oc.has_key('mtu'):
731 log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
732 (nwrec['bridge'], type, mtu))
737 int(mtu) # Check that the value is an integer
739 except ValueError, x:
740 log("Invalid value for mtu = %s" % mtu)
745 # IP Network Devices -- network devices with IP configuration
747 def pif_ipdev_name(pif):
748 """Return the ipdev name associated with pif"""
749 pifrec = db().get_pif_record(pif)
750 nwrec = db().get_network_record(pifrec['network'])
753 # TODO: sanity check that nwrec['bridgeless'] != 'true'
754 return nwrec['bridge']
756 # TODO: sanity check that nwrec['bridgeless'] == 'true'
757 return pif_netdev_name(pif)
760 # Bare Network Devices -- network devices without IP configuration
763 def netdev_exists(netdev):
764 return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
766 def pif_netdev_name(pif):
767 """Get the netdev name for a PIF."""
769 pifrec = db().get_pif_record(pif)
772 return "%(device)s.%(VLAN)s" % pifrec
774 return pifrec['device']
780 def pif_is_bridged(pif):
781 pifrec = db().get_pif_record(pif)
782 nwrec = db().get_network_record(pifrec['network'])
785 # TODO: sanity check that nwrec['bridgeless'] != 'true'
788 # TODO: sanity check that nwrec['bridgeless'] == 'true'
791 def pif_bridge_name(pif):
792 """Return the bridge name of a pif.
794 PIF must be a bridged PIF."""
795 pifrec = db().get_pif_record(pif)
797 nwrec = db().get_network_record(pifrec['network'])
800 return nwrec['bridge']
802 raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
807 def pif_is_bond(pif):
808 pifrec = db().get_pif_record(pif)
810 return len(pifrec['bond_master_of']) > 0
812 def pif_get_bond_masters(pif):
813 """Returns a list of PIFs which are bond masters of this PIF"""
815 pifrec = db().get_pif_record(pif)
817 bso = pifrec['bond_slave_of']
819 # bond-slave-of is currently a single reference but in principle a
820 # PIF could be a member of several bonds which are not
821 # concurrently attached. Be robust to this possibility.
822 if not bso or bso == "OpaqueRef:NULL":
824 elif not type(bso) == list:
827 bondrecs = [db().get_bond_record(bond) for bond in bso]
828 bondrecs = [rec for rec in bondrecs if rec]
830 return [bond['master'] for bond in bondrecs]
832 def pif_get_bond_slaves(pif):
833 """Returns a list of PIFs which make up the given bonded pif."""
835 pifrec = db().get_pif_record(pif)
837 bmo = pifrec['bond_master_of']
839 raise Error("Bond-master-of contains too many elements")
844 bondrec = db().get_bond_record(bmo[0])
846 raise Error("No bond record for bond master PIF")
848 return bondrec['slaves']
854 def pif_is_vlan(pif):
855 return db().get_pif_record(pif)['VLAN'] != '-1'
857 def pif_get_vlan_slave(pif):
858 """Find the PIF which is the VLAN slave of pif.
860 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
862 pifrec = db().get_pif_record(pif)
864 vlan = pifrec['VLAN_master_of']
865 if not vlan or vlan == "OpaqueRef:NULL":
866 raise Error("PIF is not a VLAN master")
868 vlanrec = db().get_vlan_record(vlan)
870 raise Error("No VLAN record found for PIF")
872 return vlanrec['tagged_PIF']
874 def pif_get_vlan_masters(pif):
875 """Returns a list of PIFs which are VLANs on top of the given pif."""
877 pifrec = db().get_pif_record(pif)
878 vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
879 return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
884 def pif_is_tunnel(pif):
885 rec = db().get_pif_record(pif)
886 return rec.has_key('tunnel_access_PIF_of') and len(rec['tunnel_access_PIF_of']) > 0
889 # Datapath base class
892 class Datapath(object):
893 """Object encapsulating the actions necessary to (de)configure the
894 datapath for a given PIF. Does not include configuration of the
895 IP address on the ipdev.
898 def __init__(self, pif):
903 """Class method called when write action is called. Can be used
904 to update any backend specific configuration."""
907 def configure_ipdev(self, cfg):
908 """Write ifcfg TYPE field for an IPdev, plus any type specific
911 raise NotImplementedError
913 def preconfigure(self, parent):
914 """Prepare datapath configuration for PIF, but do not actually
917 Any configuration files should be attached to parent.
919 raise NotImplementedError
921 def bring_down_existing(self):
922 """Tear down any existing network device configuration which
923 needs to be undone in order to bring this PIF up.
925 raise NotImplementedError
928 """Apply the configuration prepared in the preconfigure stage.
930 Should assume any configuration files changed attached in
931 the preconfigure stage are applied and bring up the
932 necesary devices to provide the datapath for the
935 Should not bring up the IPdev.
937 raise NotImplementedError
940 """Called after the IPdev has been brought up.
942 Should do any final setup, including reinstating any
943 devices which were taken down in the bring_down_existing
946 raise NotImplementedError
948 def bring_down(self):
949 """Tear down and deconfigure the datapath. Should assume the
950 IPdev has already been brought down.
952 raise NotImplementedError
954 def DatapathFactory():
955 # XXX Need a datapath object for bridgeless PIFs
958 network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
959 network_backend = network_conf.readline().strip()
962 raise Error("failed to determine network backend:" + e)
964 if network_backend == "bridge":
965 from InterfaceReconfigureBridge import DatapathBridge
966 return DatapathBridge
967 elif network_backend in ["openvswitch", "vswitch"]:
968 from InterfaceReconfigureVswitch import DatapathVswitch
969 return DatapathVswitch
971 raise Error("unknown network backend %s" % network_backend)