use context manager to save and read node session
[bootmanager.git] / source / BootAPI.py
index a27593b..db17356 100644 (file)
@@ -1,5 +1,5 @@
-#!/usr/bin/python2
-
+#!/usr/bin/python
+#
 # Copyright (c) 2003 Intel Corporation
 # All rights reserved.
 #
 # Copyright (c) 2003 Intel Corporation
 # All rights reserved.
 #
@@ -12,11 +12,15 @@ import xml.parsers.expat
 import hmac
 import string
 import sha
 import hmac
 import string
 import sha
+import cPickle
+import utils
+import os
 
 from Exceptions import *
 
 
 from Exceptions import *
 
+stash = None
 
 
-def create_auth_structure( vars, call_params ):
+def create_auth_structure(vars, call_params):
     """
     create and return an authentication structure for a Boot API
     call. Vars contains the boot manager runtime variables, and
     """
     create and return an authentication structure for a Boot API
     call. Vars contains the boot manager runtime variables, and
@@ -24,28 +28,68 @@ def create_auth_structure( vars, call_params ):
     API call. Return None if unable to (typically due to missing
     keys in vars, such as node_id or node_key)
     """
     API call. Return None if unable to (typically due to missing
     keys in vars, such as node_id or node_key)
     """
-    
-    auth= {}
-    auth['AuthMethod']= 'hmac'
+
+    auth = {}
 
     try:
 
     try:
-        network= vars['NETWORK_SETTINGS']
-        
-        auth['node_id']= vars['NODE_ID']
-        auth['node_ip']= network['ip']
-        node_key= vars['NODE_KEY']
-    except KeyError, e:
-        return None
-
-    msg= serialize_params(call_params)
-    node_hmac= hmac.new(node_key,msg,sha).hexdigest()
-    auth['value']= node_hmac
+        auth_session = {}
+        auth_session['AuthMethod'] = 'session'
 
 
-    return auth
+        if not vars.has_key('NODE_SESSION'):
+            # Try to load /etc/planetlab/session if it exists.
+            with open('/etc/planetlab/session', 'r') as sessionfile:
+                session = sessionfile.read().strip()
 
 
+            auth_session['session'] = session
+            # Test session.  Faults if it's no good.
+            vars['API_SERVER_INST'].AuthCheck(auth_session)
+            vars['NODE_SESSION'] = session
+
+        else:
+            auth_session['session'] = vars['NODE_SESSION']
+
+        auth = auth_session
+
+    except:
+        auth['AuthMethod'] = 'hmac'
+
+        try:
+            auth['node_id'] = vars['NODE_ID']
+            auth['node_ip'] = vars['INTERFACE_SETTINGS']['ip']
+        except KeyError as e:
+            return None
+
+        node_hmac = hmac.new(vars['NODE_KEY'], "[]".encode('utf-8'), sha).hexdigest()
+        auth['value'] = node_hmac
+        try:
+            auth_session = {}
+            if not vars.has_key('NODE_SESSION'):
+                session = vars['API_SERVER_INST'].GetSession(auth)
+                auth_session['session'] = session
+                vars['NODE_SESSION'] = session
+                # NOTE: save session value to /etc/planetlab/session for
+                # RunlevelAgent and future BootManager runs
+                if not os.path.exists("/etc/planetlab"):
+                    os.makedirs("/etc/planetlab")
+                with open('/etc/planetlab/session', 'w') as sessionfile:
+                    sessionfile.write(vars['NODE_SESSION'])
+
+            else:
+                auth_session['session'] = vars['NODE_SESSION']
+
+            auth_session['AuthMethod'] = 'session'
+            auth = auth_session
+
+        except Exception as e:
+            # NOTE: BM has failed to authenticate utterly.
+            import traceback
+            traceback.print_exc()
+            raise BootManagerAuthenticationException("{}".format(e))
+
+    return auth
 
 
 
 
-def serialize_params( call_params ):
+def serialize_params(call_params):
     """
     convert a list of parameters into a format that will be used in the
     hmac generation. both the boot manager and plc must have a common
     """
     convert a list of parameters into a format that will be used in the
     hmac generation. both the boot manager and plc must have a common
@@ -55,50 +99,28 @@ def serialize_params( call_params ):
     them into one long string encased in a set of braces.
     """
 
     them into one long string encased in a set of braces.
     """
 
-    # if there are no parameters, just return empty paren set
-    if len(call_params) == 0:
-        return "[]"
+    values = []
 
 
-    values= []
-    
     for param in call_params:
         if isinstance(param,list) or isinstance(param,tuple):
     for param in call_params:
         if isinstance(param,list) or isinstance(param,tuple):
-            values= values + map(str,param)
+            values += serialize_params(param)
         elif isinstance(param,dict):
         elif isinstance(param,dict):
