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',
337 'vswitch-controller-fail-mode',
338 'vswitch-disable-in-band' ] \
339 + _ETHTOOL_OTHERCONFIG_ATTRS
341 _NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
342 'bridge': (_str_to_xml,_str_from_xml),
343 'MTU': (_str_to_xml,_str_from_xml),
344 'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
345 lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
346 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
347 lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
350 _POOL_OTHERCONFIG_ATTRS = ['vswitch-controller-fail-mode']
352 _POOL_ATTRS = { 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _POOL_OTHERCONFIG_ATTRS),
353 lambda n: _otherconfig_from_xml(n, _POOL_OTHERCONFIG_ATTRS)),
357 # Database Cache object
363 assert(_db is not None)
366 def db_init_from_cache(cache):
369 _db = DatabaseCache(cache_file=cache)
371 def db_init_from_xenapi(session):
374 _db = DatabaseCache(session_ref=session)
376 class DatabaseCache(object):
377 def __read_xensource_inventory(self):
378 filename = root_prefix() + "/etc/xensource-inventory"
379 f = open(filename, "r")
380 lines = [x.strip("\n") for x in f.readlines()]
383 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
384 defs = [ (a, b.strip("'")) for (a,b) in defs ]
388 def __pif_on_host(self,pif):
389 return self.__pifs.has_key(pif)
391 def __get_pif_records_from_xapi(self, session, host):
393 for (p,rec) in session.xenapi.PIF.get_all_records().items():
394 if rec['host'] != host:
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,rec) in session.xenapi.VLAN.get_all_records().items():
407 if not self.__pif_on_host(rec['untagged_PIF']):
410 for f in _VLAN_ATTRS:
411 self.__vlans[v][f] = rec[f]
413 def __get_tunnel_records_from_xapi(self, session):
415 for t in session.xenapi.tunnel.get_all():
416 rec = session.xenapi.tunnel.get_record(t)
417 if not self.__pif_on_host(rec['transport_PIF']):
419 self.__tunnels[t] = {}
420 for f in _TUNNEL_ATTRS:
421 self.__tunnels[t][f] = rec[f]
423 def __get_bond_records_from_xapi(self, session):
425 for (b,rec) in session.xenapi.Bond.get_all_records().items():
426 if not self.__pif_on_host(rec['master']):
429 for f in _BOND_ATTRS:
430 self.__bonds[b][f] = rec[f]
432 def __get_network_records_from_xapi(self, session):
434 for (n,rec) in session.xenapi.network.get_all_records().items():
435 self.__networks[n] = {}
436 for f in _NETWORK_ATTRS:
438 # drop PIFs on other hosts
439 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
440 elif f == "MTU" and f not in rec:
441 # XenServer 5.5 network records did not have an
442 # MTU field, so allow this to be missing.
445 self.__networks[n][f] = rec[f]
446 self.__networks[n]['other_config'] = {}
447 for f in _NETWORK_OTHERCONFIG_ATTRS:
448 if not rec['other_config'].has_key(f): continue
449 self.__networks[n]['other_config'][f] = rec['other_config'][f]
451 def __get_pool_records_from_xapi(self, session):
453 for p in session.xenapi.pool.get_all():
454 rec = session.xenapi.pool.get_record(p)
458 for f in _POOL_ATTRS:
459 self.__pools[p][f] = rec[f]
461 for f in _POOL_OTHERCONFIG_ATTRS:
462 if rec['other_config'].has_key(f):
463 self.__pools[p]['other_config'][f] = rec['other_config'][f]
465 def __to_xml(self, xml, parent, key, ref, rec, attrs):
466 """Encode a database object as XML"""
467 e = xml.createElement(key)
468 parent.appendChild(e)
470 e.setAttribute('ref', ref)
472 for n,v in rec.items():
477 raise Error("Unknown attribute %s" % n)
478 def __from_xml(self, e, attrs):
479 """Decode a database object from XML"""
480 ref = e.attributes['ref'].value
482 for n in e.childNodes:
483 if n.nodeName in attrs:
484 _,h = attrs[n.nodeName]
485 rec[n.nodeName] = h(n)
488 def __init__(self, session_ref=None, cache_file=None):
489 if session_ref and cache_file:
490 raise Error("can't specify session reference and cache file")
491 if cache_file == None:
493 session = XenAPI.xapi_local()
496 log("No session ref given on command line, logging in.")
497 session.xenapi.login_with_password("root", "")
499 session._session = session_ref
503 inventory = self.__read_xensource_inventory()
504 assert(inventory.has_key('INSTALLATION_UUID'))
505 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
507 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
509 self.__get_pif_records_from_xapi(session, host)
511 self.__get_tunnel_records_from_xapi(session)
512 self.__get_pool_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 f = open(cache_file, 'w')
579 f.write(xml.toprettyxml())
582 def get_pif_by_uuid(self, uuid):
583 pifs = map(lambda (ref,rec): ref,
584 filter(lambda (ref,rec): uuid == rec['uuid'],
585 self.__pifs.items()))
587 raise Error("Unknown PIF \"%s\"" % uuid)
589 raise Error("Non-unique PIF \"%s\"" % uuid)
593 def get_pifs_by_device(self, device):
594 return map(lambda (ref,rec): ref,
595 filter(lambda (ref,rec): rec['device'] == device,
596 self.__pifs.items()))
598 def get_networks_with_bridge(self, bridge):
599 return map(lambda (ref,rec): ref,
600 filter(lambda (ref,rec): rec['bridge'] == bridge,
601 self.__networks.items()))
603 def get_network_by_bridge(self, bridge):
604 #Assumes one network has bridge.
606 return self.get_networks_with_bridge(bridge)[0]
610 def get_pif_by_bridge(self, bridge):
611 networks = self.get_networks_with_bridge(bridge)
613 if len(networks) == 0:
614 raise Error("No matching network \"%s\"" % bridge)
617 for network in networks:
618 nwrec = self.get_network_record(network)
619 for pif in nwrec['PIFs']:
620 pifrec = self.get_pif_record(pif)
622 raise Error("Multiple PIFs on host for network %s" % (bridge))
625 raise Error("No PIF on host for network %s" % (bridge))
628 def get_pif_record(self, pif):
629 if self.__pifs.has_key(pif):
630 return self.__pifs[pif]
631 raise Error("Unknown PIF \"%s\"" % pif)
632 def get_all_pifs(self):
634 def pif_exists(self, pif):
635 return self.__pifs.has_key(pif)
637 def get_management_pif(self):
638 """ Returns the management pif on host
640 all = self.get_all_pifs()
642 pifrec = self.get_pif_record(pif)
643 if pifrec['management']: return pif
646 def get_network_record(self, network):
647 if self.__networks.has_key(network):
648 return self.__networks[network]
649 raise Error("Unknown network \"%s\"" % network)
651 def get_bond_record(self, bond):
652 if self.__bonds.has_key(bond):
653 return self.__bonds[bond]
657 def get_vlan_record(self, vlan):
658 if self.__vlans.has_key(vlan):
659 return self.__vlans[vlan]
663 def get_pool_record(self):
664 if len(self.__pools) > 0:
665 return self.__pools.values()[0]
670 PIF_OTHERCONFIG_DEFAULTS = {'gro': 'off', 'lro': 'off'}
672 def ethtool_settings(oc, defaults = {}):
674 if oc.has_key('ethtool-speed'):
675 val = oc['ethtool-speed']
676 if val in ["10", "100", "1000"]:
677 settings += ['speed', val]
679 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
680 if oc.has_key('ethtool-duplex'):
681 val = oc['ethtool-duplex']
682 if val in ["half", "full"]:
683 settings += ['duplex', val]
685 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
686 if oc.has_key('ethtool-autoneg'):
687 val = oc['ethtool-autoneg']
688 if val in ["true", "on"]:
689 settings += ['autoneg', 'on']
690 elif val in ["false", "off"]:
691 settings += ['autoneg', 'off']
693 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
695 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro"):
696 if oc.has_key("ethtool-" + opt):
697 val = oc["ethtool-" + opt]
698 if val in ["true", "on"]:
699 offload += [opt, 'on']
700 elif val in ["false", "off"]:
701 offload += [opt, 'off']
703 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
704 elif opt in defaults:
705 offload += [opt, defaults[opt]]
706 return settings,offload
708 # By default the MTU is taken from the Network.MTU setting for VIF,
709 # PIF and Bridge. However it is possible to override this by using
710 # {VIF,PIF,Network}.other-config:mtu.
712 # type parameter is a string describing the object that the oc parameter
713 # is from. e.g. "PIF", "Network"
714 def mtu_setting(nw, type, oc):
717 nwrec = db().get_network_record(nw)
718 if nwrec.has_key('MTU'):
723 if oc.has_key('mtu'):
724 log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
725 (nwrec['bridge'], type, mtu))
730 int(mtu) # Check that the value is an integer
732 except ValueError, x:
733 log("Invalid value for mtu = %s" % mtu)
738 # IP Network Devices -- network devices with IP configuration
740 def pif_ipdev_name(pif):
741 """Return the ipdev name associated with pif"""
742 pifrec = db().get_pif_record(pif)
743 nwrec = db().get_network_record(pifrec['network'])
746 # TODO: sanity check that nwrec['bridgeless'] != 'true'
747 return nwrec['bridge']
749 # TODO: sanity check that nwrec['bridgeless'] == 'true'
750 return pif_netdev_name(pif)
753 # Bare Network Devices -- network devices without IP configuration
756 def netdev_exists(netdev):
757 return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
759 def pif_netdev_name(pif):
760 """Get the netdev name for a PIF."""
762 pifrec = db().get_pif_record(pif)
765 return "%(device)s.%(VLAN)s" % pifrec
767 return pifrec['device']
773 def pif_is_bridged(pif):
774 pifrec = db().get_pif_record(pif)
775 nwrec = db().get_network_record(pifrec['network'])
778 # TODO: sanity check that nwrec['bridgeless'] != 'true'
781 # TODO: sanity check that nwrec['bridgeless'] == 'true'
784 def pif_bridge_name(pif):
785 """Return the bridge name of a pif.
787 PIF must be a bridged PIF."""
788 pifrec = db().get_pif_record(pif)
790 nwrec = db().get_network_record(pifrec['network'])
793 return nwrec['bridge']
795 raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
800 def pif_is_bond(pif):
801 pifrec = db().get_pif_record(pif)
803 return len(pifrec['bond_master_of']) > 0
805 def pif_get_bond_masters(pif):
806 """Returns a list of PIFs which are bond masters of this PIF"""
808 pifrec = db().get_pif_record(pif)
810 bso = pifrec['bond_slave_of']
812 # bond-slave-of is currently a single reference but in principle a
813 # PIF could be a member of several bonds which are not
814 # concurrently attached. Be robust to this possibility.
815 if not bso or bso == "OpaqueRef:NULL":
817 elif not type(bso) == list:
820 bondrecs = [db().get_bond_record(bond) for bond in bso]
821 bondrecs = [rec for rec in bondrecs if rec]
823 return [bond['master'] for bond in bondrecs]
825 def pif_get_bond_slaves(pif):
826 """Returns a list of PIFs which make up the given bonded pif."""
828 pifrec = db().get_pif_record(pif)
830 bmo = pifrec['bond_master_of']
832 raise Error("Bond-master-of contains too many elements")
837 bondrec = db().get_bond_record(bmo[0])
839 raise Error("No bond record for bond master PIF")
841 return bondrec['slaves']
847 def pif_is_vlan(pif):
848 return db().get_pif_record(pif)['VLAN'] != '-1'
850 def pif_get_vlan_slave(pif):
851 """Find the PIF which is the VLAN slave of pif.
853 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
855 pifrec = db().get_pif_record(pif)
857 vlan = pifrec['VLAN_master_of']
858 if not vlan or vlan == "OpaqueRef:NULL":
859 raise Error("PIF is not a VLAN master")
861 vlanrec = db().get_vlan_record(vlan)
863 raise Error("No VLAN record found for PIF")
865 return vlanrec['tagged_PIF']
867 def pif_get_vlan_masters(pif):
868 """Returns a list of PIFs which are VLANs on top of the given pif."""
870 pifrec = db().get_pif_record(pif)
871 vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
872 return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
877 def pif_is_tunnel(pif):
878 return len(db().get_pif_record(pif)['tunnel_access_PIF_of']) > 0
881 # Datapath base class
884 class Datapath(object):
885 """Object encapsulating the actions necessary to (de)configure the
886 datapath for a given PIF. Does not include configuration of the
887 IP address on the ipdev.
890 def __init__(self, pif):
895 """Class method called when write action is called. Can be used
896 to update any backend specific configuration."""
899 def configure_ipdev(self, cfg):
900 """Write ifcfg TYPE field for an IPdev, plus any type specific
903 raise NotImplementedError
905 def preconfigure(self, parent):
906 """Prepare datapath configuration for PIF, but do not actually
909 Any configuration files should be attached to parent.
911 raise NotImplementedError
913 def bring_down_existing(self):
914 """Tear down any existing network device configuration which
915 needs to be undone in order to bring this PIF up.
917 raise NotImplementedError
920 """Apply the configuration prepared in the preconfigure stage.
922 Should assume any configuration files changed attached in
923 the preconfigure stage are applied and bring up the
924 necesary devices to provide the datapath for the
927 Should not bring up the IPdev.
929 raise NotImplementedError
932 """Called after the IPdev has been brought up.
934 Should do any final setup, including reinstating any
935 devices which were taken down in the bring_down_existing
938 raise NotImplementedError
940 def bring_down(self):
941 """Tear down and deconfigure the datapath. Should assume the
942 IPdev has already been brought down.
944 raise NotImplementedError
946 def DatapathFactory():
947 # XXX Need a datapath object for bridgeless PIFs
950 network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
951 network_backend = network_conf.readline().strip()
954 raise Error("failed to determine network backend:" + e)
956 if network_backend == "bridge":
957 from InterfaceReconfigureBridge import DatapathBridge
958 return DatapathBridge
959 elif network_backend in ["openvswitch", "vswitch"]:
960 from InterfaceReconfigureVswitch import DatapathVswitch
961 return DatapathVswitch
963 raise Error("unknown network backend %s" % network_backend)