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 [ 'vlan-bug-workaround' ] + \
288 _ETHTOOL_OTHERCONFIG_ATTRS
290 _PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
291 'management': (_bool_to_xml,_bool_from_xml),
292 'network': (_str_to_xml,_str_from_xml),
293 'device': (_str_to_xml,_str_from_xml),
294 'bond_master_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
295 lambda n: _strlist_from_xml(n, 'bond_master_of', 'slave')),
296 'bond_slave_of': (_str_to_xml,_str_from_xml),
297 'VLAN': (_str_to_xml,_str_from_xml),
298 'VLAN_master_of': (_str_to_xml,_str_from_xml),
299 'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
300 lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
301 'tunnel_access_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_access_PIF_of', 'pif', v),
302 lambda n: _strlist_from_xml(n, 'tunnel_access_PIF_of', 'pif')),
303 'tunnel_transport_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_transport_PIF_of', 'pif', v),
304 lambda n: _strlist_from_xml(n, 'tunnel_transport_PIF_of', 'pif')),
305 'ip_configuration_mode': (_str_to_xml,_str_from_xml),
306 'IP': (_str_to_xml,_str_from_xml),
307 'netmask': (_str_to_xml,_str_from_xml),
308 'gateway': (_str_to_xml,_str_from_xml),
309 'DNS': (_str_to_xml,_str_from_xml),
310 'MAC': (_str_to_xml,_str_from_xml),
311 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _PIF_OTHERCONFIG_ATTRS),
312 lambda n: _otherconfig_from_xml(n, _PIF_OTHERCONFIG_ATTRS)),
314 # Special case: We write the current value
315 # PIF.currently-attached to the cache but since it will
316 # not be valid when we come to use the cache later
317 # (i.e. after a reboot) we always read it as False.
318 'currently_attached': (_bool_to_xml, lambda n: False),
321 _VLAN_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
322 'tagged_PIF': (_str_to_xml,_str_from_xml),
323 'untagged_PIF': (_str_to_xml,_str_from_xml),
326 _TUNNEL_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
327 'access_PIF': (_str_to_xml,_str_from_xml),
328 'transport_PIF': (_str_to_xml,_str_from_xml),
330 _BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
331 'master': (_str_to_xml,_str_from_xml),
332 'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
333 lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
336 _NETWORK_OTHERCONFIG_ATTRS = [ 'mtu',
338 'vswitch-controller-fail-mode',
339 'vswitch-disable-in-band' ] \
340 + _ETHTOOL_OTHERCONFIG_ATTRS
342 _NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
343 'bridge': (_str_to_xml,_str_from_xml),
344 'MTU': (_str_to_xml,_str_from_xml),
345 'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
346 lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
347 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
348 lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
351 _POOL_OTHERCONFIG_ATTRS = ['vswitch-controller-fail-mode']
353 _POOL_ATTRS = { 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _POOL_OTHERCONFIG_ATTRS),
354 lambda n: _otherconfig_from_xml(n, _POOL_OTHERCONFIG_ATTRS)),
358 # Database Cache object
364 assert(_db is not None)
367 def db_init_from_cache(cache):
370 _db = DatabaseCache(cache_file=cache)
372 def db_init_from_xenapi(session):
375 _db = DatabaseCache(session_ref=session)
377 class DatabaseCache(object):
378 def __read_xensource_inventory(self):
379 filename = root_prefix() + "/etc/xensource-inventory"
380 f = open(filename, "r")
381 lines = [x.strip("\n") for x in f.readlines()]
384 defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
385 defs = [ (a, b.strip("'")) for (a,b) in defs ]
389 def __pif_on_host(self,pif):
390 return self.__pifs.has_key(pif)
392 def __get_pif_records_from_xapi(self, session, host):
394 for (p,rec) in session.xenapi.PIF.get_all_records().items():
395 if rec['host'] != host:
399 self.__pifs[p][f] = rec[f]
400 self.__pifs[p]['other_config'] = {}
401 for f in _PIF_OTHERCONFIG_ATTRS:
402 if 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,rec) in session.xenapi.VLAN.get_all_records().items():
408 if not self.__pif_on_host(rec['untagged_PIF']):
411 for f in _VLAN_ATTRS:
412 self.__vlans[v][f] = rec[f]
414 def __get_tunnel_records_from_xapi(self, session):
416 for t in session.xenapi.tunnel.get_all():
417 rec = session.xenapi.tunnel.get_record(t)
418 if not self.__pif_on_host(rec['transport_PIF']):
420 self.__tunnels[t] = {}
421 for f in _TUNNEL_ATTRS:
422 self.__tunnels[t][f] = rec[f]
424 def __get_bond_records_from_xapi(self, session):
426 for (b,rec) in session.xenapi.Bond.get_all_records().items():
427 if not self.__pif_on_host(rec['master']):
430 for f in _BOND_ATTRS:
431 self.__bonds[b][f] = rec[f]
433 def __get_network_records_from_xapi(self, session):
435 for (n,rec) in session.xenapi.network.get_all_records().items():
436 self.__networks[n] = {}
437 for f in _NETWORK_ATTRS:
439 # drop PIFs on other hosts
440 self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
441 elif f == "MTU" and f not in rec:
442 # XenServer 5.5 network records did not have an
443 # MTU field, so allow this to be missing.
446 self.__networks[n][f] = rec[f]
447 self.__networks[n]['other_config'] = {}
448 for f in _NETWORK_OTHERCONFIG_ATTRS:
449 if not rec['other_config'].has_key(f): continue
450 self.__networks[n]['other_config'][f] = rec['other_config'][f]
452 def __get_pool_records_from_xapi(self, session):
454 for p in session.xenapi.pool.get_all():
455 rec = session.xenapi.pool.get_record(p)
459 for f in _POOL_ATTRS:
460 self.__pools[p][f] = rec[f]
462 for f in _POOL_OTHERCONFIG_ATTRS:
463 if rec['other_config'].has_key(f):
464 self.__pools[p]['other_config'][f] = rec['other_config'][f]
466 def __to_xml(self, xml, parent, key, ref, rec, attrs):
467 """Encode a database object as XML"""
468 e = xml.createElement(key)
469 parent.appendChild(e)
471 e.setAttribute('ref', ref)
473 for n,v in rec.items():
478 raise Error("Unknown attribute %s" % n)
479 def __from_xml(self, e, attrs):
480 """Decode a database object from XML"""
481 ref = e.attributes['ref'].value
483 for n in e.childNodes:
484 if n.nodeName in attrs:
485 _,h = attrs[n.nodeName]
486 rec[n.nodeName] = h(n)
489 def __init__(self, session_ref=None, cache_file=None):
490 if session_ref and cache_file:
491 raise Error("can't specify session reference and cache file")
492 if cache_file == None:
494 session = XenAPI.xapi_local()
497 log("No session ref given on command line, logging in.")
498 session.xenapi.login_with_password("root", "")
500 session._session = session_ref
504 inventory = self.__read_xensource_inventory()
505 assert(inventory.has_key('INSTALLATION_UUID'))
506 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
508 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
510 self.__get_pif_records_from_xapi(session, host)
512 self.__get_tunnel_records_from_xapi(session)
513 self.__get_pool_records_from_xapi(session)
514 self.__get_vlan_records_from_xapi(session)
515 self.__get_bond_records_from_xapi(session)
516 self.__get_network_records_from_xapi(session)
519 session.xenapi.session.logout()
521 log("Loading xapi database cache from %s" % cache_file)
523 xml = parseXML(root_prefix() + cache_file)
532 assert(len(xml.childNodes) == 1)
533 toplevel = xml.childNodes[0]
535 assert(toplevel.nodeName == "xenserver-network-configuration")
537 for n in toplevel.childNodes:
538 if n.nodeName == "#text":
540 elif n.nodeName == _PIF_XML_TAG:
541 (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
542 self.__pifs[ref] = rec
543 elif n.nodeName == _BOND_XML_TAG:
544 (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
545 self.__bonds[ref] = rec
546 elif n.nodeName == _VLAN_XML_TAG:
547 (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
548 self.__vlans[ref] = rec
549 elif n.nodeName == _TUNNEL_XML_TAG:
550 (ref,rec) = self.__from_xml(n, _TUNNEL_ATTRS)
551 self.__vlans[ref] = rec
552 elif n.nodeName == _NETWORK_XML_TAG:
553 (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
554 self.__networks[ref] = rec
555 elif n.nodeName == _POOL_XML_TAG:
556 (ref,rec) = self.__from_xml(n, _POOL_ATTRS)
557 self.__pools[ref] = rec
559 raise Error("Unknown XML element %s" % n.nodeName)
561 def save(self, cache_file):
563 xml = getDOMImplementation().createDocument(
564 None, "xenserver-network-configuration", None)
565 for (ref,rec) in self.__pifs.items():
566 self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
567 for (ref,rec) in self.__bonds.items():
568 self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
569 for (ref,rec) in self.__vlans.items():
570 self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
571 for (ref,rec) in self.__tunnels.items():
572 self.__to_xml(xml, xml.documentElement, _TUNNEL_XML_TAG, ref, rec, _TUNNEL_ATTRS)
573 for (ref,rec) in self.__networks.items():
574 self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
576 for (ref,rec) in self.__pools.items():
577 self.__to_xml(xml, xml.documentElement, _POOL_XML_TAG, ref, rec, _POOL_ATTRS)
579 f = open(cache_file, 'w')
580 f.write(xml.toprettyxml())
583 def get_pif_by_uuid(self, uuid):
584 pifs = map(lambda (ref,rec): ref,
585 filter(lambda (ref,rec): uuid == rec['uuid'],
586 self.__pifs.items()))
588 raise Error("Unknown PIF \"%s\"" % uuid)
590 raise Error("Non-unique PIF \"%s\"" % uuid)
594 def get_pifs_by_device(self, device):
595 return map(lambda (ref,rec): ref,
596 filter(lambda (ref,rec): rec['device'] == device,
597 self.__pifs.items()))
599 def get_networks_with_bridge(self, bridge):
600 return map(lambda (ref,rec): ref,
601 filter(lambda (ref,rec): rec['bridge'] == bridge,
602 self.__networks.items()))
604 def get_network_by_bridge(self, bridge):
605 #Assumes one network has bridge.
607 return self.get_networks_with_bridge(bridge)[0]
611 def get_pif_by_bridge(self, bridge):
612 networks = self.get_networks_with_bridge(bridge)
614 if len(networks) == 0:
615 raise Error("No matching network \"%s\"" % bridge)
618 for network in networks:
619 nwrec = self.get_network_record(network)
620 for pif in nwrec['PIFs']:
621 pifrec = self.get_pif_record(pif)
623 raise Error("Multiple PIFs on host for network %s" % (bridge))
626 raise Error("No PIF on host for network %s" % (bridge))
629 def get_pif_record(self, pif):
630 if self.__pifs.has_key(pif):
631 return self.__pifs[pif]
632 raise Error("Unknown PIF \"%s\"" % pif)
633 def get_all_pifs(self):
635 def pif_exists(self, pif):
636 return self.__pifs.has_key(pif)
638 def get_management_pif(self):
639 """ Returns the management pif on host
641 all = self.get_all_pifs()
643 pifrec = self.get_pif_record(pif)
644 if pifrec['management']: return pif
647 def get_network_record(self, network):
648 if self.__networks.has_key(network):
649 return self.__networks[network]
650 raise Error("Unknown network \"%s\"" % network)
652 def get_bond_record(self, bond):
653 if self.__bonds.has_key(bond):
654 return self.__bonds[bond]
658 def get_vlan_record(self, vlan):
659 if self.__vlans.has_key(vlan):
660 return self.__vlans[vlan]
664 def get_pool_record(self):
665 if len(self.__pools) > 0:
666 return self.__pools.values()[0]
671 PIF_OTHERCONFIG_DEFAULTS = {'gro': 'off', 'lro': 'off'}
673 def ethtool_settings(oc, defaults = {}):
675 if oc.has_key('ethtool-speed'):
676 val = oc['ethtool-speed']
677 if val in ["10", "100", "1000"]:
678 settings += ['speed', val]
680 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
681 if oc.has_key('ethtool-duplex'):
682 val = oc['ethtool-duplex']
683 if val in ["half", "full"]:
684 settings += ['duplex', val]
686 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
687 if oc.has_key('ethtool-autoneg'):
688 val = oc['ethtool-autoneg']
689 if val in ["true", "on"]:
690 settings += ['autoneg', 'on']
691 elif val in ["false", "off"]:
692 settings += ['autoneg', 'off']
694 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
696 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro"):
697 if oc.has_key("ethtool-" + opt):
698 val = oc["ethtool-" + opt]
699 if val in ["true", "on"]:
700 offload += [opt, 'on']
701 elif val in ["false", "off"]:
702 offload += [opt, 'off']
704 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
705 elif opt in defaults:
706 offload += [opt, defaults[opt]]
707 return settings,offload
709 # By default the MTU is taken from the Network.MTU setting for VIF,
710 # PIF and Bridge. However it is possible to override this by using
711 # {VIF,PIF,Network}.other-config:mtu.
713 # type parameter is a string describing the object that the oc parameter
714 # is from. e.g. "PIF", "Network"
715 def mtu_setting(nw, type, oc):
718 nwrec = db().get_network_record(nw)
719 if nwrec.has_key('MTU'):
724 if oc.has_key('mtu'):
725 log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
726 (nwrec['bridge'], type, mtu))
731 int(mtu) # Check that the value is an integer
733 except ValueError, x:
734 log("Invalid value for mtu = %s" % mtu)
739 # IP Network Devices -- network devices with IP configuration
741 def pif_ipdev_name(pif):
742 """Return the ipdev name associated with pif"""
743 pifrec = db().get_pif_record(pif)
744 nwrec = db().get_network_record(pifrec['network'])
747 # TODO: sanity check that nwrec['bridgeless'] != 'true'
748 return nwrec['bridge']
750 # TODO: sanity check that nwrec['bridgeless'] == 'true'
751 return pif_netdev_name(pif)
754 # Bare Network Devices -- network devices without IP configuration
757 def netdev_exists(netdev):
758 return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
760 def pif_netdev_name(pif):
761 """Get the netdev name for a PIF."""
763 pifrec = db().get_pif_record(pif)
766 return "%(device)s.%(VLAN)s" % pifrec
768 return pifrec['device']
774 def pif_is_bridged(pif):
775 pifrec = db().get_pif_record(pif)
776 nwrec = db().get_network_record(pifrec['network'])
779 # TODO: sanity check that nwrec['bridgeless'] != 'true'
782 # TODO: sanity check that nwrec['bridgeless'] == 'true'
785 def pif_bridge_name(pif):
786 """Return the bridge name of a pif.
788 PIF must be a bridged PIF."""
789 pifrec = db().get_pif_record(pif)
791 nwrec = db().get_network_record(pifrec['network'])
794 return nwrec['bridge']
796 raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
801 def pif_is_bond(pif):
802 pifrec = db().get_pif_record(pif)
804 return len(pifrec['bond_master_of']) > 0
806 def pif_get_bond_masters(pif):
807 """Returns a list of PIFs which are bond masters of this PIF"""
809 pifrec = db().get_pif_record(pif)
811 bso = pifrec['bond_slave_of']
813 # bond-slave-of is currently a single reference but in principle a
814 # PIF could be a member of several bonds which are not
815 # concurrently attached. Be robust to this possibility.
816 if not bso or bso == "OpaqueRef:NULL":
818 elif not type(bso) == list:
821 bondrecs = [db().get_bond_record(bond) for bond in bso]
822 bondrecs = [rec for rec in bondrecs if rec]
824 return [bond['master'] for bond in bondrecs]
826 def pif_get_bond_slaves(pif):
827 """Returns a list of PIFs which make up the given bonded pif."""
829 pifrec = db().get_pif_record(pif)
831 bmo = pifrec['bond_master_of']
833 raise Error("Bond-master-of contains too many elements")
838 bondrec = db().get_bond_record(bmo[0])
840 raise Error("No bond record for bond master PIF")
842 return bondrec['slaves']
848 def pif_is_vlan(pif):
849 return db().get_pif_record(pif)['VLAN'] != '-1'
851 def pif_get_vlan_slave(pif):
852 """Find the PIF which is the VLAN slave of pif.
854 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
856 pifrec = db().get_pif_record(pif)
858 vlan = pifrec['VLAN_master_of']
859 if not vlan or vlan == "OpaqueRef:NULL":
860 raise Error("PIF is not a VLAN master")
862 vlanrec = db().get_vlan_record(vlan)
864 raise Error("No VLAN record found for PIF")
866 return vlanrec['tagged_PIF']
868 def pif_get_vlan_masters(pif):
869 """Returns a list of PIFs which are VLANs on top of the given pif."""
871 pifrec = db().get_pif_record(pif)
872 vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
873 return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
878 def pif_is_tunnel(pif):
879 return len(db().get_pif_record(pif)['tunnel_access_PIF_of']) > 0
882 # Datapath base class
885 class Datapath(object):
886 """Object encapsulating the actions necessary to (de)configure the
887 datapath for a given PIF. Does not include configuration of the
888 IP address on the ipdev.
891 def __init__(self, pif):
896 """Class method called when write action is called. Can be used
897 to update any backend specific configuration."""
900 def configure_ipdev(self, cfg):
901 """Write ifcfg TYPE field for an IPdev, plus any type specific
904 raise NotImplementedError
906 def preconfigure(self, parent):
907 """Prepare datapath configuration for PIF, but do not actually
910 Any configuration files should be attached to parent.
912 raise NotImplementedError
914 def bring_down_existing(self):
915 """Tear down any existing network device configuration which
916 needs to be undone in order to bring this PIF up.
918 raise NotImplementedError
921 """Apply the configuration prepared in the preconfigure stage.
923 Should assume any configuration files changed attached in
924 the preconfigure stage are applied and bring up the
925 necesary devices to provide the datapath for the
928 Should not bring up the IPdev.
930 raise NotImplementedError
933 """Called after the IPdev has been brought up.
935 Should do any final setup, including reinstating any
936 devices which were taken down in the bring_down_existing
939 raise NotImplementedError
941 def bring_down(self):
942 """Tear down and deconfigure the datapath. Should assume the
943 IPdev has already been brought down.
945 raise NotImplementedError
947 def DatapathFactory():
948 # XXX Need a datapath object for bridgeless PIFs
951 network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
952 network_backend = network_conf.readline().strip()
955 raise Error("failed to determine network backend:" + e)
957 if network_backend == "bridge":
958 from InterfaceReconfigureBridge import DatapathBridge
959 return DatapathBridge
960 elif network_backend in ["openvswitch", "vswitch"]:
961 from InterfaceReconfigureVswitch import DatapathVswitch
962 return DatapathVswitch
964 raise Error("unknown network backend %s" % network_backend)