-            values= values + collapse_dict(param)        
+            values += serialize_params(param.values())
+        elif isinstance(param,xmlrpclib.Boolean):
+            # bool was not a real type in Python <2.3 and had to be
+            # marshalled as a custom type in xmlrpclib. Make sure that
+            # bools serialize consistently.
+            if param:
+                values.append("True")
+            else:
+                values.append("False")
         else:
         else:
-            values.append( str(param) )
-                
-    values.sort()
-    values= "[" + string.join(values,"") + "]"
-    return values
+            values.append(unicode(param))
 
 
-    
-def collapse_dict( value ):
-    """
-    given a dictionary, return a list of all the keys and values as strings,
-    in no particular order
-    """
+    return values
 
 
-    item_list= []
-    
-    if not isinstance(value,dict):
-        return item_list
-    
-    for key in value.keys():
-        key_value= value[key]
-        if isinstance(key_value,list) or isinstance(key_value,tuple):
-            item_list= item_list + map(str,key_value)
-        elif isinstance(key_value,dict):
-            item_list= item_list + collapse_dict(key_value)
-        else:
-            item_list.append( str(key_value) )
 
 
-    return item_list
-            
-    
-    
-def call_api_function( vars, function, user_params ):
+def call_api_function(vars, function, user_params):
     """
     call the named api function with params, and return the
     value to the caller. the authentication structure is handled
     """
     call the named api function with params, and return the
     value to the caller. the authentication structure is handled
@@ -106,26 +128,71 @@ def call_api_function( vars, function, user_params ):
 
     If the call fails, a BootManagerException is raised.
     """
 
     If the call fails, a BootManagerException is raised.
     """
-    
-    try:
-        api_server= vars['API_SERVER_INST']
-    except KeyError, e:
-        raise BootManagerException, "No connection to the API server exists."
+    global stash
 
 
-    auth= create_auth_structure(vars,user_params)
+    try:
+        api_server = vars['API_SERVER_INST']
+    except KeyError as e:
+        raise BootManagerException("No connection to the API server exists.")
+
+    if api_server is None:
+        if not stash:
+            load(vars)
+        for i in stash:
+            if i[0] == function and i[1] == user_params:
+               return i[2]
+        raise BootManagerException(
+              "Disconnected operation failed, insufficient stash.")
+
+    auth = create_auth_structure(vars,user_params)
     if auth is None:
     if auth is None:
-        raise BootManagerException, \
-              "Could not create auth structure, missing values."
-    
-    params= (auth,)
-    params= params + user_params
+        raise BootManagerException(
+              "Could not create auth structure, missing values.")
+
+    params = (auth,)
+    params = params + user_params
 
     try:
 
     try:
-        exec( "rc= api_server.%s(*params)" % function )
+        exec("rc= api_server.{}(*params)".format(function))
+        if stash is None:
+            stash = []
+        stash += [ [ function, user_params, rc ] ]
         return rc
         return rc
-    except xmlrpclib.Fault, fault:
-        raise BootManagerException, "API Fault: %s" % fault
-    except xmlrpclib.ProtocolError, err:
-        raise BootManagerException,"XML RPC protocol error: %s" % err
-    except xml.parsers.expat.ExpatError, err:
-        raise BootManagerException,"XML parsing error: %s" % err
+    except xmlrpclib.Fault as fault:
+        raise BootManagerException("API Fault: {}".format(fault))
+    except xmlrpclib.ProtocolError as err:
+        raise BootManagerException("XML RPC protocol error: {}".format(err))
+    except xml.parsers.expat.ExpatError as err:
+        raise BootManagerException("XML parsing error: {}".format(err))
+
+
+class Stash(file):
+    mntpnt = '/tmp/stash'
+    def __init__(self, vars, mode):
+        utils.makedirs(self.mntpnt)
+        try:
+            utils.sysexec('mount -t auto -U {} {}'.format(vars['DISCONNECTED_OPERATION'], self.mntpnt))
+            # make sure it's not read-only
+            f = file('{}/api.cache'.format(self.mntpnt), 'a')
+            f.close()
+            file.__init__(self, '{}/api.cache'.format(self.mntpnt), mode)
+        except:
+            utils.sysexec_noerr('umount {}'.format(self.mntpnt))
+            raise BootManagerException("Couldn't find API-cache for disconnected operation")
+
+    def close(self):
+        file.close(self)
+        utils.sysexec_noerr('umount {}'.format(self.mntpnt))
+
+def load(vars):
+    global stash
+    s = Stash(vars, 'r')
+    stash = cPickle.load(s)
+    s.close()
+
+def save(vars):
+    global stash
+    if vars['DISCONNECTED_OPERATION']:
+        s = Stash(vars, 'w')
+        cPickle.dump(stash, s)
+        s.close()