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.12 2006/11/02 22:07:22 mlhuang Exp $
19 # Append PLC to the system path
20 sys.path.append(os.path.dirname(os.path.realpath(sys.argv[0])))
22 from PLC.API import PLCAPI
23 from PLC.Parameter import Mixed
24 from PLC.Auth import Auth
25 from PLC.Config import Config
26 from PLC.Method import Method
30 config = "/etc/planetlab/plc_config"
38 if len(sys.argv) < 2 or not os.path.exists(sys.argv[1]):
39 # Parse options if called interactively
41 print "Usage: %s [OPTION]..." % sys.argv[0]
43 print " -f, --config=FILE PLC configuration file"
44 print " -h, --url=URL API URL"
45 print " -m, --method=METHOD API authentication method"
46 print " -u, --user=EMAIL API user name"
47 print " -p, --password=STRING API password"
48 print " -r, --role=ROLE API role"
49 print " -x, --xmlrpc Use XML-RPC interface"
50 print " --help This message"
54 (opts, argv) = getopt.getopt(sys.argv[1:],
56 ["config=", "cfg=", "file=",
60 "password=", "pass=", "authstring=",
64 except getopt.GetoptError, err:
65 print "Error: ", err.msg
68 for (opt, optval) in opts:
69 if opt == "-f" or opt == "--config" or opt == "--cfg" or opt == "--file":
71 elif opt == "-h" or opt == "--host" or opt == "--url":
73 elif opt == "-m" or opt == "--method":
75 elif opt == "-u" or opt == "--username" or opt == "--user":
77 elif opt == "-p" or opt == "--password" or opt == "--pass" or opt == "--authstring":
79 elif opt == "-r" or opt == "--role":
81 elif opt == "-x" or opt == "--xmlrpc":
87 # If any XML-RPC options have been specified, do not try
88 # connecting directly to the DB.
89 if (url, method, user, password, role, xmlrpc) != \
90 (None, None, None, None, None, False):
93 # Otherwise, first try connecting directly to the DB. If this
94 # fails, try connecting to the API server via XML-RPC.
99 # Try connecting to the API server via XML-RPC
101 config = Config(config)
104 if int(config.PLC_API_PORT) == 443:
108 url += config.PLC_API_HOST + \
109 ":" + str(config.PLC_API_PORT) + \
110 "/" + config.PLC_API_PATH + "/"
112 server = xmlrpclib.ServerProxy(url, allow_none = 1)
114 # Default is to use capability authentication
115 if (method, user, password) == (None, None, None):
116 method = "capability"
118 if method == "capability":
120 user = config.PLC_API_MAINTENANCE_USER
122 password = config.PLC_API_MAINTENANCE_PASSWORD
128 if role == "anonymous" or method == "anonymous":
129 auth = {'AuthMethod': "anonymous"}
132 print "Error: must specify a username with -u"
137 password = getpass.getpass()
138 except (EOFError, KeyboardInterrupt):
142 auth = {'AuthMethod': method,
144 'AuthString': password}
149 # More convenient multicall support
157 raise Exception, "multicall already in progress"
167 results = system.multicall(calls)
168 for result in results:
169 if type(result) == type({}):
170 raise xmlrpclib.Fault(item['faultCode'], item['faultString'])
171 elif type(result) == type([]):
172 ret.append(result[0])
174 raise ValueError, "unexpected type in multicall result"
185 Wrapper to call a method either directly or remotely. Initialize
186 with no arguments to use as a dummy class to support tab
187 completion of API methods with dots in their names (e.g.,
191 def __init__(self, method = None):
194 if method is not None:
195 # Figure out if the function requires an authentication
196 # structure as its first argument.
200 func = api.callable(method)
201 if func.accepts and \
202 (isinstance(func.accepts[0], Auth) or \
203 (isinstance(func.accepts[0], Mixed) and \
204 filter(lambda param: isinstance(param, Auth), func.accepts[0]))):
207 traceback.print_exc()
208 # XXX Ignore undefined methods for now
211 if server is not None:
212 self.func = getattr(server, method)
216 def __call__(self, *args, **kwds):
218 Automagically add the authentication structure if the function
219 requires it and it has not been specified.
225 (not args or not isinstance(args[0], dict) or \
226 (not args[0].has_key('AuthMethod') and \
227 not args[0].has_key('session'))):
228 args = (auth,) + args
231 calls.append({'methodName': self.name, 'params': list(args)})
234 return self.func(*args, **kwds)
236 # Define all methods in the global namespace to support tab completion
237 for method in PLC.Methods.methods:
238 paths = method.split(".")
241 if first not in globals():
242 globals()[first] = Callable()
243 obj = globals()[first]
245 if not hasattr(obj, path):
246 if path == paths[-1]:
247 setattr(obj, path, Callable(method))
249 setattr(obj, path, Callable())
250 obj = getattr(obj, path)
252 globals()[method] = Callable(method)
257 Override builtin help() function to support calling help(method).
261 if isinstance(thing, Callable) and thing.name is not None:
262 pydoc.pager(system.methodHelp(thing.name))
272 # If called by a script
273 if len(sys.argv) > 1 and os.path.exists(sys.argv[1]):
274 # Pop us off the argument stack
276 execfile(sys.argv[0])
279 # Otherwise, create an interactive shell environment
282 print "PlanetLab Central Direct API Access"
284 elif auth['AuthMethod'] == "anonymous":
285 prompt = "[anonymous]"
286 print "Connected anonymously"
288 prompt = "[%s]" % auth['Username']
289 print "%s connected using %s authentication" % \
290 (auth['Username'], auth['AuthMethod'])
291 print 'Type "system.listMethods()" or "help(method)" for more information.'
293 # Readline and tab completion support
298 # Load command history
299 history_path = os.path.join(os.environ["HOME"], ".plcapi_history")
301 file(history_path, 'a').close()
302 readline.read_history_file(history_path)
303 atexit.register(readline.write_history_file, history_path)
307 # Enable tab completion
308 readline.parse_and_bind("tab: complete")
320 line = raw_input(prompt + sep)
322 except KeyboardInterrupt:
327 # Build up multi-line command
330 # Blank line or first line does not end in :
331 if line == "" or (command == line and line[-1] != ':'):
334 command += os.linesep
340 elif command in ["q", "quit", "exit"]:
345 # Try evaluating as an expression and printing the result
346 result = eval(command)
347 if result is not None:
350 # Fall back to executing as a statement
352 except Exception, err:
353 traceback.print_exc()