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.14 2006/12/05 16:25:26 thierry 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
26 # the list of globals formerly defined by Shell.py before it was made a class
27 former_globals = ['api','auth','config','begin','commit','calls']
32 Override builtin help() function to support calling help(method).
36 if isinstance(thing, Shell.Callable) and thing.name is not None:
37 pydoc.pager(system.methodHelp(thing.name))
50 def __init__ (self,argv,config=None):
53 if config is not None:
56 self.config = "/etc/planetlab/plc_config"
65 # More convenient multicall support
70 def init_from_argv (self):
73 (opts, argv) = getopt.getopt(self.argv[1:],
75 ["config=", "cfg=", "file=",
79 "password=", "pass=", "authstring=",
83 except getopt.GetoptError, err:
84 print "Error: ", err.msg
87 for (opt, optval) in opts:
88 if opt == "-f" or opt == "--config" or opt == "--cfg" or opt == "--file":
90 elif opt == "-h" or opt == "--host" or opt == "--url":
92 elif opt == "-m" or opt == "--method":
94 elif opt == "-u" or opt == "--username" or opt == "--user":
96 elif opt == "-p" or opt == "--password" or opt == "--pass" or opt == "--authstring":
97 self.password = optval
98 elif opt == "-r" or opt == "--role":
100 elif opt == "-x" or opt == "--xmlrpc":
102 elif opt == "--help":
103 self.usage(self.argv)
105 def usage(self,argv):
106 print "Usage: %s [OPTION]..." % argv[0]
108 print " -f, --config=FILE PLC configuration file"
109 print " -h, --url=URL API URL"
110 print " -m, --method=METHOD API authentication method"
111 print " -u, --user=EMAIL API user name"
112 print " -p, --password=STRING API password"
113 print " -r, --role=ROLE API role"
114 print " -x, --xmlrpc Use XML-RPC interface"
115 print " --help This message"
118 def init_connection(self):
120 # Append PLC to the system path
121 sys.path.append(os.path.dirname(os.path.realpath(self.argv[0])))
124 # If any XML-RPC options have been specified, do not try
125 # connecting directly to the DB.
126 if (self.url, self.method, self.user, self.password, self.role, self.xmlrpc) != \
127 (None, None, None, None, None, False):
130 # Otherwise, first try connecting directly to the DB. If this
131 # fails, try connecting to the API server via XML-RPC.
132 self.api = PLCAPI(self.config)
133 self.config = self.api.config
136 # Try connecting to the API server via XML-RPC
137 self.api = PLCAPI(None)
138 self.config = Config(self.config)
141 if int(self.config.PLC_API_PORT) == 443:
142 self.url = "https://"
145 self.url += config.PLC_API_HOST + \
146 ":" + str(config.PLC_API_PORT) + \
147 "/" + config.PLC_API_PATH + "/"
149 self.server = xmlrpclib.ServerProxy(self.url, allow_none = 1)
151 # Default is to use capability authentication
152 if (self.method, self.user, self.password) == (None, None, None):
153 self.method = "capability"
155 if self.method == "capability":
156 if self.user is None:
157 self.user = self.config.PLC_API_MAINTENANCE_USER
158 if self.password is None:
159 self.password = self.config.PLC_API_MAINTENANCE_PASSWORD
160 if self.role is None:
162 elif self.method is None:
163 self.method = "password"
165 if self.role == "anonymous" or self.method == "anonymous":
166 self.auth = {'AuthMethod': "anonymous"}
168 if self.user is None:
169 print "Error: must specify a username with -u"
172 if self.password is None:
174 self.password = getpass.getpass()
175 except (EOFError, KeyboardInterrupt):
179 self.auth = {'AuthMethod': self.method,
180 'Username': self.user,
181 'AuthString': self.password}
183 if self.role is not None:
184 self.auth['Role'] = self.role
188 raise Exception, "multicall already in progress"
197 results = system.multicall(calls)
198 for result in results:
199 if type(result) == type({}):
200 raise xmlrpclib.Fault(item['faultCode'], item['faultString'])
201 elif type(result) == type([]):
202 ret.append(result[0])
204 raise ValueError, "unexpected type in multicall result"
215 Wrapper to call a method either directly or remotely. Initialize
216 with no arguments to use as a dummy class to support tab
217 completion of API methods with dots in their names (e.g.,
221 def __init__(self, shell, method = None):
225 if method is not None:
226 # Figure out if the function requires an authentication
227 # structure as its first argument.
231 func = shell.api.callable(method)
232 if func.accepts and \
233 (isinstance(func.accepts[0], Auth) or \
234 (isinstance(func.accepts[0], Mixed) and \
235 filter(lambda param: isinstance(param, Auth), func.accepts[0]))):
238 traceback.print_exc()
239 # XXX Ignore undefined methods for now
242 if shell.server is not None:
243 self.func = getattr(shell.server, method)
247 def __call__(self, *args, **kwds):
249 Automagically add the authentication structure if the function
250 requires it and it has not been specified.
254 (not args or not isinstance(args[0], dict) or \
255 (not args[0].has_key('AuthMethod') and \
256 not args[0].has_key('session'))):
257 args = (self.shell.auth,) + args
260 self.shell.calls.append({'methodName': self.name, 'params': list(args)})
263 return self.func(*args, **kwds)
265 def init_methods (self):
266 # makes methods defined on self
267 for method in PLC.Methods.methods:
268 # ignore path-defined methods for now
269 if "." not in method:
270 setattr(self,method,Shell.Callable(self,method))
272 def init_globals (self):
273 # Define all methods in the global namespace to support tab completion
274 for method in PLC.Methods.methods:
275 paths = method.split(".")
278 if first not in globals():
279 globals()[first] = Shell.Callable(self)
280 obj = globals()[first]
282 if not hasattr(obj, path):
283 if path == paths[-1]:
284 setattr(obj, path, Shell.Callable(self,method))
286 setattr(obj, path, Shell.Callable(self))
287 obj = getattr(obj, path)
289 globals()[method] = Shell.Callable(self,method)
290 # Other stuff to be made visible in globals()
291 for slot in former_globals:
292 #print 'Inserting global',slot
293 globals()[slot] = getattr(self,slot)
295 def run_script (self):
296 # Pop us off the argument stack
298 execfile(self.argv[0],globals(),globals())
301 def show_config (self, verbose=False):
302 if self.server is None:
303 print "PlanetLab Central Direct API Access"
305 elif self.auth['AuthMethod'] == "anonymous":
306 self.prompt = "[anonymous]"
307 print "Connected anonymously"
309 self.prompt = "[%s]" % self.auth['Username']
310 print "%s connected using %s authentication" % \
311 (self.auth['Username'], self.auth['AuthMethod'])
315 print 'server',self.server
316 print 'method',self.method
317 print 'user',self.user,
318 print 'password',self.password
319 print 'role',self.role,
320 print 'xmlrpc',self.xmlrpc,
321 print 'multi',self.multi,
322 print 'calls',self.calls
324 def run_interactive (self):
325 # Readline and tab completion support
330 print 'Type "system.listMethods()" or "help(method)" for more information.'
331 # Load command history
332 history_path = os.path.join(os.environ["HOME"], ".plcapi_history")
334 file(history_path, 'a').close()
335 readline.read_history_file(history_path)
336 atexit.register(readline.write_history_file, history_path)
340 # Enable tab completion
341 readline.parse_and_bind("tab: complete")
353 line = raw_input(self.prompt + sep)
355 except KeyboardInterrupt:
360 # Build up multi-line command
363 # Blank line or first line does not end in :
364 if line == "" or (command == line and line[-1] != ':'):
367 command += os.linesep
373 elif command in ["q", "quit", "exit"]:
378 # Try evaluating as an expression and printing the result
379 result = eval(command)
380 if result is not None:
383 # Fall back to executing as a statement
385 except Exception, err:
386 traceback.print_exc()
392 # support former behaviour
394 if len(self.argv) < 2 or not os.path.exists(self.argv[1]):
395 # Parse options if called interactively
396 self.init_from_argv()
397 self.init_connection()
399 # If called by a script
400 if len(sys.argv) > 1 and os.path.exists(sys.argv[1]):
405 self.run_interactive()
407 # does not run anything, support for multi-plc, see e.g. TestPeers.py
409 self.init_from_argv()
410 self.init_connection()
413 if __name__ == '__main__':
414 Shell(sys.argv).run()