c7d810570fe9b205b0b22ff80e3d2659df8843f2
[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             sessionfile = open('/etc/planetlab/session', 'r')
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             sessionfile.close()
49         else:
50             auth_session['session'] = vars['NODE_SESSION']
51
52         auth = auth_session
53
54     except:
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                 if not os.path.exists("/etc/planetlab"):
74                     os.makedirs("/etc/planetlab")
75                 sessionfile = open('/etc/planetlab/session', 'w')
76                 sessionfile.write( vars['NODE_SESSION'] )
77                 sessionfile.close()
78             else:
79                 auth_session['session'] = vars['NODE_SESSION']
80
81             auth_session['AuthMethod'] = 'session'
82             auth = auth_session
83
84         except Exception, e:
85             # NOTE: BM has failed to authenticate utterly.
86             raise BootManagerAuthenticationException, "%s" % e
87
88     return auth
89
90
91 def serialize_params( call_params ):
92     """
93     convert a list of parameters into a format that will be used in the
94     hmac generation. both the boot manager and plc must have a common
95     format. full documentation is in the boot manager technical document,
96     but essentially we are going to take all the values (and keys for
97     dictionary objects), and put them into a list. sort them, and combine
98     them into one long string encased in a set of braces.
99     """
100
101     values= []
102     
103     for param in call_params:
104         if isinstance(param,list) or isinstance(param,tuple):
105             values += serialize_params(param)
106         elif isinstance(param,dict):
107             values += serialize_params(param.values())
108         elif isinstance(param,xmlrpclib.Boolean):
109             # bool was not a real type in Python <2.3 and had to be
110             # marshalled as a custom type in xmlrpclib. Make sure that
111             # bools serialize consistently.
112             if param:
113                 values.append("True")
114             else:
115                 values.append("False")
116         else:
117             values.append(unicode(param))
118                 
119     return values
120
121     
122 def call_api_function( vars, function, user_params ):
123     """
124     call the named api function with params, and return the
125     value to the caller. the authentication structure is handled
126     automatically, and doesn't need to be passed in with params.
127
128     If the call fails, a BootManagerException is raised.
129     """
130     global stash
131
132     try:
133         api_server= vars['API_SERVER_INST']
134     except KeyError, e:
135         raise BootManagerException, "No connection to the API server exists."
136
137     if api_server is None:
138         if not stash:
139             load(vars)
140         for i in stash:
141             if i[0] == function and i[1] == user_params:
142                return i[2]
143         raise BootManagerException, \
144               "Disconnected operation failed, insufficient stash."
145
146     auth= create_auth_structure(vars,user_params)
147     if auth is None:
148         raise BootManagerException, \
149               "Could not create auth structure, missing values."
150     
151     params= (auth,)
152     params= params + user_params
153
154     try:
155         exec( "rc= api_server.%s(*params)" % function )
156         if stash is None:
157             stash = []
158         stash += [ [ function, user_params, rc ] ]
159         return rc
160     except xmlrpclib.Fault, fault:
161         raise BootManagerException, "API Fault: %s" % fault
162     except xmlrpclib.ProtocolError, err:
163         raise BootManagerException,"XML RPC protocol error: %s" % err
164     except xml.parsers.expat.ExpatError, err:
165         raise BootManagerException,"XML parsing error: %s" % err
166
167
168 class Stash(file):
169     mntpnt = '/tmp/stash'
170     def __init__(self, vars, mode):
171         utils.makedirs(self.mntpnt)
172         try:
173             utils.sysexec('mount -t auto -U %s %s' % (vars['DISCONNECTED_OPERATION'], self.mntpnt))
174             # make sure it's not read-only
175             f = file('%s/api.cache' % self.mntpnt, 'a')
176             f.close()
177             file.__init__(self, '%s/api.cache' % self.mntpnt, mode)
178         except:
179             utils.sysexec_noerr('umount %s' % self.mntpnt)
180             raise BootManagerException, "Couldn't find API-cache for disconnected operation"
181
182     def close(self):
183         file.close(self)
184         utils.sysexec_noerr('umount %s' % self.mntpnt)
185
186 def load(vars):
187     global stash
188     s = Stash(vars, 'r')
189     stash = cPickle.load(s)
190     s.close()
191
192 def save(vars):
193     global stash
194     if vars['DISCONNECTED_OPERATION']:
195         s = Stash(vars, 'w')
196         cPickle.dump(stash, s)
197         s.close()