3 # Interactive shell for testing PLCAPI
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 # Copyright (C) 2005 The Trustees of Princeton University
13 from PLC.API import PLCAPI
14 from PLC.Parameter import Mixed
15 from PLC.Auth import Auth
16 from PLC.Config import Config
17 from PLC.Method import Method
18 from PLC.PyCurl import PyCurlTransport
23 Wrapper to call a method either directly or remotely and
24 automagically add the authentication structure if necessary.
27 def __init__(self, shell, name, func, auth = None):
33 def __call__(self, *args, **kwds):
35 Automagically add the authentication structure if the function
36 requires it and it has not been specified.
40 (not args or not isinstance(args[0], dict) or \
41 ('AuthMethod' not in args[0] and \
42 'session' not in args[0])):
43 args = (self.auth,) + args
46 self.shell.calls.append({'methodName': self.name, 'params': list(args)})
49 return self.func(*args, **kwds)
53 # Add API functions to global scope
58 url = None, xmlrpc = False, cacert = None,
59 # API authentication method
61 # Password authentication
62 role = None, user = None, password = None,
63 # Session authentication
66 Initialize a new shell instance. Re-initializes globals.
70 # If any XML-RPC options have been specified, do not try
71 # connecting directly to the DB.
72 if (url, method, user, password, role, cacert, xmlrpc) != \
73 (None, None, None, None, None, None, False):
76 # Otherwise, first try connecting directly to the DB. This
77 # absolutely requires a configuration file; the API
78 # instance looks for one in a default location if one is
79 # not specified. If this fails, try connecting to the API
84 self.api = PLCAPI(config)
85 self.config = self.api.config
88 except Exception as err:
89 # Try connecting to the API server via XML-RPC
90 self.api = PLCAPI(None)
94 self.config = Config()
96 self.config = Config(config)
97 except Exception as err:
98 # Try to continue if no configuration file is available
102 if self.config is None:
103 raise Exception("Must specify API URL")
105 url = "https://" + self.config.PLC_API_HOST + \
106 ":" + str(self.config.PLC_API_PORT) + \
107 "/" + self.config.PLC_API_PATH + "/"
110 cacert = self.config.PLC_API_CA_SSL_CRT
113 if cacert is not None:
114 self.server = xmlrpc.client.ServerProxy(url, PyCurlTransport(url, cacert), allow_none = 1)
116 self.server = xmlrpc.client.ServerProxy(url, allow_none = 1)
118 # Set up authentication structure
120 # Default is to use session or capability authentication
121 if (method, user, password) == (None, None, None):
122 if session is not None or os.path.exists("/etc/planetlab/session"):
125 session = "/etc/planetlab/session"
127 method = "capability"
129 if method == "capability":
130 # Load defaults from configuration file if using capability
132 if user is None and self.config is not None:
133 user = self.config.PLC_API_MAINTENANCE_USER
134 if password is None and self.config is not None:
135 password = self.config.PLC_API_MAINTENANCE_PASSWORD
139 # Otherwise, default to password authentication
142 if role == "anonymous" or method == "anonymous":
143 self.auth = {'AuthMethod': "anonymous"}
144 elif method == "session":
146 raise Exception("Must specify session")
148 if os.path.exists(session):
149 with open(session) as feed:
150 session = feed.read()
152 self.auth = {'AuthMethod': "session", 'session': session}
155 raise Exception("Must specify username")
158 raise Exception("Must specify password")
160 self.auth = {'AuthMethod': method,
162 'AuthString': password}
165 self.auth['Role'] = role
167 for method in PLC.API.PLCAPI.all_methods:
168 api_function = self.api.callable(method)
170 if self.server is None:
171 # Can just call it directly
174 func = getattr(self.server, method)
176 # If the function requires an authentication structure as
177 # its first argument, automagically add an auth struct to
179 if api_function.accepts and \
180 (isinstance(api_function.accepts[0], Auth) or \
181 (isinstance(api_function.accepts[0], Mixed) and \
182 [param for param in api_function.accepts[0] if isinstance(param, Auth)])):
187 callable = Callable(self, method, func, auth)
189 # Add to ourself and the global environment. Add dummy
190 # subattributes to support tab completion of methods with
191 # dots in their names (e.g., system.listMethods).
193 paths = method.split(".")
197 if not hasattr(self, first):
199 setattr(self, first, obj)
200 # Also add to global environment if specified
201 if globals is not None:
204 obj = getattr(self, first)
207 if not hasattr(obj, path):
208 if path == paths[-1]:
209 setattr(obj, path, callable)
211 setattr(obj, path, Dummy())
212 obj = getattr(obj, path)
214 setattr(self, method, callable)
215 # Also add to global environment if specified
216 if globals is not None:
217 globals[method] = callable
219 # Override help(), begin(), and commit()
220 if globals is not None:
221 globals['help'] = self.help
222 globals['begin'] = self.begin
223 globals['commit'] = self.commit
229 def help(self, topic = None):
230 if isinstance(topic, Callable):
231 pydoc.pager(self.system.methodHelp(topic.name))
237 raise Exception("multicall already in progress")
245 results = self.system.multicall(self.calls)
246 for result in results:
247 if type(result) == type({}):
248 raise xmlrpc.client.Fault(result['faultCode'], result['faultString'])
249 elif type(result) == type([]):
250 ret.append(result[0])
252 raise ValueError("unexpected type in multicall result")