3 # Interactive shell for testing PLCAPI
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 # Copyright (C) 2005 The Trustees of Princeton University
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
23 Wrapper to call a method either directly or remotely and
24 automagically add the authentication structure if necessary.
27 def __init__(self, shell, name, func, auth = None):
33 def __call__(self, *args, **kwds):
35 Automagically add the authentication structure if the function
36 requires it and it has not been specified.
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
46 self.shell.calls.append({'methodName': self.name, 'params': list(args)})
49 return self.func(*args, **kwds)
53 # Add API functions to global scope
58 url = None, xmlrpc = False, cacert = None,
59 # API authentication method
61 # Password authentication
62 role = None, user = None, password = None,
63 # Session authentication
66 Initialize a new shell instance. Re-initializes globals.
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, cacert, xmlrpc) != \
73 (None, None, None, None, None, None, False):
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
84 self.api = PLCAPI(config)
85 self.config = self.api.config
88 except Exception, err:
89 # Try connecting to the API server via XML-RPC
90 self.api = PLCAPI(None)
94 self.config = Config()
96 self.config = Config(config)
97 except Exception, err:
98 # Try to continue if no configuration file is available
102 if self.config is None:
103 raise Exception, "Must specify API URL"
105 url = "https://" + self.config.PLC_API_HOST + \
106 ":" + str(self.config.PLC_API_PORT) + \
107 "/" + self.config.PLC_API_PATH + "/"
110 cacert = self.config.PLC_API_CA_SSL_CRT
113 if cacert is not None:
114 self.server = xmlrpclib.ServerProxy(url, PyCurlTransport(url, cacert), allow_none = 1)
116 self.server = xmlrpclib.ServerProxy(url, allow_none = 1)
118 # Set up authentication structure
120 # Default is to use session or capability authentication
121 if (method, user, password) == (None, None, None):
122 if session is not None or os.path.exists("/etc/planetlab/session"):
125 session = "/etc/planetlab/session"
127 method = "capability"
129 if method == "capability":
130 # Load defaults from configuration file if using capability
132 if user is None and self.config is not None:
133 user = self.config.PLC_API_MAINTENANCE_USER
134 if password is None and self.config is not None:
135 password = self.config.PLC_API_MAINTENANCE_PASSWORD
139 # Otherwise, default to password authentication
142 if role == "anonymous" or method == "anonymous":
143 self.auth = {'AuthMethod': "anonymous"}
144 elif method == "session":
146 raise Exception, "Must specify session"
148 if os.path.exists(session):
149 session = file(session).read()
151 self.auth = {'AuthMethod': "session", 'session': session}
154 raise Exception, "Must specify username"
157 raise Exception, "Must specify password"
159 self.auth = {'AuthMethod': method,
161 'AuthString': password}
164 self.auth['Role'] = role
166 for method in PLC.API.PLCAPI.all_methods:
167 api_function = self.api.callable(method)
169 if self.server is None:
170 # Can just call it directly
173 func = getattr(self.server, method)
175 # If the function requires an authentication structure as
176 # its first argument, automagically add an auth struct to
178 if api_function.accepts and \
179 (isinstance(api_function.accepts[0], Auth) or \
180 (isinstance(api_function.accepts[0], Mixed) and \
181 filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
186 callable = Callable(self, method, func, auth)
188 # Add to ourself and the global environment. Add dummy
189 # subattributes to support tab completion of methods with
190 # dots in their names (e.g., system.listMethods).
192 paths = method.split(".")
196 if not hasattr(self, first):
198 setattr(self, first, obj)
199 # Also add to global environment if specified
200 if globals is not None:
203 obj = getattr(self, first)
206 if not hasattr(obj, path):
207 if path == paths[-1]:
208 setattr(obj, path, callable)
210 setattr(obj, path, Dummy())
211 obj = getattr(obj, path)
213 setattr(self, method, callable)
214 # Also add to global environment if specified
215 if globals is not None:
216 globals[method] = callable
218 # Override help(), begin(), and commit()
219 if globals is not None:
220 globals['help'] = self.help
221 globals['begin'] = self.begin
222 globals['commit'] = self.commit
228 def help(self, topic = None):
229 if isinstance(topic, Callable):
230 pydoc.pager(self.system.methodHelp(topic.name))
236 raise Exception, "multicall already in progress"
244 results = self.system.multicall(self.calls)
245 for result in results:
246 if type(result) == type({}):
247 raise xmlrpclib.Fault(result['faultCode'], result['faultString'])
248 elif type(result) == type([]):
249 ret.append(result[0])
251 raise ValueError, "unexpected type in multicall result"