3 # Interactive shell for testing PLCAPI
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 # Copyright (C) 2005 The Trustees of Princeton University
8 # $Id: Shell.py,v 1.17 2006/12/13 22:29:28 mlhuang Exp $
19 from PLC.API import PLCAPI
20 from PLC.Parameter import Mixed
21 from PLC.Auth import Auth
22 from PLC.Config import Config
23 from PLC.Method import Method
24 from PLC.PyCurl import PyCurlTransport
27 # the list of globals formerly defined by Shell.py before it was made a class
28 former_globals = ['api','auth','config','begin','commit','calls']
33 Override builtin help() function to support calling help(method).
37 if isinstance(thing, Shell.Callable) and thing.name is not None:
38 pydoc.pager(system.methodHelp(thing.name))
51 def __init__ (self,argv,config=None):
54 if config is not None:
57 # support running on non-myplc boxes
58 default_config_file = "/etc/planetlab/plc_config"
60 open (default_config_file).close()
62 default_config_file="/dev/null"
63 self.config = default_config_file
73 # More convenient multicall support
78 def init_from_argv (self):
81 (opts, argv) = getopt.getopt(self.argv[1:],
83 ["config=", "cfg=", "file=",
87 "password=", "pass=", "authstring=",
92 except getopt.GetoptError, err:
93 print "Error: ", err.msg
96 for (opt, optval) in opts:
97 if opt == "-f" or opt == "--config" or opt == "--cfg" or opt == "--file":
99 elif opt == "-h" or opt == "--host" or opt == "--url":
101 elif opt == "-m" or opt == "--method":
103 elif opt == "-u" or opt == "--username" or opt == "--user":
105 elif opt == "-p" or opt == "--password" or opt == "--pass" or opt == "--authstring":
106 self.password = optval
107 elif opt == "-r" or opt == "--role":
109 elif opt == "-x" or opt == "--xmlrpc":
111 elif opt == "--cacert":
113 elif opt == "--help":
114 self.usage(self.argv)
116 def usage(self,argv):
117 print "Usage: %s [OPTION]..." % argv[0]
119 print " -f, --config=FILE PLC configuration file"
120 print " -h, --url=URL API URL"
121 print " -m, --method=METHOD API authentication method"
122 print " -u, --user=EMAIL API user name"
123 print " -p, --password=STRING API password"
124 print " -r, --role=ROLE API role"
125 print " -x, --xmlrpc Use XML-RPC interface"
126 print " --cacert=CACERT API SSL certificate"
127 print " --help This message"
130 def init_connection(self):
132 # Append PLC to the system path
133 sys.path.append(os.path.dirname(os.path.realpath(self.argv[0])))
136 # If any XML-RPC options have been specified, do not try
137 # connecting directly to the DB.
138 if (self.url, self.method, self.user, self.password, self.role, self.cacert, self.xmlrpc) != \
139 (None, None, None, None, None, None, False):
142 # Otherwise, first try connecting directly to the DB. If this
143 # fails, try connecting to the API server via XML-RPC.
144 self.api = PLCAPI(self.config)
145 self.config = self.api.config
148 # Try connecting to the API server via XML-RPC
149 self.api = PLCAPI(None)
150 self.config = Config(self.config)
153 if int(self.config.PLC_API_PORT) == 443:
154 self.url = "https://"
157 self.url += self.config.PLC_API_HOST + \
158 ":" + str(self.config.PLC_API_PORT) + \
159 "/" + self.config.PLC_API_PATH + "/"
161 if self.cacert is None:
162 self.cacert = self.config.PLC_API_CA_SSL_CRT
164 self.server = xmlrpclib.ServerProxy(self.url, PyCurlTransport(self.url, self.cacert), allow_none = 1)
166 # Default is to use capability authentication
167 if (self.method, self.user, self.password) == (None, None, None):
168 self.method = "capability"
170 if self.method == "capability":
171 if self.user is None:
172 self.user = self.config.PLC_API_MAINTENANCE_USER
173 if self.password is None:
174 self.password = self.config.PLC_API_MAINTENANCE_PASSWORD
175 if self.role is None:
177 elif self.method is None:
178 self.method = "password"
180 if self.role == "anonymous" or self.method == "anonymous":
181 self.auth = {'AuthMethod': "anonymous"}
183 if self.user is None:
184 print "Error: must specify a username with -u"
187 if self.password is None:
189 self.password = getpass.getpass()
190 except (EOFError, KeyboardInterrupt):
194 self.auth = {'AuthMethod': self.method,
195 'Username': self.user,
196 'AuthString': self.password}
198 if self.role is not None:
199 self.auth['Role'] = self.role
203 raise Exception, "multicall already in progress"
212 results = system.multicall(calls)
213 for result in results:
214 if type(result) == type({}):
215 raise xmlrpclib.Fault(item['faultCode'], item['faultString'])
216 elif type(result) == type([]):
217 ret.append(result[0])
219 raise ValueError, "unexpected type in multicall result"
230 Wrapper to call a method either directly or remotely. Initialize
231 with no arguments to use as a dummy class to support tab
232 completion of API methods with dots in their names (e.g.,
236 def __init__(self, shell, method = None):
240 if method is not None:
241 # Figure out if the function requires an authentication
242 # structure as its first argument.
246 func = shell.api.callable(method)
247 if func.accepts and \
248 (isinstance(func.accepts[0], Auth) or \
249 (isinstance(func.accepts[0], Mixed) and \
250 filter(lambda param: isinstance(param, Auth), func.accepts[0]))):
253 traceback.print_exc()
254 # XXX Ignore undefined methods for now
257 if shell.server is not None:
258 self.func = getattr(shell.server, method)
262 def __call__(self, *args, **kwds):
264 Automagically add the authentication structure if the function
265 requires it and it has not been specified.
269 (not args or not isinstance(args[0], dict) or \
270 (not args[0].has_key('AuthMethod') and \
271 not args[0].has_key('session'))):
272 args = (self.shell.auth,) + args
275 self.shell.calls.append({'methodName': self.name, 'params': list(args)})
278 return self.func(*args, **kwds)
280 def init_methods (self):
281 # makes methods defined on self
282 for method in PLC.Methods.methods:
283 # ignore path-defined methods for now
284 if "." not in method:
285 setattr(self,method,Shell.Callable(self,method))
287 def init_globals (self):
288 # Define all methods in the global namespace to support tab completion
289 for method in PLC.Methods.methods:
290 paths = method.split(".")
293 if first not in globals():
294 globals()[first] = Shell.Callable(self)
295 obj = globals()[first]
297 if not hasattr(obj, path):
298 if path == paths[-1]:
299 setattr(obj, path, Shell.Callable(self,method))
301 setattr(obj, path, Shell.Callable(self))
302 obj = getattr(obj, path)
304 globals()[method] = Shell.Callable(self,method)
305 # Other stuff to be made visible in globals()
306 for slot in former_globals:
307 #print 'Inserting global',slot
308 globals()[slot] = getattr(self,slot)
310 def run_script (self):
311 # Pop us off the argument stack
313 execfile(self.argv[0],globals(),globals())
316 def show_config (self, verbose=False):
317 if self.server is None:
318 print "PlanetLab Central Direct API Access"
320 elif self.auth['AuthMethod'] == "anonymous":
321 self.prompt = "[anonymous]"
322 print "Connected anonymously"
324 self.prompt = "[%s]" % self.auth['Username']
325 print "%s connected using %s authentication" % \
326 (self.auth['Username'], self.auth['AuthMethod'])
330 print 'server',self.server
331 print 'method',self.method
332 print 'user',self.user,
333 print 'password',self.password
334 print 'role',self.role,
335 print 'xmlrpc',self.xmlrpc,
336 print 'multi',self.multi,
337 print 'calls',self.calls
339 def run_interactive (self):
340 # Readline and tab completion support
345 print 'Type "system.listMethods()" or "help(method)" for more information.'
346 # Load command history
347 history_path = os.path.join(os.environ["HOME"], ".plcapi_history")
349 file(history_path, 'a').close()
350 readline.read_history_file(history_path)
351 atexit.register(readline.write_history_file, history_path)
355 # Enable tab completion
356 readline.parse_and_bind("tab: complete")
368 line = raw_input(self.prompt + sep)
370 except KeyboardInterrupt:
375 # Build up multi-line command
378 # Blank line or first line does not end in :
379 if line == "" or (command == line and line[-1] != ':'):
382 command += os.linesep
388 elif command in ["q", "quit", "exit"]:
393 # Try evaluating as an expression and printing the result
394 result = eval(command)
395 if result is not None:
398 # Fall back to executing as a statement
400 except Exception, err:
401 traceback.print_exc()
407 # support former behaviour
409 if len(self.argv) < 2 or not os.path.exists(self.argv[1]):
410 # Parse options if called interactively
411 self.init_from_argv()
412 self.init_connection()
414 # If called by a script
415 if len(sys.argv) > 1 and os.path.exists(sys.argv[1]):
420 self.run_interactive()
422 # does not run anything, support for multi-plc, see e.g. TestPeers.py
424 self.init_from_argv()
425 self.init_connection()
428 if __name__ == '__main__':
429 Shell(sys.argv).run()