# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
+import sys
import syslog
import os
from xml.dom.minidom import getDOMImplementation
from xml.dom.minidom import parse as parseXML
+the_root_prefix = ""
+def root_prefix():
+ """Returns a string to prefix to all file name references, which
+ is useful for testing."""
+ return the_root_prefix
+def set_root_prefix(prefix):
+ global the_root_prefix
+ the_root_prefix = prefix
+
+log_destination = "syslog"
+def get_log_destination():
+ """Returns the current log destination.
+ 'syslog' means "log to syslog".
+ 'stderr' means "log to stderr"."""
+ return log_destination
+def set_log_destination(dest):
+ global log_destination
+ log_destination = dest
+
#
# Logging.
#
def log(s):
- syslog.syslog(s)
+ if get_log_destination() == 'syslog':
+ syslog.syslog(s)
+ else:
+ print >>sys.stderr, s
#
# Exceptions.
def run_command(command):
log("Running command: " + ' '.join(command))
- rc = os.spawnl(os.P_WAIT, command[0], *command)
+ rc = os.spawnl(os.P_WAIT, root_prefix() + command[0], *command)
if rc != 0:
log("Command failed %d: " % rc + ' '.join(command))
return False
ret.append(_str_from_xml(n))
return ret
-def _otherconfig_to_xml(xml, parent, val, attrs):
- otherconfig = xml.createElement("other_config")
- parent.appendChild(otherconfig)
+def _map_to_xml(xml, parent, tag, val, attrs):
+ e = xml.createElement(tag)
+ parent.appendChild(e)
for n,v in val.items():
- if not n in attrs:
- raise Error("Unknown other-config attribute: %s" % n)
- _str_to_xml(xml, otherconfig, n, v)
-def _otherconfig_from_xml(n, attrs):
+ if n in attrs:
+ _str_to_xml(xml, e, n, v)
+ else:
+ log("Unknown other-config attribute: %s" % n)
+
+def _map_from_xml(n, attrs):
ret = {}
for n in n.childNodes:
if n.nodeName in attrs:
ret[n.nodeName] = _str_from_xml(n)
return ret
+def _otherconfig_to_xml(xml, parent, val, attrs):
+ return _map_to_xml(xml, parent, "other_config", val, attrs)
+def _otherconfig_from_xml(n, attrs):
+ return _map_from_xml(n, attrs)
+
#
# Definitions of the database objects (and their attributes) used by interface-reconfigure.
#
_PIF_XML_TAG = "pif"
_VLAN_XML_TAG = "vlan"
+_TUNNEL_XML_TAG = "tunnel"
_BOND_XML_TAG = "bond"
_NETWORK_XML_TAG = "network"
+_POOL_XML_TAG = "pool"
-_ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
+_ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso', 'gro', 'lro' ]
_PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
- [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
+ [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay',
+ 'updelay', 'use_carrier', 'hashing-algorithm' ] + \
+ [ 'vlan-bug-workaround' ] + \
_ETHTOOL_OTHERCONFIG_ATTRS
_PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
'VLAN_master_of': (_str_to_xml,_str_from_xml),
'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
+ 'tunnel_access_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_access_PIF_of', 'pif', v),
+ lambda n: _strlist_from_xml(n, 'tunnel_access_PIF_of', 'pif')),
+ 'tunnel_transport_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_transport_PIF_of', 'pif', v),
+ lambda n: _strlist_from_xml(n, 'tunnel_transport_PIF_of', 'pif')),
'ip_configuration_mode': (_str_to_xml,_str_from_xml),
'IP': (_str_to_xml,_str_from_xml),
'netmask': (_str_to_xml,_str_from_xml),
'untagged_PIF': (_str_to_xml,_str_from_xml),
}
+_TUNNEL_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
+ 'access_PIF': (_str_to_xml,_str_from_xml),
+ 'transport_PIF': (_str_to_xml,_str_from_xml),
+ }
_BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
'master': (_str_to_xml,_str_from_xml),
'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
}
-_NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + _ETHTOOL_OTHERCONFIG_ATTRS
+_NETWORK_OTHERCONFIG_ATTRS = [ 'mtu',
+ 'static-routes',
+ 'vswitch-controller-fail-mode',
+ 'vswitch-disable-in-band' ] \
+ + _ETHTOOL_OTHERCONFIG_ATTRS
_NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
'bridge': (_str_to_xml,_str_from_xml),
+ 'MTU': (_str_to_xml,_str_from_xml),
'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
}
+_POOL_OTHERCONFIG_ATTRS = ['vswitch-controller-fail-mode']
+
+_POOL_ATTRS = { 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _POOL_OTHERCONFIG_ATTRS),
+ lambda n: _otherconfig_from_xml(n, _POOL_OTHERCONFIG_ATTRS)),
+ }
+
#
# Database Cache object
#
class DatabaseCache(object):
def __read_xensource_inventory(self):
- filename = "/etc/xensource-inventory"
+ filename = root_prefix() + "/etc/xensource-inventory"
f = open(filename, "r")
lines = [x.strip("\n") for x in f.readlines()]
f.close()
def __get_vlan_records_from_xapi(self, session):
self.__vlans = {}
- for v in session.xenapi.VLAN.get_all():
- rec = session.xenapi.VLAN.get_record(v)
+ for (v,rec) in session.xenapi.VLAN.get_all_records().items():
if not self.__pif_on_host(rec['untagged_PIF']):
continue
self.__vlans[v] = {}
for f in _VLAN_ATTRS:
self.__vlans[v][f] = rec[f]
+ def __get_tunnel_records_from_xapi(self, session):
+ self.__tunnels = {}
+ for t in session.xenapi.tunnel.get_all():
+ rec = session.xenapi.tunnel.get_record(t)
+ if not self.__pif_on_host(rec['transport_PIF']):
+ continue
+ self.__tunnels[t] = {}
+ for f in _TUNNEL_ATTRS:
+ self.__tunnels[t][f] = rec[f]
+
def __get_bond_records_from_xapi(self, session):
self.__bonds = {}
- for b in session.xenapi.Bond.get_all():
- rec = session.xenapi.Bond.get_record(b)
+ for (b,rec) in session.xenapi.Bond.get_all_records().items():
if not self.__pif_on_host(rec['master']):
continue
self.__bonds[b] = {}
def __get_network_records_from_xapi(self, session):
self.__networks = {}
- for n in session.xenapi.network.get_all():
- rec = session.xenapi.network.get_record(n)
+ for (n,rec) in session.xenapi.network.get_all_records().items():
self.__networks[n] = {}
for f in _NETWORK_ATTRS:
if f == "PIFs":
# drop PIFs on other hosts
self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
+ elif f == "MTU" and f not in rec:
+ # XenServer 5.5 network records did not have an
+ # MTU field, so allow this to be missing.
+ pass
else:
self.__networks[n][f] = rec[f]
self.__networks[n]['other_config'] = {}
if not rec['other_config'].has_key(f): continue
self.__networks[n]['other_config'][f] = rec['other_config'][f]
+ def __get_pool_records_from_xapi(self, session):
+ self.__pools = {}
+ for p in session.xenapi.pool.get_all():
+ rec = session.xenapi.pool.get_record(p)
+
+ self.__pools[p] = {}
+
+ for f in _POOL_ATTRS:
+ self.__pools[p][f] = rec[f]
+
+ for f in _POOL_OTHERCONFIG_ATTRS:
+ if rec['other_config'].has_key(f):
+ self.__pools[p]['other_config'][f] = rec['other_config'][f]
+
def __to_xml(self, xml, parent, key, ref, rec, attrs):
"""Encode a database object as XML"""
e = xml.createElement(key)
host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
self.__get_pif_records_from_xapi(session, host)
-
+ self.__get_pool_records_from_xapi(session)
+ self.__get_tunnel_records_from_xapi(session)
self.__get_vlan_records_from_xapi(session)
self.__get_bond_records_from_xapi(session)
self.__get_network_records_from_xapi(session)
else:
log("Loading xapi database cache from %s" % cache_file)
- xml = parseXML(cache_file)
+ xml = parseXML(root_prefix() + cache_file)
self.__pifs = {}
self.__bonds = {}
self.__vlans = {}
+ self.__pools = {}
+ self.__tunnels = {}
self.__networks = {}
assert(len(xml.childNodes) == 1)
elif n.nodeName == _VLAN_XML_TAG:
(ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
self.__vlans[ref] = rec
+ elif n.nodeName == _TUNNEL_XML_TAG:
+ (ref,rec) = self.__from_xml(n, _TUNNEL_ATTRS)
+ self.__vlans[ref] = rec
elif n.nodeName == _NETWORK_XML_TAG:
(ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
self.__networks[ref] = rec
+ elif n.nodeName == _POOL_XML_TAG:
+ (ref,rec) = self.__from_xml(n, _POOL_ATTRS)
+ self.__pools[ref] = rec
else:
raise Error("Unknown XML element %s" % n.nodeName)
self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
for (ref,rec) in self.__vlans.items():
self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
+ for (ref,rec) in self.__tunnels.items():
+ self.__to_xml(xml, xml.documentElement, _TUNNEL_XML_TAG, ref, rec, _TUNNEL_ATTRS)
for (ref,rec) in self.__networks.items():
self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
_NETWORK_ATTRS)
+ for (ref,rec) in self.__pools.items():
+ self.__to_xml(xml, xml.documentElement, _POOL_XML_TAG, ref, rec, _POOL_ATTRS)
- f = open(cache_file, 'w')
+ temp_file = cache_file + ".%d" % os.getpid()
+ f = open(temp_file, 'w')
f.write(xml.toprettyxml())
f.close()
+ os.rename(temp_file, cache_file)
def get_pif_by_uuid(self, uuid):
pifs = map(lambda (ref,rec): ref,
filter(lambda (ref,rec): rec['device'] == device,
self.__pifs.items()))
+ def get_networks_with_bridge(self, bridge):
+ return map(lambda (ref,rec): ref,
+ filter(lambda (ref,rec): rec['bridge'] == bridge,
+ self.__networks.items()))
+
+ def get_network_by_bridge(self, bridge):
+ #Assumes one network has bridge.
+ try:
+ return self.get_networks_with_bridge(bridge)[0]
+ except KeyError:
+ return None
+
def get_pif_by_bridge(self, bridge):
- networks = map(lambda (ref,rec): ref,
- filter(lambda (ref,rec): rec['bridge'] == bridge,
- self.__networks.items()))
+ networks = self.get_networks_with_bridge(bridge)
+
if len(networks) == 0:
raise Error("No matching network \"%s\"" % bridge)
else:
return None
+ def get_pool_record(self):
+ if len(self.__pools) > 0:
+ return self.__pools.values()[0]
+
#
#
#
+PIF_OTHERCONFIG_DEFAULTS = {'gro': 'off', 'lro': 'off'}
-def ethtool_settings(oc):
+def ethtool_settings(oc, defaults = {}):
settings = []
if oc.has_key('ethtool-speed'):
val = oc['ethtool-speed']
log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
if oc.has_key('ethtool-duplex'):
val = oc['ethtool-duplex']
- if val in ["10", "100", "1000"]:
- settings += ['duplex', 'val']
+ if val in ["half", "full"]:
+ settings += ['duplex', val]
else:
log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
if oc.has_key('ethtool-autoneg'):
else:
log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
offload = []
- for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
+ for opt in ("rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro"):
if oc.has_key("ethtool-" + opt):
val = oc["ethtool-" + opt]
if val in ["true", "on"]:
offload += [opt, 'off']
else:
log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
+ elif opt in defaults:
+ offload += [opt, defaults[opt]]
return settings,offload
-def mtu_setting(oc):
+# By default the MTU is taken from the Network.MTU setting for VIF,
+# PIF and Bridge. However it is possible to override this by using
+# {VIF,PIF,Network}.other-config:mtu.
+#
+# type parameter is a string describing the object that the oc parameter
+# is from. e.g. "PIF", "Network"
+def mtu_setting(nw, type, oc):
+ mtu = None
+
+ nwrec = db().get_network_record(nw)
+ if nwrec.has_key('MTU'):
+ mtu = nwrec['MTU']
+ else:
+ mtu = "1500"
+
if oc.has_key('mtu'):
+ log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
+ (nwrec['bridge'], type, mtu))
+ mtu = oc['mtu']
+
+ if mtu is not None:
try:
- int(oc['mtu']) # Check that the value is an integer
- return oc['mtu']
+ int(mtu) # Check that the value is an integer
+ return mtu
except ValueError, x:
- log("Invalid value for mtu = %s" % oc['mtu'])
+ log("Invalid value for mtu = %s" % mtu)
+
return None
#
#
def netdev_exists(netdev):
- return os.path.exists("/sys/class/net/" + netdev)
+ return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
def pif_netdev_name(pif):
"""Get the netdev name for a PIF."""
else:
return pifrec['device']
+#
+# Bridges
+#
+
+def pif_is_bridged(pif):
+ pifrec = db().get_pif_record(pif)
+ nwrec = db().get_network_record(pifrec['network'])
+
+ if nwrec['bridge']:
+ # TODO: sanity check that nwrec['bridgeless'] != 'true'
+ return True
+ else:
+ # TODO: sanity check that nwrec['bridgeless'] == 'true'
+ return False
+
+def pif_bridge_name(pif):
+ """Return the bridge name of a pif.
+
+ PIF must be a bridged PIF."""
+ pifrec = db().get_pif_record(pif)
+
+ nwrec = db().get_network_record(pifrec['network'])
+
+ if nwrec['bridge']:
+ return nwrec['bridge']
+ else:
+ raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
+
#
# Bonded PIFs
#
vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
+#
+# Tunnel PIFs
+#
+def pif_is_tunnel(pif):
+ return len(db().get_pif_record(pif)['tunnel_access_PIF_of']) > 0
+
#
# Datapath base class
#
def __init__(self, pif):
self._pif = pif
+ @classmethod
+ def rewrite(cls):
+ """Class method called when write action is called. Can be used
+ to update any backend specific configuration."""
+ pass
+
def configure_ipdev(self, cfg):
"""Write ifcfg TYPE field for an IPdev, plus any type specific
fields to cfg
Should assume any configuration files changed attached in
the preconfigure stage are applied and bring up the
- necesary devices to provide the datapath for the
+ necessary devices to provide the datapath for the
PIF.
Should not bring up the IPdev.
"""
raise NotImplementedError
-def DatapathFactory(pif):
+def DatapathFactory():
# XXX Need a datapath object for bridgeless PIFs
try:
- network_conf = open("/etc/xensource/network.conf", 'r')
+ network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
network_backend = network_conf.readline().strip()
network_conf.close()
except Exception, e:
if network_backend == "bridge":
from InterfaceReconfigureBridge import DatapathBridge
- return DatapathBridge(pif)
- elif network_backend == "vswitch":
+ return DatapathBridge
+ elif network_backend in ["openvswitch", "vswitch"]:
from InterfaceReconfigureVswitch import DatapathVswitch
- return DatapathVswitch(pif)
+ return DatapathVswitch
else:
raise Error("unknown network backend %s" % network_backend)