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.15 2006/12/11 11:56:37 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 # support running on non-myplc boxes
57 default_config_file = "/etc/planetlab/plc_config"
59 open (default_config_file).close()
61 default_config_file="/dev/null"
62 self.config = default_config_file
71 # More convenient multicall support
76 def init_from_argv (self):
79 (opts, argv) = getopt.getopt(self.argv[1:],
81 ["config=", "cfg=", "file=",
85 "password=", "pass=", "authstring=",
89 except getopt.GetoptError, err:
90 print "Error: ", err.msg
93 for (opt, optval) in opts:
94 if opt == "-f" or opt == "--config" or opt == "--cfg" or opt == "--file":
96 elif opt == "-h" or opt == "--host" or opt == "--url":
98 elif opt == "-m" or opt == "--method":
100 elif opt == "-u" or opt == "--username" or opt == "--user":
102 elif opt == "-p" or opt == "--password" or opt == "--pass" or opt == "--authstring":
103 self.password = optval
104 elif opt == "-r" or opt == "--role":
106 elif opt == "-x" or opt == "--xmlrpc":
108 elif opt == "--help":
109 self.usage(self.argv)
111 def usage(self,argv):
112 print "Usage: %s [OPTION]..." % argv[0]
114 print " -f, --config=FILE PLC configuration file"
115 print " -h, --url=URL API URL"
116 print " -m, --method=METHOD API authentication method"
117 print " -u, --user=EMAIL API user name"
118 print " -p, --password=STRING API password"
119 print " -r, --role=ROLE API role"
120 print " -x, --xmlrpc Use XML-RPC interface"
121 print " --help This message"
124 def init_connection(self):
126 # Append PLC to the system path
127 sys.path.append(os.path.dirname(os.path.realpath(self.argv[0])))
130 # If any XML-RPC options have been specified, do not try
131 # connecting directly to the DB.
132 if (self.url, self.method, self.user, self.password, self.role, self.xmlrpc) != \
133 (None, None, None, None, None, False):
136 # Otherwise, first try connecting directly to the DB. If this
137 # fails, try connecting to the API server via XML-RPC.
138 self.api = PLCAPI(self.config)
139 self.config = self.api.config
142 # Try connecting to the API server via XML-RPC
143 self.api = PLCAPI(None)
144 self.config = Config(self.config)
147 if int(self.config.PLC_API_PORT) == 443:
148 self.url = "https://"
151 self.url += config.PLC_API_HOST + \
152 ":" + str(config.PLC_API_PORT) + \
153 "/" + config.PLC_API_PATH + "/"
155 self.server = xmlrpclib.ServerProxy(self.url, allow_none = 1)
157 # Default is to use capability authentication
158 if (self.method, self.user, self.password) == (None, None, None):
159 self.method = "capability"
161 if self.method == "capability":
162 if self.user is None:
163 self.user = self.config.PLC_API_MAINTENANCE_USER
164 if self.password is None:
165 self.password = self.config.PLC_API_MAINTENANCE_PASSWORD
166 if self.role is None:
168 elif self.method is None:
169 self.method = "password"
171 if self.role == "anonymous" or self.method == "anonymous":
172 self.auth = {'AuthMethod': "anonymous"}
174 if self.user is None:
175 print "Error: must specify a username with -u"
178 if self.password is None:
180 self.password = getpass.getpass()
181 except (EOFError, KeyboardInterrupt):
185 self.auth = {'AuthMethod': self.method,
186 'Username': self.user,
187 'AuthString': self.password}
189 if self.role is not None:
190 self.auth['Role'] = self.role
194 raise Exception, "multicall already in progress"
203 results = system.multicall(calls)
204 for result in results:
205 if type(result) == type({}):
206 raise xmlrpclib.Fault(item['faultCode'], item['faultString'])
207 elif type(result) == type([]):
208 ret.append(result[0])
210 raise ValueError, "unexpected type in multicall result"
221 Wrapper to call a method either directly or remotely. Initialize
222 with no arguments to use as a dummy class to support tab
223 completion of API methods with dots in their names (e.g.,
227 def __init__(self, shell, method = None):
231 if method is not None:
232 # Figure out if the function requires an authentication
233 # structure as its first argument.
237 func = shell.api.callable(method)
238 if func.accepts and \
239 (isinstance(func.accepts[0], Auth) or \
240 (isinstance(func.accepts[0], Mixed) and \
241 filter(lambda param: isinstance(param, Auth), func.accepts[0]))):
244 traceback.print_exc()
245 # XXX Ignore undefined methods for now
248 if shell.server is not None:
249 self.func = getattr(shell.server, method)
253 def __call__(self, *args, **kwds):
255 Automagically add the authentication structure if the function
256 requires it and it has not been specified.
260 (not args or not isinstance(args[0], dict) or \
261 (not args[0].has_key('AuthMethod') and \
262 not args[0].has_key('session'))):
263 args = (self.shell.auth,) + args
266 self.shell.calls.append({'methodName': self.name, 'params': list(args)})
269 return self.func(*args, **kwds)
271 def init_methods (self):
272 # makes methods defined on self
273 for method in PLC.Methods.methods:
274 # ignore path-defined methods for now
275 if "." not in method:
276 setattr(self,method,Shell.Callable(self,method))
278 def init_globals (self):
279 # Define all methods in the global namespace to support tab completion
280 for method in PLC.Methods.methods:
281 paths = method.split(".")
284 if first not in globals():
285 globals()[first] = Shell.Callable(self)
286 obj = globals()[first]
288 if not hasattr(obj, path):
289 if path == paths[-1]:
290 setattr(obj, path, Shell.Callable(self,method))
292 setattr(obj, path, Shell.Callable(self))
293 obj = getattr(obj, path)
295 globals()[method] = Shell.Callable(self,method)
296 # Other stuff to be made visible in globals()
297 for slot in former_globals:
298 #print 'Inserting global',slot
299 globals()[slot] = getattr(self,slot)
301 def run_script (self):
302 # Pop us off the argument stack
304 execfile(self.argv[0],globals(),globals())
307 def show_config (self, verbose=False):
308 if self.server is None:
309 print "PlanetLab Central Direct API Access"
311 elif self.auth['AuthMethod'] == "anonymous":
312 self.prompt = "[anonymous]"
313 print "Connected anonymously"
315 self.prompt = "[%s]" % self.auth['Username']
316 print "%s connected using %s authentication" % \
317 (self.auth['Username'], self.auth['AuthMethod'])
321 print 'server',self.server
322 print 'method',self.method
323 print 'user',self.user,
324 print 'password',self.password
325 print 'role',self.role,
326 print 'xmlrpc',self.xmlrpc,
327 print 'multi',self.multi,
328 print 'calls',self.calls
330 def run_interactive (self):
331 # Readline and tab completion support
336 print 'Type "system.listMethods()" or "help(method)" for more information.'
337 # Load command history
338 history_path = os.path.join(os.environ["HOME"], ".plcapi_history")
340 file(history_path, 'a').close()
341 readline.read_history_file(history_path)
342 atexit.register(readline.write_history_file, history_path)
346 # Enable tab completion
347 readline.parse_and_bind("tab: complete")
359 line = raw_input(self.prompt + sep)
361 except KeyboardInterrupt:
366 # Build up multi-line command
369 # Blank line or first line does not end in :
370 if line == "" or (command == line and line[-1] != ':'):
373 command += os.linesep
379 elif command in ["q", "quit", "exit"]:
384 # Try evaluating as an expression and printing the result
385 result = eval(command)
386 if result is not None:
389 # Fall back to executing as a statement
391 except Exception, err:
392 traceback.print_exc()
398 # support former behaviour
400 if len(self.argv) < 2 or not os.path.exists(self.argv[1]):
401 # Parse options if called interactively
402 self.init_from_argv()
403 self.init_connection()
405 # If called by a script
406 if len(sys.argv) > 1 and os.path.exists(sys.argv[1]):
411 self.run_interactive()
413 # does not run anything, support for multi-plc, see e.g. TestPeers.py
415 self.init_from_argv()
416 self.init_connection()
419 if __name__ == '__main__':
420 Shell(sys.argv).run()