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', 'updelay', 'use_carrier' ] + \
286 _ETHTOOL_OTHERCONFIG_ATTRS
288 _PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
289 'management': (_bool_to_xml,_bool_from_xml),
290 'network': (_str_to_xml,_str_from_xml),
291 'device': (_str_to_xml,_str_from_xml),
292 'bond_master_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
293 lambda n: _strlist_from_xml(n, 'bond_master_of', 'slave')),
294 'bond_slave_of': (_str_to_xml,_str_from_xml),
295 'VLAN': (_str_to_xml,_str_from_xml),
296 'VLAN_master_of': (_str_to_xml,_str_from_xml),
297 'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
298 lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
299 'tunnel_access_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_access_PIF_of', 'pif', v),
300 lambda n: _strlist_from_xml(n, 'tunnel_access_PIF_of', 'pif')),
301 'tunnel_transport_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_transport_PIF_of', 'pif', v),
302 lambda n: _strlist_from_xml(n, 'tunnel_transport_PIF_of', 'pif')),
303 'ip_configuration_mode': (_str_to_xml,_str_from_xml),
304 'IP': (_str_to_xml,_str_from_xml),
305 'netmask': (_str_to_xml,_str_from_xml),
306 'gateway': (_str_to_xml,_str_from_xml),
307 'DNS': (_str_to_xml,_str_from_xml),
308 'MAC': (_str_to_xml,_str_from_xml),
309 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _PIF_OTHERCONFIG_ATTRS),
310 lambda n: _otherconfig_from_xml(n, _PIF_OTHERCONFIG_ATTRS)),
312 # Special case: We write the current value
313 # PIF.currently-attached to the cache but since it will
314 # not be valid when we come to use the cache later
315 # (i.e. after a reboot) we always read it as False.
316 'currently_attached': (_bool_to_xml, lambda n: False),
319 _VLAN_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
320 'tagged_PIF': (_str_to_xml,_str_from_xml),
321 'untagged_PIF': (_str_to_xml,_str_from_xml),
324 _TUNNEL_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
325 'access_PIF': (_str_to_xml,_str_from_xml),
326 'transport_PIF': (_str_to_xml,_str_from_xml),
328 _BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
329 'master': (_str_to_xml,_str_from_xml),
330 'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
331 lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
334 _NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes', 'vswitch-controller-fail-mode' ] + _ETHTOOL_OTHERCONFIG_ATTRS
336 _NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
337 'bridge': (_str_to_xml,_str_from_xml),
338 'MTU': (_str_to_xml,_str_from_xml),
339 'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
340 lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
341 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
342 lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
345 _POOL_OTHERCONFIG_ATTRS = ['vswitch-controller-fail-mode']
347 _POOL_ATTRS = { 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _POOL_OTHERCONFIG_ATTRS),
348 lambda n: _otherconfig_from_xml(n, _POOL_OTHERCONFIG_ATTRS)),
352 # Database Cache object
358 assert(_db is not None)
361 def db_init_from_cache(cache):
364 _db = DatabaseCache(cache_file=cache)
366 def db_init_from_xenapi(session):
369 _db = DatabaseCache(session_ref=session)
371 class DatabaseCache(object):
372 def __read_xensource_inventory(self):
373 filename = root_prefix() + "/etc/xensource-inventory"
374 f = open(filename, "r")
375 lines = [x.strip("\n") for x in f.readlines()]
378 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
379 defs = [ (a, b.strip("'")) for (a,b) in defs ]
383 def __pif_on_host(self,pif):
384 return self.__pifs.has_key(pif)
386 def __get_pif_records_from_xapi(self, session, host):
388 for (p,rec) in session.xenapi.PIF.get_all_records().items():
389 if rec['host'] != host:
393 if f in [ "tunnel_access_PIF_of", "tunnel_transport_PIF_of" ] and f not in rec:
394 # XenServer 5.5 network records did not have
395 # these fields, so allow them to be missing.
398 self.__pifs[p][f] = rec[f]
399 self.__pifs[p]['other_config'] = {}
400 for f in _PIF_OTHERCONFIG_ATTRS:
401 if not rec['other_config'].has_key(f): continue
402 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
404 def __get_vlan_records_from_xapi(self, session):
406 for v in session.xenapi.VLAN.get_all():
407 rec = session.xenapi.VLAN.get_record(v)
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 in session.xenapi.Bond.get_all():
427 rec = session.xenapi.Bond.get_record(b)
428 if not self.__pif_on_host(rec['master']):
431 for f in _BOND_ATTRS:
432 self.__bonds[b][f] = rec[f]
434 def __get_network_records_from_xapi(self, session):
436 for n in session.xenapi.network.get_all():
437 rec = session.xenapi.network.get_record(n)
438 self.__networks[n] = {}
439 for f in _NETWORK_ATTRS:
441 # drop PIFs on other hosts
442 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
443 elif f == "MTU" and f not in rec:
444 # XenServer 5.5 network records did not have an
445 # MTU field, so allow this to be missing.
448 self.__networks[n][f] = rec[f]
449 self.__networks[n]['other_config'] = {}
450 for f in _NETWORK_OTHERCONFIG_ATTRS:
451 if not rec['other_config'].has_key(f): continue
452 self.__networks[n]['other_config'][f] = rec['other_config'][f]
454 def __get_pool_records_from_xapi(self, session):
456 for p in session.xenapi.pool.get_all():
457 rec = session.xenapi.pool.get_record(p)
461 for f in _POOL_ATTRS:
462 self.__pools[p][f] = rec[f]
464 for f in _POOL_OTHERCONFIG_ATTRS:
465 if rec['other_config'].has_key(f):
466 self.__pools[p]['other_config'][f] = rec['other_config'][f]
468 def __to_xml(self, xml, parent, key, ref, rec, attrs):
469 """Encode a database object as XML"""
470 e = xml.createElement(key)
471 parent.appendChild(e)
473 e.setAttribute('ref', ref)
475 for n,v in rec.items():
480 raise Error("Unknown attribute %s" % n)
481 def __from_xml(self, e, attrs):
482 """Decode a database object from XML"""
483 ref = e.attributes['ref'].value
485 for n in e.childNodes:
486 if n.nodeName in attrs:
487 _,h = attrs[n.nodeName]
488 rec[n.nodeName] = h(n)
491 def __init__(self, session_ref=None, cache_file=None):
492 if session_ref and cache_file:
493 raise Error("can't specify session reference and cache file")
494 if cache_file == None:
496 session = XenAPI.xapi_local()
499 log("No session ref given on command line, logging in.")
500 session.xenapi.login_with_password("root", "")
502 session._session = session_ref
506 inventory = self.__read_xensource_inventory()
507 assert(inventory.has_key('INSTALLATION_UUID'))
508 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
510 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
512 self.__get_pif_records_from_xapi(session, host)
515 self.__get_tunnel_records_from_xapi(session)
516 except XenAPI.Failure, e:
517 error,details = e.details
518 if error == "MESSAGE_METHOD_UNKNOWN" and details == "tunnel.get_all":
521 self.__get_pool_records_from_xapi(session)
522 self.__get_vlan_records_from_xapi(session)
523 self.__get_bond_records_from_xapi(session)
524 self.__get_network_records_from_xapi(session)
527 session.xenapi.session.logout()
529 log("Loading xapi database cache from %s" % cache_file)
531 xml = parseXML(root_prefix() + cache_file)
540 assert(len(xml.childNodes) == 1)
541 toplevel = xml.childNodes[0]
543 assert(toplevel.nodeName == "xenserver-network-configuration")
545 for n in toplevel.childNodes:
546 if n.nodeName == "#text":
548 elif n.nodeName == _PIF_XML_TAG:
549 (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
550 self.__pifs[ref] = rec
551 elif n.nodeName == _BOND_XML_TAG:
552 (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
553 self.__bonds[ref] = rec
554 elif n.nodeName == _VLAN_XML_TAG:
555 (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
556 self.__vlans[ref] = rec
557 elif n.nodeName == _TUNNEL_XML_TAG:
558 (ref,rec) = self.__from_xml(n, _TUNNEL_ATTRS)
559 self.__vlans[ref] = rec
560 elif n.nodeName == _NETWORK_XML_TAG:
561 (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
562 self.__networks[ref] = rec
563 elif n.nodeName == _POOL_XML_TAG:
564 (ref,rec) = self.__from_xml(n, _POOL_ATTRS)
565 self.__pools[ref] = rec
567 raise Error("Unknown XML element %s" % n.nodeName)
569 def save(self, cache_file):
571 xml = getDOMImplementation().createDocument(
572 None, "xenserver-network-configuration", None)
573 for (ref,rec) in self.__pifs.items():
574 self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
575 for (ref,rec) in self.__bonds.items():
576 self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
577 for (ref,rec) in self.__vlans.items():
578 self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
579 for (ref,rec) in self.__tunnels.items():
580 self.__to_xml(xml, xml.documentElement, _TUNNEL_XML_TAG, ref, rec, _TUNNEL_ATTRS)
581 for (ref,rec) in self.__networks.items():
582 self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
584 for (ref,rec) in self.__pools.items():
585 self.__to_xml(xml, xml.documentElement, _POOL_XML_TAG, ref, rec, _POOL_ATTRS)
587 f = open(cache_file, 'w')
588 f.write(xml.toprettyxml())
591 def get_pif_by_uuid(self, uuid):
592 pifs = map(lambda (ref,rec): ref,
593 filter(lambda (ref,rec): uuid == rec['uuid'],
594 self.__pifs.items()))
596 raise Error("Unknown PIF \"%s\"" % uuid)
598 raise Error("Non-unique PIF \"%s\"" % uuid)
602 def get_pifs_by_device(self, device):
603 return map(lambda (ref,rec): ref,
604 filter(lambda (ref,rec): rec['device'] == device,
605 self.__pifs.items()))
607 def get_networks_with_bridge(self, bridge):
608 return map(lambda (ref,rec): ref,
609 filter(lambda (ref,rec): rec['bridge'] == bridge,
610 self.__networks.items()))
612 def get_network_by_bridge(self, bridge):
613 #Assumes one network has bridge.
615 return self.get_networks_with_bridge(bridge)[0]
619 def get_pif_by_bridge(self, bridge):
620 networks = self.get_networks_with_bridge(bridge)
622 if len(networks) == 0:
623 raise Error("No matching network \"%s\"" % bridge)
626 for network in networks:
627 nwrec = self.get_network_record(network)
628 for pif in nwrec['PIFs']:
629 pifrec = self.get_pif_record(pif)
631 raise Error("Multiple PIFs on host for network %s" % (bridge))
634 raise Error("No PIF on host for network %s" % (bridge))
637 def get_pif_record(self, pif):
638 if self.__pifs.has_key(pif):
639 return self.__pifs[pif]
640 raise Error("Unknown PIF \"%s\"" % pif)
641 def get_all_pifs(self):
643 def pif_exists(self, pif):
644 return self.__pifs.has_key(pif)
646 def get_management_pif(self):
647 """ Returns the management pif on host
649 all = self.get_all_pifs()
651 pifrec = self.get_pif_record(pif)
652 if pifrec['management']: return pif
655 def get_network_record(self, network):
656 if self.__networks.has_key(network):
657 return self.__networks[network]
658 raise Error("Unknown network \"%s\"" % network)
660 def get_bond_record(self, bond):
661 if self.__bonds.has_key(bond):
662 return self.__bonds[bond]
666 def get_vlan_record(self, vlan):
667 if self.__vlans.has_key(vlan):
668 return self.__vlans[vlan]
672 def get_pool_record(self):
673 if len(self.__pools) > 0:
674 return self.__pools.values()[0]
680 def ethtool_settings(oc):
682 if oc.has_key('ethtool-speed'):
683 val = oc['ethtool-speed']
684 if val in ["10", "100", "1000"]:
685 settings += ['speed', val]
687 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
688 if oc.has_key('ethtool-duplex'):
689 val = oc['ethtool-duplex']
690 if val in ["10", "100", "1000"]:
691 settings += ['duplex', 'val']
693 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
694 if oc.has_key('ethtool-autoneg'):
695 val = oc['ethtool-autoneg']
696 if val in ["true", "on"]:
697 settings += ['autoneg', 'on']
698 elif val in ["false", "off"]:
699 settings += ['autoneg', 'off']
701 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
703 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
704 if oc.has_key("ethtool-" + opt):
705 val = oc["ethtool-" + opt]
706 if val in ["true", "on"]:
707 offload += [opt, 'on']
708 elif val in ["false", "off"]:
709 offload += [opt, 'off']
711 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
712 return settings,offload
714 # By default the MTU is taken from the Network.MTU setting for VIF,
715 # PIF and Bridge. However it is possible to override this by using
716 # {VIF,PIF,Network}.other-config:mtu.
718 # type parameter is a string describing the object that the oc parameter
719 # is from. e.g. "PIF", "Network"
720 def mtu_setting(nw, type, oc):
723 nwrec = db().get_network_record(nw)
724 if nwrec.has_key('MTU'):
729 if oc.has_key('mtu'):
730 log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
731 (nwrec['bridge'], type, mtu))
736 int(mtu) # Check that the value is an integer
738 except ValueError, x:
739 log("Invalid value for mtu = %s" % mtu)
744 # IP Network Devices -- network devices with IP configuration
746 def pif_ipdev_name(pif):
747 """Return the ipdev name associated with pif"""
748 pifrec = db().get_pif_record(pif)
749 nwrec = db().get_network_record(pifrec['network'])
752 # TODO: sanity check that nwrec['bridgeless'] != 'true'
753 return nwrec['bridge']
755 # TODO: sanity check that nwrec['bridgeless'] == 'true'
756 return pif_netdev_name(pif)
759 # Bare Network Devices -- network devices without IP configuration
762 def netdev_exists(netdev):
763 return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
765 def pif_netdev_name(pif):
766 """Get the netdev name for a PIF."""
768 pifrec = db().get_pif_record(pif)
771 return "%(device)s.%(VLAN)s" % pifrec
773 return pifrec['device']
779 def pif_is_bridged(pif):
780 pifrec = db().get_pif_record(pif)
781 nwrec = db().get_network_record(pifrec['network'])
784 # TODO: sanity check that nwrec['bridgeless'] != 'true'
787 # TODO: sanity check that nwrec['bridgeless'] == 'true'
790 def pif_bridge_name(pif):
791 """Return the bridge name of a pif.
793 PIF must be a bridged PIF."""
794 pifrec = db().get_pif_record(pif)
796 nwrec = db().get_network_record(pifrec['network'])
799 return nwrec['bridge']
801 raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
806 def pif_is_bond(pif):
807 pifrec = db().get_pif_record(pif)
809 return len(pifrec['bond_master_of']) > 0
811 def pif_get_bond_masters(pif):
812 """Returns a list of PIFs which are bond masters of this PIF"""
814 pifrec = db().get_pif_record(pif)
816 bso = pifrec['bond_slave_of']
818 # bond-slave-of is currently a single reference but in principle a
819 # PIF could be a member of several bonds which are not
820 # concurrently attached. Be robust to this possibility.
821 if not bso or bso == "OpaqueRef:NULL":
823 elif not type(bso) == list:
826 bondrecs = [db().get_bond_record(bond) for bond in bso]
827 bondrecs = [rec for rec in bondrecs if rec]
829 return [bond['master'] for bond in bondrecs]
831 def pif_get_bond_slaves(pif):
832 """Returns a list of PIFs which make up the given bonded pif."""
834 pifrec = db().get_pif_record(pif)
836 bmo = pifrec['bond_master_of']
838 raise Error("Bond-master-of contains too many elements")
843 bondrec = db().get_bond_record(bmo[0])
845 raise Error("No bond record for bond master PIF")
847 return bondrec['slaves']
853 def pif_is_vlan(pif):
854 return db().get_pif_record(pif)['VLAN'] != '-1'
856 def pif_get_vlan_slave(pif):
857 """Find the PIF which is the VLAN slave of pif.
859 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
861 pifrec = db().get_pif_record(pif)
863 vlan = pifrec['VLAN_master_of']
864 if not vlan or vlan == "OpaqueRef:NULL":
865 raise Error("PIF is not a VLAN master")
867 vlanrec = db().get_vlan_record(vlan)
869 raise Error("No VLAN record found for PIF")
871 return vlanrec['tagged_PIF']
873 def pif_get_vlan_masters(pif):
874 """Returns a list of PIFs which are VLANs on top of the given pif."""
876 pifrec = db().get_pif_record(pif)
877 vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
878 return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
883 def pif_is_tunnel(pif):
884 rec = db().get_pif_record(pif)
885 return rec.has_key('tunnel_access_PIF_of') and len(rec['tunnel_access_PIF_of']) > 0
888 # Datapath base class
891 class Datapath(object):
892 """Object encapsulating the actions necessary to (de)configure the
893 datapath for a given PIF. Does not include configuration of the
894 IP address on the ipdev.
897 def __init__(self, pif):
902 """Class method called when write action is called. Can be used
903 to update any backend specific configuration."""
906 def configure_ipdev(self, cfg):
907 """Write ifcfg TYPE field for an IPdev, plus any type specific
910 raise NotImplementedError
912 def preconfigure(self, parent):
913 """Prepare datapath configuration for PIF, but do not actually
916 Any configuration files should be attached to parent.
918 raise NotImplementedError
920 def bring_down_existing(self):
921 """Tear down any existing network device configuration which
922 needs to be undone in order to bring this PIF up.
924 raise NotImplementedError
927 """Apply the configuration prepared in the preconfigure stage.
929 Should assume any configuration files changed attached in
930 the preconfigure stage are applied and bring up the
931 necesary devices to provide the datapath for the
934 Should not bring up the IPdev.
936 raise NotImplementedError
939 """Called after the IPdev has been brought up.
941 Should do any final setup, including reinstating any
942 devices which were taken down in the bring_down_existing
945 raise NotImplementedError
947 def bring_down(self):
948 """Tear down and deconfigure the datapath. Should assume the
949 IPdev has already been brought down.
951 raise NotImplementedError
953 def DatapathFactory():
954 # XXX Need a datapath object for bridgeless PIFs
957 network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
958 network_backend = network_conf.readline().strip()
961 raise Error("failed to determine network backend:" + e)
963 if network_backend == "bridge":
964 from InterfaceReconfigureBridge import DatapathBridge
965 return DatapathBridge
966 elif network_backend in ["openvswitch", "vswitch"]:
967 from InterfaceReconfigureVswitch import DatapathVswitch
968 return DatapathVswitch
970 raise Error("unknown network backend %s" % network_backend)