--- /dev/null
+#!/usr/bin/python
+#
+# Interactive shell for testing PLCAPI
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# 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