3 # Interactive shell for testing PLCAPI
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 # Copyright (C) 2005 The Trustees of Princeton University
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
25 Wrapper to call a method either directly or remotely and
26 automagically add the authentication structure if necessary.
29 def __init__(self, shell, name, func, auth = None):
35 def __call__(self, *args, **kwds):
37 Automagically add the authentication structure if the function
38 requires it and it has not been specified.
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
48 self.shell.calls.append({'methodName': self.name, 'params': list(args)})
51 return self.func(*args, **kwds)
55 # Add API functions to global scope
60 url = None, xmlrpc = False, cacert = None,
61 # API authentication method
63 # Password authentication
64 role = None, user = None, password = None,
65 # Session authentication
68 Initialize a new shell instance. Re-initializes globals.
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):
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
86 self.api = PLCAPI(config)
87 self.config = self.api.config
90 except Exception, err:
91 # Try connecting to the API server via XML-RPC
92 self.api = PLCAPI(None)
96 self.config = Config()
98 self.config = Config(config)
99 except Exception, err:
100 # Try to continue if no configuration file is available
104 if self.config is None:
105 raise Exception, "Must specify API URL"
107 url = "https://" + self.config.PLC_API_HOST + \
108 ":" + str(self.config.PLC_API_PORT) + \
109 "/" + self.config.PLC_API_PATH + "/"
112 cacert = self.config.PLC_API_CA_SSL_CRT
115 if cacert is not None:
116 self.server = xmlrpclib.ServerProxy(url, PyCurlTransport(url, cacert), allow_none = 1)
118 self.server = xmlrpclib.ServerProxy(url, allow_none = 1)
120 # Set up authentication structure
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"):
127 session = "/etc/planetlab/session"
129 method = "capability"
131 if method == "capability":
132 # Load defaults from configuration file if using capability
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
141 # Otherwise, default to password authentication
144 if role == "anonymous" or method == "anonymous":
145 self.auth = {'AuthMethod': "anonymous"}
146 elif method == "session":
148 raise Exception, "Must specify session"
150 if os.path.exists(session):
151 session = file(session).read()
153 self.auth = {'AuthMethod': "session", 'session': session}
156 raise Exception, "Must specify username"
159 raise Exception, "Must specify password"
161 self.auth = {'AuthMethod': method,
163 'AuthString': password}
166 self.auth['Role'] = role
168 for method in PLC.Methods.methods:
169 api_function = self.api.callable(method)
171 if self.server is None:
172 # Can just call it directly
175 func = getattr(self.server, method)
177 # If the function requires an authentication structure as
178 # its first argument, automagically add an auth struct to
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]))):
188 callable = Callable(self, method, func, auth)
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).
194 paths = method.split(".")
198 if not hasattr(self, first):
200 setattr(self, first, obj)
201 # Also add to global environment if specified
202 if globals is not None:
205 obj = getattr(self, first)
208 if not hasattr(obj, path):
209 if path == paths[-1]:
210 setattr(obj, path, callable)
212 setattr(obj, path, Dummy())
213 obj = getattr(obj, path)
215 setattr(self, method, callable)
216 # Also add to global environment if specified
217 if globals is not None:
218 globals[method] = callable
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
230 def help(self, topic = None):
231 if isinstance(topic, Callable):
232 pydoc.pager(self.system.methodHelp(topic.name))
238 raise Exception, "multicall already in progress"
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])
253 raise ValueError, "unexpected type in multicall result"