Merge remote-tracking branch 'origin/pycurl' into planetlab-4_0-branch
[plcapi.git] / PLC / Shell.py
1 #!/usr/bin/python
2 #
3 # Interactive shell for testing PLCAPI
4 #
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 # Copyright (C) 2005 The Trustees of Princeton University
7 #
8 # $Id: Shell.py 5574 2007-10-25 20:33:17Z thierry $
9 #
10
11 import os
12 import pydoc
13 import xmlrpclib
14
15 from PLC.API import PLCAPI
16 from PLC.Parameter import Mixed
17 from PLC.Auth import Auth
18 from PLC.Config import Config
19 from PLC.Method import Method
20 from PLC.PyCurl import PyCurlTransport
21 import PLC.Methods
22
23 class Callable:
24     """
25     Wrapper to call a method either directly or remotely and
26     automagically add the authentication structure if necessary.
27     """
28
29     def __init__(self, shell, name, func, auth = None):
30         self.shell = shell
31         self.name = name
32         self.func = func
33         self.auth = auth
34
35     def __call__(self, *args, **kwds):
36         """
37         Automagically add the authentication structure if the function
38         requires it and it has not been specified.
39         """
40
41         if self.auth and \
42            (not args or not isinstance(args[0], dict) or \
43             (not args[0].has_key('AuthMethod') and \
44              not args[0].has_key('session'))):
45             args = (self.auth,) + args
46
47         if self.shell.multi:
48             self.shell.calls.append({'methodName': self.name, 'params': list(args)})
49             return None
50         else:
51             return self.func(*args, **kwds)
52
53 class Shell:
54     def __init__(self,
55                  # Add API functions to global scope
56                  globals = None,
57                  # Configuration file
58                  config = None,
59                  # XML-RPC server
60                  url = None, xmlrpc = False, cacert = None,
61                  # API authentication method
62                  method = None,
63                  # Password authentication
64                  role = None, user = None, password = None,
65                  # Session authentication
66                  session = None):
67         """
68         Initialize a new shell instance. Re-initializes globals.
69         """
70
71         try:
72             # If any XML-RPC options have been specified, do not try
73             # connecting directly to the DB.
74             if (url, method, user, password, role, cacert, xmlrpc) != \
75                    (None, None, None, None, None, None, False):
76                 raise Exception
77
78             # Otherwise, first try connecting directly to the DB. This
79             # absolutely requires a configuration file; the API
80             # instance looks for one in a default location if one is
81             # not specified. If this fails, try connecting to the API
82             # server via XML-RPC.
83             if config is None:
84                 self.api = PLCAPI()
85             else:
86                 self.api = PLCAPI(config)
87             self.config = self.api.config
88             self.url = None
89             self.server = None
90         except Exception, err:
91             # Try connecting to the API server via XML-RPC
92             self.api = PLCAPI(None)
93
94             try:
95                 if config is None:
96                     self.config = Config()
97                 else:
98                     self.config = Config(config)
99             except Exception, err:
100                 # Try to continue if no configuration file is available
101                 self.config = None
102
103             if url is None:
104                 if self.config is None:
105                     raise Exception, "Must specify API URL"
106
107                 url = "https://" + self.config.PLC_API_HOST + \
108                       ":" + str(self.config.PLC_API_PORT) + \
109                       "/" + self.config.PLC_API_PATH + "/"
110
111                 if cacert is None:
112                     cacert = self.config.PLC_API_CA_SSL_CRT
113
114             self.url = url
115             if cacert is not None:
116                 self.server = xmlrpclib.ServerProxy(url, PyCurlTransport(url, cacert), allow_none = 1)
117             else:
118                 self.server = xmlrpclib.ServerProxy(url, allow_none = 1)
119
120         # Set up authentication structure
121
122         # Default is to use session or capability authentication
123         if (method, user, password) == (None, None, None):
124             if session is not None or os.path.exists("/etc/planetlab/session"):
125                 method = "session"
126                 if session is None:
127                     session = "/etc/planetlab/session"
128             else:
129                 method = "capability"
130
131         if method == "capability":
132             # Load defaults from configuration file if using capability
133             # authentication.
134             if user is None and self.config is not None:
135                 user = self.config.PLC_API_MAINTENANCE_USER
136             if password is None and self.config is not None:
137                 password = self.config.PLC_API_MAINTENANCE_PASSWORD
138             if role is None:
139                 role = "admin"
140         elif method is None:
141             # Otherwise, default to password authentication
142             method = "password"
143
144         if role == "anonymous" or method == "anonymous":
145             self.auth = {'AuthMethod': "anonymous"}
146         elif method == "session":
147             if session is None:
148                 raise Exception, "Must specify session"
149
150             if os.path.exists(session):
151                 session = file(session).read()
152
153             self.auth = {'AuthMethod': "session", 'session': session}
154         else:
155             if user is None:
156                 raise Exception, "Must specify username"
157
158             if password is None:
159                 raise Exception, "Must specify password"
160
161             self.auth = {'AuthMethod': method,
162                          'Username': user,
163                          'AuthString': password}
164
165             if role is not None:
166                 self.auth['Role'] = role
167
168         for method in PLC.Methods.methods:
169             api_function = self.api.callable(method)
170
171             if self.server is None:
172                 # Can just call it directly
173                 func = api_function
174             else:
175                 func = getattr(self.server, method)
176
177             # If the function requires an authentication structure as
178             # its first argument, automagically add an auth struct to
179             # the call.
180             if api_function.accepts and \
181                (isinstance(api_function.accepts[0], Auth) or \
182                 (isinstance(api_function.accepts[0], Mixed) and \
183                  filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
184                 auth = self.auth
185             else:
186                 auth = None
187
188             callable = Callable(self, method, func, auth)
189
190             # Add to ourself and the global environment. Add dummy
191             # subattributes to support tab completion of methods with
192             # dots in their names (e.g., system.listMethods).
193             class Dummy: pass
194             paths = method.split(".")
195             if len(paths) > 1:
196                 first = paths.pop(0)
197
198                 if not hasattr(self, first):
199                     obj = Dummy()
200                     setattr(self, first, obj)
201                     # Also add to global environment if specified
202                     if globals is not None:
203                         globals[first] = obj
204
205                 obj = getattr(self, first)
206
207                 for path in paths:
208                     if not hasattr(obj, path):
209                         if path == paths[-1]:
210                             setattr(obj, path, callable)
211                         else:
212                             setattr(obj, path, Dummy())
213                     obj = getattr(obj, path)
214             else:
215                 setattr(self, method, callable)
216                 # Also add to global environment if specified
217                 if globals is not None:
218                     globals[method] = callable
219
220         # Override help(), begin(), and commit()
221         if globals is not None:
222             globals['help'] = self.help
223             globals['begin'] = self.begin
224             globals['commit'] = self.commit
225
226         # Multicall support
227         self.calls = []
228         self.multi = False
229
230     def help(self, topic = None):
231         if isinstance(topic, Callable):
232             pydoc.pager(self.system.methodHelp(topic.name))
233         else:
234             pydoc.help(topic)
235
236     def begin(self):
237         if self.calls:
238             raise Exception, "multicall already in progress"
239
240         self.multi = True
241
242     def commit(self):
243         if self.calls:
244             ret = []
245             self.multi = False
246             results = self.system.multicall(self.calls)
247             for result in results:
248                 if type(result) == type({}):
249                     raise xmlrpclib.Fault(result['faultCode'], result['faultString'])
250                 elif type(result) == type([]):
251                     ret.append(result[0])
252                 else:
253                     raise ValueError, "unexpected type in multicall result"
254         else:
255             ret = None
256
257         self.calls = []
258         self.multi = False
259
260         return ret