X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=trunk%2FPLC%2FShell.py;fp=trunk%2FPLC%2FShell.py;h=6e4a77b250b620748184729b12142e5f3eed8ae6;hb=5a4c1b1278ffa01e630fde47f7c54888ed20a576;hp=0000000000000000000000000000000000000000;hpb=cee5ab52df1c9f38b6eaff2dd354cb22f59028c7;p=plcapi.git diff --git a/trunk/PLC/Shell.py b/trunk/PLC/Shell.py new file mode 100644 index 0000000..6e4a77b --- /dev/null +++ b/trunk/PLC/Shell.py @@ -0,0 +1,260 @@ +#!/usr/bin/python +# +# Interactive shell for testing PLCAPI +# +# Mark Huang +# Copyright (C) 2005 The Trustees of Princeton University +# +# $Id$ +# + +import os +import pydoc +import xmlrpclib + +from PLC.API import PLCAPI +from PLC.Parameter import Mixed +from PLC.Auth import Auth +from PLC.Config import Config +from PLC.Method import Method +from PLC.PyCurl import PyCurlTransport +import PLC.Methods + +class Callable: + """ + Wrapper to call a method either directly or remotely and + automagically add the authentication structure if necessary. + """ + + def __init__(self, shell, name, func, auth = None): + self.shell = shell + self.name = name + self.func = func + self.auth = auth + + def __call__(self, *args, **kwds): + """ + Automagically add the authentication structure if the function + requires it and it has not been specified. + """ + + if self.auth and \ + (not args or not isinstance(args[0], dict) or \ + (not args[0].has_key('AuthMethod') and \ + not args[0].has_key('session'))): + args = (self.auth,) + args + + if self.shell.multi: + self.shell.calls.append({'methodName': self.name, 'params': list(args)}) + return None + else: + return self.func(*args, **kwds) + +class Shell: + def __init__(self, + # Add API functions to global scope + globals = None, + # Configuration file + config = None, + # XML-RPC server + url = None, xmlrpc = False, cacert = None, + # API authentication method + method = None, + # Password authentication + role = None, user = None, password = None, + # Session authentication + session = None): + """ + Initialize a new shell instance. Re-initializes globals. + """ + + try: + # If any XML-RPC options have been specified, do not try + # connecting directly to the DB. + if (url, method, user, password, role, cacert, xmlrpc) != \ + (None, None, None, None, None, None, False): + raise Exception + + # Otherwise, first try connecting directly to the DB. This + # absolutely requires a configuration file; the API + # instance looks for one in a default location if one is + # not specified. If this fails, try connecting to the API + # server via XML-RPC. + if config is None: + self.api = PLCAPI() + else: + self.api = PLCAPI(config) + self.config = self.api.config + self.url = None + self.server = None + except Exception, err: + # Try connecting to the API server via XML-RPC + self.api = PLCAPI(None) + + try: + if config is None: + self.config = Config() + else: + self.config = Config(config) + except Exception, err: + # Try to continue if no configuration file is available + self.config = None + + if url is None: + if self.config is None: + raise Exception, "Must specify API URL" + + url = "https://" + self.config.PLC_API_HOST + \ + ":" + str(self.config.PLC_API_PORT) + \ + "/" + self.config.PLC_API_PATH + "/" + + if cacert is None: + cacert = self.config.PLC_API_CA_SSL_CRT + + self.url = url + if cacert is not None: + self.server = xmlrpclib.ServerProxy(url, PyCurlTransport(url, cacert), allow_none = 1) + else: + self.server = xmlrpclib.ServerProxy(url, allow_none = 1) + + # Set up authentication structure + + # Default is to use session or capability authentication + if (method, user, password) == (None, None, None): + if session is not None or os.path.exists("/etc/planetlab/session"): + method = "session" + if session is None: + session = "/etc/planetlab/session" + else: + method = "capability" + + if method == "capability": + # Load defaults from configuration file if using capability + # authentication. + if user is None and self.config is not None: + user = self.config.PLC_API_MAINTENANCE_USER + if password is None and self.config is not None: + password = self.config.PLC_API_MAINTENANCE_PASSWORD + if role is None: + role = "admin" + elif method is None: + # Otherwise, default to password authentication + method = "password" + + if role == "anonymous" or method == "anonymous": + self.auth = {'AuthMethod': "anonymous"} + elif method == "session": + if session is None: + raise Exception, "Must specify session" + + if os.path.exists(session): + session = file(session).read() + + self.auth = {'AuthMethod': "session", 'session': session} + else: + if user is None: + raise Exception, "Must specify username" + + if password is None: + raise Exception, "Must specify password" + + self.auth = {'AuthMethod': method, + 'Username': user, + 'AuthString': password} + + if role is not None: + self.auth['Role'] = role + + for method in PLC.Methods.methods: + api_function = self.api.callable(method) + + if self.server is None: + # Can just call it directly + func = api_function + else: + func = getattr(self.server, method) + + # If the function requires an authentication structure as + # its first argument, automagically add an auth struct to + # the call. + if api_function.accepts and \ + (isinstance(api_function.accepts[0], Auth) or \ + (isinstance(api_function.accepts[0], Mixed) and \ + filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))): + auth = self.auth + else: + auth = None + + callable = Callable(self, method, func, auth) + + # Add to ourself and the global environment. Add dummy + # subattributes to support tab completion of methods with + # dots in their names (e.g., system.listMethods). + class Dummy: pass + paths = method.split(".") + if len(paths) > 1: + first = paths.pop(0) + + if not hasattr(self, first): + obj = Dummy() + setattr(self, first, obj) + # Also add to global environment if specified + if globals is not None: + globals[first] = obj + + obj = getattr(self, first) + + for path in paths: + if not hasattr(obj, path): + if path == paths[-1]: + setattr(obj, path, callable) + else: + setattr(obj, path, Dummy()) + obj = getattr(obj, path) + else: + setattr(self, method, callable) + # Also add to global environment if specified + if globals is not None: + globals[method] = callable + + # Override help(), begin(), and commit() + if globals is not None: + globals['help'] = self.help + globals['begin'] = self.begin + globals['commit'] = self.commit + + # Multicall support + self.calls = [] + self.multi = False + + def help(self, topic = None): + if isinstance(topic, Callable): + pydoc.pager(self.system.methodHelp(topic.name)) + else: + pydoc.help(topic) + + def begin(self): + if self.calls: + raise Exception, "multicall already in progress" + + self.multi = True + + def commit(self): + if self.calls: + ret = [] + self.multi = False + results = self.system.multicall(self.calls) + for result in results: + if type(result) == type({}): + raise xmlrpclib.Fault(result['faultCode'], result['faultString']) + elif type(result) == type([]): + ret.append(result[0]) + else: + raise ValueError, "unexpected type in multicall result" + else: + ret = None + + self.calls = [] + self.multi = False + + return ret