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