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