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