Disconnected operation.
[bootmanager.git] / source / BootAPI.py
1 #!/usr/bin/python2
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     auth['AuthMethod']= 'hmac'
33
34     try:
35         network= vars['NETWORK_SETTINGS']
36         
37         auth['node_id']= vars['NODE_ID']
38         auth['node_ip']= network['ip']
39         node_key= vars['NODE_KEY']
40     except KeyError, e:
41         return None
42
43     params= serialize_params(call_params)
44     params.sort()
45     msg= "[" + "".join(params) + "]"
46     node_hmac= hmac.new(node_key,msg.encode('utf-8'),sha).hexdigest()
47     auth['value']= node_hmac
48
49     return auth
50
51
52
53 def serialize_params( call_params ):
54     """
55     convert a list of parameters into a format that will be used in the
56     hmac generation. both the boot manager and plc must have a common
57     format. full documentation is in the boot manager technical document,
58     but essentially we are going to take all the values (and keys for
59     dictionary objects), and put them into a list. sort them, and combine
60     them into one long string encased in a set of braces.
61     """
62
63     values= []
64     
65     for param in call_params:
66         if isinstance(param,list) or isinstance(param,tuple):
67             values += serialize_params(param)
68         elif isinstance(param,dict):
69             values += serialize_params(param.values())
70         elif isinstance(param,xmlrpclib.Boolean):
71             # bool was not a real type in Python <2.3 and had to be
72             # marshalled as a custom type in xmlrpclib. Make sure that
73             # bools serialize consistently.
74             if param:
75                 values.append("True")
76             else:
77                 values.append("False")
78         else:
79             values.append(unicode(param))
80                 
81     return values
82
83     
84 def call_api_function( vars, function, user_params ):
85     """
86     call the named api function with params, and return the
87     value to the caller. the authentication structure is handled
88     automatically, and doesn't need to be passed in with params.
89
90     If the call fails, a BootManagerException is raised.
91     """
92     global stash
93
94     try:
95         api_server= vars['API_SERVER_INST']
96     except KeyError, e:
97         raise BootManagerException, "No connection to the API server exists."
98
99     if api_server is None:
100         if not stash:
101             load(vars)
102         for i in stash:
103             if i[0] == function and i[1] == user_params:
104                return i[2]
105         raise BootManagerException, \
106               "Disconnected operation failed, insufficient stash."
107
108     auth= create_auth_structure(vars,user_params)
109     if auth is None:
110         raise BootManagerException, \
111               "Could not create auth structure, missing values."
112     
113     params= (auth,)
114     params= params + user_params
115
116     try:
117         exec( "rc= api_server.%s(*params)" % function )
118         if stash is None:
119             stash = []
120         stash += [ [ function, user_params, rc ] ]
121         return rc
122     except xmlrpclib.Fault, fault:
123         raise BootManagerException, "API Fault: %s" % fault
124     except xmlrpclib.ProtocolError, err:
125         raise BootManagerException,"XML RPC protocol error: %s" % err
126     except xml.parsers.expat.ExpatError, err:
127         raise BootManagerException,"XML parsing error: %s" % err
128
129
130 class Stash(file):
131     mntpnt = '/tmp/stash'
132     def __init__(self, vars, mode):
133         utils.makedirs(self.mntpnt)
134         try:
135             utils.sysexec('mount -t auto -U %s %s' % (vars['DISCONNECTED_OPERATION'], self.mntpnt))
136             # make sure it's not read-only
137             f = file('%s/api.cache' % self.mntpnt, 'a')
138             f.close()
139             file.__init__(self, '%s/api.cache' % self.mntpnt, mode)
140         except:
141             utils.sysexec_noerr('umount %s' % self.mntpnt)
142             raise BootManagerException, "Couldn't find API-cache for disconnected operation"
143
144     def close(self):
145         file.close(self)
146         utils.sysexec_noerr('umount %s' % self.mntpnt)
147
148 def load(vars):
149     global stash
150     s = Stash(vars, 'r')
151     stash = cPickle.load(s)
152     s.close()
153
154 def save(vars):
155     global stash
156     if vars['DISCONNECTED_OPERATION']:
157         s = Stash(vars, 'w')
158         cPickle.dump(stash, s)
159         s.close()