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