use context manager to save and read node session
[bootmanager.git] / source / BootAPI.py
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2003 Intel Corporation
4 # All rights reserved.
5 #
6 # Copyright (c) 2004-2006 The Trustees of Princeton University
7 # All rights reserved.
8
9
10 import xmlrpclib
11 import xml.parsers.expat
12 import hmac
13 import string
14 import sha
15 import cPickle
16 import utils
17 import os
18
19 from Exceptions import *
20
21 stash = None
22
23 def create_auth_structure(vars, call_params):
24     """
25     create and return an authentication structure for a Boot API
26     call. Vars contains the boot manager runtime variables, and
27     call_params is a tuple of the parameters that will be passed to the
28     API call. Return None if unable to (typically due to missing
29     keys in vars, such as node_id or node_key)
30     """
31
32     auth = {}
33
34     try:
35         auth_session = {}
36         auth_session['AuthMethod'] = 'session'
37
38         if not vars.has_key('NODE_SESSION'):
39             # Try to load /etc/planetlab/session if it exists.
40             with open('/etc/planetlab/session', 'r') as sessionfile:
41                 session = sessionfile.read().strip()
42
43             auth_session['session'] = session
44             # Test session.  Faults if it's no good.
45             vars['API_SERVER_INST'].AuthCheck(auth_session)
46             vars['NODE_SESSION'] = session
47
48         else:
49             auth_session['session'] = vars['NODE_SESSION']
50
51         auth = auth_session
52
53     except:
54         auth['AuthMethod'] = 'hmac'
55
56         try:
57             auth['node_id'] = vars['NODE_ID']
58             auth['node_ip'] = vars['INTERFACE_SETTINGS']['ip']
59         except KeyError as e:
60             return None
61
62         node_hmac = hmac.new(vars['NODE_KEY'], "[]".encode('utf-8'), sha).hexdigest()
63         auth['value'] = node_hmac
64         try:
65             auth_session = {}
66             if not vars.has_key('NODE_SESSION'):
67                 session = vars['API_SERVER_INST'].GetSession(auth)
68                 auth_session['session'] = session
69                 vars['NODE_SESSION'] = session
70                 # NOTE: save session value to /etc/planetlab/session for
71                 # RunlevelAgent and future BootManager runs
72                 if not os.path.exists("/etc/planetlab"):
73                     os.makedirs("/etc/planetlab")
74                 with open('/etc/planetlab/session', 'w') as sessionfile:
75                     sessionfile.write(vars['NODE_SESSION'])
76
77             else:
78                 auth_session['session'] = vars['NODE_SESSION']
79
80             auth_session['AuthMethod'] = 'session'
81             auth = auth_session
82
83         except Exception as e:
84             # NOTE: BM has failed to authenticate utterly.
85             import traceback
86             traceback.print_exc()
87             raise BootManagerAuthenticationException("{}".format(e))
88
89     return auth
90
91
92 def serialize_params(call_params):
93     """
94     convert a list of parameters into a format that will be used in the
95     hmac generation. both the boot manager and plc must have a common
96     format. full documentation is in the boot manager technical document,
97     but essentially we are going to take all the values (and keys for
98     dictionary objects), and put them into a list. sort them, and combine
99     them into one long string encased in a set of braces.
100     """
101
102     values = []
103
104     for param in call_params:
105         if isinstance(param,list) or isinstance(param,tuple):
106             values += serialize_params(param)
107         elif isinstance(param,dict):
108             values += serialize_params(param.values())
109         elif isinstance(param,xmlrpclib.Boolean):
110             # bool was not a real type in Python <2.3 and had to be
111             # marshalled as a custom type in xmlrpclib. Make sure that
112             # bools serialize consistently.
113             if param:
114                 values.append("True")
115             else:
116                 values.append("False")
117         else:
118             values.append(unicode(param))
119
120     return values
121
122
123 def call_api_function(vars, function, user_params):
124     """
125     call the named api function with params, and return the
126     value to the caller. the authentication structure is handled
127     automatically, and doesn't need to be passed in with params.
128
129     If the call fails, a BootManagerException is raised.
130     """
131     global stash
132
133     try:
134         api_server = vars['API_SERVER_INST']
135     except KeyError as e:
136         raise BootManagerException("No connection to the API server exists.")
137
138     if api_server is None:
139         if not stash:
140             load(vars)
141         for i in stash:
142             if i[0] == function and i[1] == user_params:
143                return i[2]
144         raise BootManagerException(
145               "Disconnected operation failed, insufficient stash.")
146
147     auth = create_auth_structure(vars,user_params)
148     if auth is None:
149         raise BootManagerException(
150               "Could not create auth structure, missing values.")
151
152     params = (auth,)
153     params = params + user_params
154
155     try:
156         exec("rc= api_server.{}(*params)".format(function))
157         if stash is None:
158             stash = []
159         stash += [ [ function, user_params, rc ] ]
160         return rc
161     except xmlrpclib.Fault as fault:
162         raise BootManagerException("API Fault: {}".format(fault))
163     except xmlrpclib.ProtocolError as err:
164         raise BootManagerException("XML RPC protocol error: {}".format(err))
165     except xml.parsers.expat.ExpatError as err:
166         raise BootManagerException("XML parsing error: {}".format(err))
167
168
169 class Stash(file):
170     mntpnt = '/tmp/stash'
171     def __init__(self, vars, mode):
172         utils.makedirs(self.mntpnt)
173         try:
174             utils.sysexec('mount -t auto -U {} {}'.format(vars['DISCONNECTED_OPERATION'], self.mntpnt))
175             # make sure it's not read-only
176             f = file('{}/api.cache'.format(self.mntpnt), 'a')
177             f.close()
178             file.__init__(self, '{}/api.cache'.format(self.mntpnt), mode)
179         except:
180             utils.sysexec_noerr('umount {}'.format(self.mntpnt))
181             raise BootManagerException("Couldn't find API-cache for disconnected operation")
182
183     def close(self):
184         file.close(self)
185         utils.sysexec_noerr('umount {}'.format(self.mntpnt))
186
187 def load(vars):
188     global stash
189     s = Stash(vars, 'r')
190     stash = cPickle.load(s)
191     s.close()
192
193 def save(vars):
194     global stash
195     if vars['DISCONNECTED_OPERATION']:
196         s = Stash(vars, 'w')
197         cPickle.dump(stash, s)
198         s.close()