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', 'gro', 'lro' ]
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 self.__pifs[p][f] = rec[f]
395 self.__pifs[p]['other_config'] = {}
396 for f in _PIF_OTHERCONFIG_ATTRS:
397 if not rec['other_config'].has_key(f): continue
398 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
400 def __get_vlan_records_from_xapi(self, session):
402 for (v,rec) in session.xenapi.VLAN.get_all_records().items():
403 if not self.__pif_on_host(rec['untagged_PIF']):
406 for f in _VLAN_ATTRS:
407 self.__vlans[v][f] = rec[f]
409 def __get_tunnel_records_from_xapi(self, session):
411 for t in session.xenapi.tunnel.get_all():
412 rec = session.xenapi.tunnel.get_record(t)
413 if not self.__pif_on_host(rec['transport_PIF']):
415 self.__tunnels[t] = {}
416 for f in _TUNNEL_ATTRS:
417 self.__tunnels[t][f] = rec[f]
419 def __get_bond_records_from_xapi(self, session):
421 for (b,rec) in session.xenapi.Bond.get_all_records().items():
422 if not self.__pif_on_host(rec['master']):
425 for f in _BOND_ATTRS:
426 self.__bonds[b][f] = rec[f]
428 def __get_network_records_from_xapi(self, session):
430 for (n,rec) in session.xenapi.network.get_all_records().items():
431 self.__networks[n] = {}
432 for f in _NETWORK_ATTRS:
434 # drop PIFs on other hosts
435 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
436 elif f == "MTU" and f not in rec:
437 # XenServer 5.5 network records did not have an
438 # MTU field, so allow this to be missing.
441 self.__networks[n][f] = rec[f]
442 self.__networks[n]['other_config'] = {}
443 for f in _NETWORK_OTHERCONFIG_ATTRS:
444 if not rec['other_config'].has_key(f): continue
445 self.__networks[n]['other_config'][f] = rec['other_config'][f]
447 def __get_pool_records_from_xapi(self, session):
449 for p in session.xenapi.pool.get_all():
450 rec = session.xenapi.pool.get_record(p)
454 for f in _POOL_ATTRS:
455 self.__pools[p][f] = rec[f]
457 for f in _POOL_OTHERCONFIG_ATTRS:
458 if rec['other_config'].has_key(f):
459 self.__pools[p]['other_config'][f] = rec['other_config'][f]
461 def __to_xml(self, xml, parent, key, ref, rec, attrs):
462 """Encode a database object as XML"""
463 e = xml.createElement(key)
464 parent.appendChild(e)
466 e.setAttribute('ref', ref)
468 for n,v in rec.items():
473 raise Error("Unknown attribute %s" % n)
474 def __from_xml(self, e, attrs):
475 """Decode a database object from XML"""
476 ref = e.attributes['ref'].value
478 for n in e.childNodes:
479 if n.nodeName in attrs:
480 _,h = attrs[n.nodeName]
481 rec[n.nodeName] = h(n)
484 def __init__(self, session_ref=None, cache_file=None):
485 if session_ref and cache_file:
486 raise Error("can't specify session reference and cache file")
487 if cache_file == None:
489 session = XenAPI.xapi_local()
492 log("No session ref given on command line, logging in.")
493 session.xenapi.login_with_password("root", "")
495 session._session = session_ref
499 inventory = self.__read_xensource_inventory()
500 assert(inventory.has_key('INSTALLATION_UUID'))
501 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
503 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
505 self.__get_pif_records_from_xapi(session, host)
507 self.__get_tunnel_records_from_xapi(session)
508 self.__get_pool_records_from_xapi(session)
509 self.__get_vlan_records_from_xapi(session)
510 self.__get_bond_records_from_xapi(session)
511 self.__get_network_records_from_xapi(session)
514 session.xenapi.session.logout()
516 log("Loading xapi database cache from %s" % cache_file)
518 xml = parseXML(root_prefix() + cache_file)
527 assert(len(xml.childNodes) == 1)
528 toplevel = xml.childNodes[0]
530 assert(toplevel.nodeName == "xenserver-network-configuration")
532 for n in toplevel.childNodes:
533 if n.nodeName == "#text":
535 elif n.nodeName == _PIF_XML_TAG:
536 (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
537 self.__pifs[ref] = rec
538 elif n.nodeName == _BOND_XML_TAG:
539 (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
540 self.__bonds[ref] = rec
541 elif n.nodeName == _VLAN_XML_TAG:
542 (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
543 self.__vlans[ref] = rec
544 elif n.nodeName == _TUNNEL_XML_TAG:
545 (ref,rec) = self.__from_xml(n, _TUNNEL_ATTRS)
546 self.__vlans[ref] = rec
547 elif n.nodeName == _NETWORK_XML_TAG:
548 (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
549 self.__networks[ref] = rec
550 elif n.nodeName == _POOL_XML_TAG:
551 (ref,rec) = self.__from_xml(n, _POOL_ATTRS)
552 self.__pools[ref] = rec
554 raise Error("Unknown XML element %s" % n.nodeName)
556 def save(self, cache_file):
558 xml = getDOMImplementation().createDocument(
559 None, "xenserver-network-configuration", None)
560 for (ref,rec) in self.__pifs.items():
561 self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
562 for (ref,rec) in self.__bonds.items():
563 self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
564 for (ref,rec) in self.__vlans.items():
565 self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
566 for (ref,rec) in self.__tunnels.items():
567 self.__to_xml(xml, xml.documentElement, _TUNNEL_XML_TAG, ref, rec, _TUNNEL_ATTRS)
568 for (ref,rec) in self.__networks.items():
569 self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
571 for (ref,rec) in self.__pools.items():
572 self.__to_xml(xml, xml.documentElement, _POOL_XML_TAG, ref, rec, _POOL_ATTRS)
574 f = open(cache_file, 'w')
575 f.write(xml.toprettyxml())
578 def get_pif_by_uuid(self, uuid):
579 pifs = map(lambda (ref,rec): ref,
580 filter(lambda (ref,rec): uuid == rec['uuid'],
581 self.__pifs.items()))
583 raise Error("Unknown PIF \"%s\"" % uuid)
585 raise Error("Non-unique PIF \"%s\"" % uuid)
589 def get_pifs_by_device(self, device):
590 return map(lambda (ref,rec): ref,
591 filter(lambda (ref,rec): rec['device'] == device,
592 self.__pifs.items()))
594 def get_networks_with_bridge(self, bridge):
595 return map(lambda (ref,rec): ref,
596 filter(lambda (ref,rec): rec['bridge'] == bridge,
597 self.__networks.items()))
599 def get_network_by_bridge(self, bridge):
600 #Assumes one network has bridge.
602 return self.get_networks_with_bridge(bridge)[0]
606 def get_pif_by_bridge(self, bridge):
607 networks = self.get_networks_with_bridge(bridge)
609 if len(networks) == 0:
610 raise Error("No matching network \"%s\"" % bridge)
613 for network in networks:
614 nwrec = self.get_network_record(network)
615 for pif in nwrec['PIFs']:
616 pifrec = self.get_pif_record(pif)
618 raise Error("Multiple PIFs on host for network %s" % (bridge))
621 raise Error("No PIF on host for network %s" % (bridge))
624 def get_pif_record(self, pif):
625 if self.__pifs.has_key(pif):
626 return self.__pifs[pif]
627 raise Error("Unknown PIF \"%s\"" % pif)
628 def get_all_pifs(self):
630 def pif_exists(self, pif):
631 return self.__pifs.has_key(pif)
633 def get_management_pif(self):
634 """ Returns the management pif on host
636 all = self.get_all_pifs()
638 pifrec = self.get_pif_record(pif)
639 if pifrec['management']: return pif
642 def get_network_record(self, network):
643 if self.__networks.has_key(network):
644 return self.__networks[network]
645 raise Error("Unknown network \"%s\"" % network)
647 def get_bond_record(self, bond):
648 if self.__bonds.has_key(bond):
649 return self.__bonds[bond]
653 def get_vlan_record(self, vlan):
654 if self.__vlans.has_key(vlan):
655 return self.__vlans[vlan]
659 def get_pool_record(self):
660 if len(self.__pools) > 0:
661 return self.__pools.values()[0]
666 PIF_OTHERCONFIG_DEFAULTS = {'gro': 'off', 'lro': 'off'}
668 def ethtool_settings(oc, defaults = {}):
670 if oc.has_key('ethtool-speed'):
671 val = oc['ethtool-speed']
672 if val in ["10", "100", "1000"]:
673 settings += ['speed', val]
675 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
676 if oc.has_key('ethtool-duplex'):
677 val = oc['ethtool-duplex']
678 if val in ["half", "full"]:
679 settings += ['duplex', val]
681 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
682 if oc.has_key('ethtool-autoneg'):
683 val = oc['ethtool-autoneg']
684 if val in ["true", "on"]:
685 settings += ['autoneg', 'on']
686 elif val in ["false", "off"]:
687 settings += ['autoneg', 'off']
689 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
691 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro"):
692 if oc.has_key("ethtool-" + opt):
693 val = oc["ethtool-" + opt]
694 if val in ["true", "on"]:
695 offload += [opt, 'on']
696 elif val in ["false", "off"]:
697 offload += [opt, 'off']
699 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
700 elif opt in defaults:
701 offload += [opt, defaults[opt]]
702 return settings,offload
704 # By default the MTU is taken from the Network.MTU setting for VIF,
705 # PIF and Bridge. However it is possible to override this by using
706 # {VIF,PIF,Network}.other-config:mtu.
708 # type parameter is a string describing the object that the oc parameter
709 # is from. e.g. "PIF", "Network"
710 def mtu_setting(nw, type, oc):
713 nwrec = db().get_network_record(nw)
714 if nwrec.has_key('MTU'):
719 if oc.has_key('mtu'):
720 log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
721 (nwrec['bridge'], type, mtu))
726 int(mtu) # Check that the value is an integer
728 except ValueError, x:
729 log("Invalid value for mtu = %s" % mtu)
734 # IP Network Devices -- network devices with IP configuration
736 def pif_ipdev_name(pif):
737 """Return the ipdev name associated with pif"""
738 pifrec = db().get_pif_record(pif)
739 nwrec = db().get_network_record(pifrec['network'])
742 # TODO: sanity check that nwrec['bridgeless'] != 'true'
743 return nwrec['bridge']
745 # TODO: sanity check that nwrec['bridgeless'] == 'true'
746 return pif_netdev_name(pif)
749 # Bare Network Devices -- network devices without IP configuration
752 def netdev_exists(netdev):
753 return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
755 def pif_netdev_name(pif):
756 """Get the netdev name for a PIF."""
758 pifrec = db().get_pif_record(pif)
761 return "%(device)s.%(VLAN)s" % pifrec
763 return pifrec['device']
769 def pif_is_bridged(pif):
770 pifrec = db().get_pif_record(pif)
771 nwrec = db().get_network_record(pifrec['network'])
774 # TODO: sanity check that nwrec['bridgeless'] != 'true'
777 # TODO: sanity check that nwrec['bridgeless'] == 'true'
780 def pif_bridge_name(pif):
781 """Return the bridge name of a pif.
783 PIF must be a bridged PIF."""
784 pifrec = db().get_pif_record(pif)
786 nwrec = db().get_network_record(pifrec['network'])
789 return nwrec['bridge']
791 raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
796 def pif_is_bond(pif):
797 pifrec = db().get_pif_record(pif)
799 return len(pifrec['bond_master_of']) > 0
801 def pif_get_bond_masters(pif):
802 """Returns a list of PIFs which are bond masters of this PIF"""
804 pifrec = db().get_pif_record(pif)
806 bso = pifrec['bond_slave_of']
808 # bond-slave-of is currently a single reference but in principle a
809 # PIF could be a member of several bonds which are not
810 # concurrently attached. Be robust to this possibility.
811 if not bso or bso == "OpaqueRef:NULL":
813 elif not type(bso) == list:
816 bondrecs = [db().get_bond_record(bond) for bond in bso]
817 bondrecs = [rec for rec in bondrecs if rec]
819 return [bond['master'] for bond in bondrecs]
821 def pif_get_bond_slaves(pif):
822 """Returns a list of PIFs which make up the given bonded pif."""
824 pifrec = db().get_pif_record(pif)
826 bmo = pifrec['bond_master_of']
828 raise Error("Bond-master-of contains too many elements")
833 bondrec = db().get_bond_record(bmo[0])
835 raise Error("No bond record for bond master PIF")
837 return bondrec['slaves']
843 def pif_is_vlan(pif):
844 return db().get_pif_record(pif)['VLAN'] != '-1'
846 def pif_get_vlan_slave(pif):
847 """Find the PIF which is the VLAN slave of pif.
849 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
851 pifrec = db().get_pif_record(pif)
853 vlan = pifrec['VLAN_master_of']
854 if not vlan or vlan == "OpaqueRef:NULL":
855 raise Error("PIF is not a VLAN master")
857 vlanrec = db().get_vlan_record(vlan)
859 raise Error("No VLAN record found for PIF")
861 return vlanrec['tagged_PIF']
863 def pif_get_vlan_masters(pif):
864 """Returns a list of PIFs which are VLANs on top of the given pif."""
866 pifrec = db().get_pif_record(pif)
867 vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
868 return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
873 def pif_is_tunnel(pif):
874 return len(db().get_pif_record(pif)['tunnel_access_PIF_of']) > 0
877 # Datapath base class
880 class Datapath(object):
881 """Object encapsulating the actions necessary to (de)configure the
882 datapath for a given PIF. Does not include configuration of the
883 IP address on the ipdev.
886 def __init__(self, pif):
891 """Class method called when write action is called. Can be used
892 to update any backend specific configuration."""
895 def configure_ipdev(self, cfg):
896 """Write ifcfg TYPE field for an IPdev, plus any type specific
899 raise NotImplementedError
901 def preconfigure(self, parent):
902 """Prepare datapath configuration for PIF, but do not actually
905 Any configuration files should be attached to parent.
907 raise NotImplementedError
909 def bring_down_existing(self):
910 """Tear down any existing network device configuration which
911 needs to be undone in order to bring this PIF up.
913 raise NotImplementedError
916 """Apply the configuration prepared in the preconfigure stage.
918 Should assume any configuration files changed attached in
919 the preconfigure stage are applied and bring up the
920 necesary devices to provide the datapath for the
923 Should not bring up the IPdev.
925 raise NotImplementedError
928 """Called after the IPdev has been brought up.
930 Should do any final setup, including reinstating any
931 devices which were taken down in the bring_down_existing
934 raise NotImplementedError
936 def bring_down(self):
937 """Tear down and deconfigure the datapath. Should assume the
938 IPdev has already been brought down.
940 raise NotImplementedError
942 def DatapathFactory():
943 # XXX Need a datapath object for bridgeless PIFs
946 network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
947 network_backend = network_conf.readline().strip()
950 raise Error("failed to determine network backend:" + e)
952 if network_backend == "bridge":
953 from InterfaceReconfigureBridge import DatapathBridge
954 return DatapathBridge
955 elif network_backend in ["openvswitch", "vswitch"]:
956 from InterfaceReconfigureVswitch import DatapathVswitch
957 return DatapathVswitch
959 raise Error("unknown network backend %s" % network_backend)