xenserver: Allow NULL pool in configuration cache.
[sliver-openvswitch.git] / xenserver / opt_xensource_libexec_InterfaceReconfigure.py
index 229f3b9..3030e0f 100644 (file)
 # 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.
@@ -38,7 +61,7 @@ class Error(Exception):
 
 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
@@ -218,20 +241,27 @@ def _strlist_from_xml(n, ltag, itag):
             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.
 #
@@ -244,8 +274,10 @@ def _otherconfig_from_xml(n, attrs):
 
 _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' ]
 
@@ -264,6 +296,10 @@ _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),
@@ -285,6 +321,10 @@ _VLAN_ATTRS = { 'uuid': (_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),
@@ -295,12 +335,19 @@ _NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + _ETHTOOL_OTHERCONFIG_A
 
 _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
 #
@@ -323,7 +370,7 @@ def db_init_from_xenapi(session):
     
 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()
@@ -332,6 +379,7 @@ class DatabaseCache(object):
         defs = [ (a, b.strip("'")) for (a,b) in defs ]
 
         return dict(defs)
+
     def __pif_on_host(self,pif):
         return self.__pifs.has_key(pif)
 
@@ -342,7 +390,12 @@ class DatabaseCache(object):
                 continue
             self.__pifs[p] = {}
             for f in _PIF_ATTRS:
-                self.__pifs[p][f] = rec[f]
+                if f in [ "tunnel_access_PIF_of", "tunnel_transport_PIF_of" ] and f not in rec:
+                    # XenServer 5.5 network records did not have
+                    # these fields, so allow them to be missing.
+                    pass
+                else:
+                    self.__pifs[p][f] = rec[f]
             self.__pifs[p]['other_config'] = {}
             for f in _PIF_OTHERCONFIG_ATTRS:
                 if not rec['other_config'].has_key(f): continue
@@ -358,6 +411,16 @@ class DatabaseCache(object):
             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():
@@ -377,6 +440,10 @@ class DatabaseCache(object):
                 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'] = {}
@@ -384,6 +451,20 @@ class DatabaseCache(object):
                 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)
@@ -430,6 +511,14 @@ class DatabaseCache(object):
 
                 self.__get_pif_records_from_xapi(session, host)
 
+                try:
+                    self.__get_tunnel_records_from_xapi(session)
+                except XenAPI.Failure, e:
+                    error,details = e.details
+                    if error == "MESSAGE_METHOD_UNKNOWN" and details == "tunnel.get_all":
+                        pass
+
+                self.__get_pool_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)
@@ -439,11 +528,13 @@ class DatabaseCache(object):
         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)
@@ -463,9 +554,15 @@ class DatabaseCache(object):
                 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)
 
@@ -479,9 +576,13 @@ class DatabaseCache(object):
             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')
         f.write(xml.toprettyxml())
@@ -557,6 +658,10 @@ class DatabaseCache(object):
         else:
             return None
 
+    def get_pool_record(self):
+        if len(self.__pools) > 0:
+            return self.__pools.values()[0]
+
 #
 #
 #
@@ -595,13 +700,33 @@ def ethtool_settings(oc):
                 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
     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
 
 #
@@ -624,7 +749,7 @@ def pif_ipdev_name(pif):
 #
 
 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."""
@@ -636,6 +761,34 @@ def pif_netdev_name(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
 #
@@ -713,6 +866,13 @@ def pif_get_vlan_masters(pif):
     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):
+    rec = db().get_pif_record(pif)
+    return rec.has_key('tunnel_access_PIF_of') and len(rec['tunnel_access_PIF_of']) > 0
+
 #
 # Datapath base class
 #
@@ -726,6 +886,12 @@ class Datapath(object):
     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
@@ -773,11 +939,11 @@ class Datapath(object):
         """
         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:
@@ -785,9 +951,9 @@ def DatapathFactory(pif):
     
     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)