3 # Interactive shell for testing PLCAPI
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 # Copyright (C) 2005 The Trustees of Princeton University
16 from PLC.API import PLCAPI
17 from PLC.Parameter import Mixed
18 from PLC.Auth import Auth
19 from PLC.Config import Config
20 from PLC.Method import Method
21 from PLC.PyCurl import PyCurlTransport
26 Wrapper to call a method either directly or remotely and
27 automagically add the authentication structure if necessary.
30 def __init__(self, shell, name, func, auth = None):
36 def __call__(self, *args, **kwds):
38 Automagically add the authentication structure if the function
39 requires it and it has not been specified.
43 (not args or not isinstance(args[0], dict) or \
44 (not args[0].has_key('AuthMethod') and \
45 not args[0].has_key('session'))):
46 args = (self.auth,) + args
49 self.shell.calls.append({'methodName': self.name, 'params': list(args)})
52 return self.func(*args, **kwds)
56 # Add API functions to global scope
61 url = None, xmlrpc = False, cacert = None,
62 # API authentication method
64 # Password authentication
65 role = None, user = None, password = None,
66 # Session authentication
69 Initialize a new shell instance. Re-initializes globals.
73 # If any XML-RPC options have been specified, do not try
74 # connecting directly to the DB.
75 if (url, method, user, password, role, cacert, xmlrpc) != \
76 (None, None, None, None, None, None, False):
79 # Otherwise, first try connecting directly to the DB. This
80 # absolutely requires a configuration file; the API
81 # instance looks for one in a default location if one is
82 # not specified. If this fails, try connecting to the API
87 self.api = PLCAPI(config)
88 self.config = self.api.config
91 except Exception, err:
92 # Try connecting to the API server via XML-RPC
93 self.api = PLCAPI(None)
97 self.config = Config()
99 self.config = Config(config)
100 except Exception, err:
101 # Try to continue if no configuration file is available
105 if self.config is None:
106 raise Exception, "Must specify API URL"
108 url = "https://" + self.config.PLC_API_HOST + \
109 ":" + str(self.config.PLC_API_PORT) + \
110 "/" + self.config.PLC_API_PATH + "/"
113 cacert = self.config.PLC_API_CA_SSL_CRT
116 if cacert is not None:
117 self.server = xmlrpclib.ServerProxy(url, PyCurlTransport(url, cacert), allow_none = 1)
119 self.server = xmlrpclib.ServerProxy(url, allow_none = 1)
121 # Set up authentication structure
123 # Default is to use session or capability authentication
124 if (method, user, password) == (None, None, None):
125 if session is not None or os.path.exists("/etc/planetlab/session"):
128 session = "/etc/planetlab/session"
130 method = "capability"
132 if method == "capability":
133 # Load defaults from configuration file if using capability
135 if user is None and self.config is not None:
136 user = self.config.PLC_API_MAINTENANCE_USER
137 if password is None and self.config is not None:
138 password = self.config.PLC_API_MAINTENANCE_PASSWORD
142 # Otherwise, default to password authentication
145 if role == "anonymous" or method == "anonymous":
146 self.auth = {'AuthMethod': "anonymous"}
147 elif method == "session":
149 raise Exception, "Must specify session"
151 if os.path.exists(session):
152 session = file(session).read()
154 self.auth = {'AuthMethod': "session", 'session': session}
157 raise Exception, "Must specify username"
160 raise Exception, "Must specify password"
162 self.auth = {'AuthMethod': method,
164 'AuthString': password}
167 self.auth['Role'] = role
169 for method in PLC.API.PLCAPI.all_methods:
170 api_function = self.api.callable(method)
172 if self.server is None:
173 # Can just call it directly
176 func = getattr(self.server, method)
178 # If the function requires an authentication structure as
179 # its first argument, automagically add an auth struct to
181 if api_function.accepts and \
182 (isinstance(api_function.accepts[0], Auth) or \
183 (isinstance(api_function.accepts[0], Mixed) and \
184 filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
189 callable = Callable(self, method, func, auth)
191 # Add to ourself and the global environment. Add dummy
192 # subattributes to support tab completion of methods with
193 # dots in their names (e.g., system.listMethods).
195 paths = method.split(".")
199 if not hasattr(self, first):
201 setattr(self, first, obj)
202 # Also add to global environment if specified
203 if globals is not None:
206 obj = getattr(self, first)
209 if not hasattr(obj, path):
210 if path == paths[-1]:
211 setattr(obj, path, callable)
213 setattr(obj, path, Dummy())
214 obj = getattr(obj, path)
216 setattr(self, method, callable)
217 # Also add to global environment if specified
218 if globals is not None:
219 globals[method] = callable
221 # Override help(), begin(), and commit()
222 if globals is not None:
223 globals['help'] = self.help
224 globals['begin'] = self.begin
225 globals['commit'] = self.commit
231 def help(self, topic = None):
232 if isinstance(topic, Callable):
233 pydoc.pager(self.system.methodHelp(topic.name))
239 raise Exception, "multicall already in progress"
247 results = self.system.multicall(self.calls)
248 for result in results:
249 if type(result) == type({}):
250 raise xmlrpclib.Fault(result['faultCode'], result['faultString'])
251 elif type(result) == type([]):
252 ret.append(result[0])
254 raise ValueError, "unexpected type in multicall result"