4 This module exposes an XMLRPC interface that allows PlanetLab users to
5 create/destroy slivers with delegated instantiation, start and stop
6 slivers, make resource loans, and examine resource allocations. The
7 XMLRPC is provided on a localhost-only TCP port as well as via a Unix
8 domain socket that is accessible by ssh-ing into a delegate account
9 with the forward_api_calls shell.
12 import SimpleXMLRPCServer
24 from PLC.Parameter import Parameter, Mixed
26 def Parameter(a = None, b = None): pass
27 def Mixed(a = None, b = None, c = None): pass
33 # TODO: These try/excepts are a hack to allow doc/DocBookLocal.py to
34 # import this file in order to extract the documentation from each
36 # A better approach will involve more extensive code splitting, I think.
38 except: import logger as database
39 import ticket as ticket_module
42 deliver_ticket = None # set in slivermanager.start()
47 def export_to_api(nargs):
49 nargs_dict[method.__name__] = nargs
50 api_method_dict[method.__name__] = method
54 def export_to_docbook(**kwargs):
67 # Inspect method. Remove self from the argument list.
68 max_args = method.func_code.co_varnames[0:method.func_code.co_argcount]
69 defaults = method.func_defaults
72 min_args = max_args[0:len(max_args) - len(defaults)]
74 defaults = tuple([None for arg in min_args]) + defaults
75 return (min_args, max_args, defaults)
77 keywords['name'] = method.__name__
78 keywords['args'] = args
80 method.__setattr__(arg, keywords[arg])
83 method.__setattr__(arg, kwargs[arg])
94 @export_to_docbook(roles=['self'],
96 returns=Parameter([], 'A list of supported functions'))
99 """Get a list of functions currently supported by the Node Manager API"""
100 names=api_method_dict.keys()
102 return ''.join(['**** ' + api_method_dict[name].__name__ + '\n' + api_method_dict[name].__doc__ + '\n'
105 @export_to_docbook(roles=['self'],
106 accepts=[Parameter(str, 'A ticket returned from GetSliceTicket()')],
107 returns=Parameter(int, '1 if successful'))
110 """The Node Manager periodically polls the PLC API for a list of all
111 slices that are allowed to exist on the given node. Before
112 actions are performed on a delegated slice (such as creation),
113 a controller slice must deliver a valid slice ticket to NM.
115 This ticket is the value retured by PLC's GetSliceTicket() API call."""
117 data = ticket_module.verify(ticket)
118 name = data['slivers'][0]['name']
121 logger.log('api_calls: Ticket delivered for %s' % name)
122 Create(database.db.get(name))
123 except Exception, err:
124 raise xmlrpclib.Fault(102, 'Ticket error: ' + str(err))
126 @export_to_docbook(roles=['self'],
127 accepts=[Parameter(str, 'A ticket returned from GetSlivers()')],
128 returns=Parameter(int, '1 if successful'))
130 def AdminTicket(ticket):
131 """Admin interface to create slivers based on ticket returned by GetSlivers()."""
133 data, = xmlrpclib.loads(ticket)[0]
134 name = data['slivers'][0]['name']
137 logger.log('api_calls: Admin Ticket delivered for %s' % name)
138 Create(database.db.get(name))
139 except Exception, err:
140 raise xmlrpclib.Fault(102, 'Ticket error: ' + str(err))
143 @export_to_docbook(roles=['self'],
145 returns={'sliver_name' : Parameter(int, 'the associated xid')})
148 """Return an dictionary mapping Slice names to XIDs"""
149 return dict([(pwent[0], pwent[2]) for pwent in pwd.getpwall() if pwent[6] == slivermanager.sliver_password_shell])
151 @export_to_docbook(roles=['self'],
153 returns={ 'sliver_name' : Parameter(str, 'the associated SSHKey')})
156 """Return an dictionary mapping slice names to SSH keys"""
158 for rec in database.db.itervalues():
160 keydict[rec['name']] = rec['keys']
164 @export_to_docbook(roles=['nm-controller', 'self'],
165 accepts=[Parameter(str, 'A sliver/slice name.')],
166 returns=Parameter(int, '1 if successful'))
168 def Create(sliver_name):
169 """Create a non-PLC-instantiated sliver"""
171 if rec['instantiation'] == 'delegated':
172 account.get(rec['name']).ensure_created(rec)
173 logger.log("api_calls: Create %s"%rec['name'])
175 raise Exception, "Only PLC can create non delegated slivers."
178 @export_to_docbook(roles=['nm-controller', 'self'],
179 accepts=[Parameter(str, 'A sliver/slice name.')],
180 returns=Parameter(int, '1 if successful'))
182 def Destroy(sliver_name):
183 """Destroy a non-PLC-instantiated sliver"""
185 if rec['instantiation'] == 'delegated':
186 account.get(rec['name']).ensure_destroyed()
187 logger.log("api_calls: Destroy %s"%rec['name'])
189 raise Exception, "Only PLC can destroy non delegated slivers."
192 @export_to_docbook(roles=['nm-controller', 'self'],
193 accepts=[Parameter(str, 'A sliver/slice name.')],
194 returns=Parameter(int, '1 if successful'))
196 def Start(sliver_name):
197 """Configure and start sliver."""
199 account.get(rec['name']).start(rec)
200 logger.log("api_calls: Start %s"%rec['name'])
203 @export_to_docbook(roles=['nm-controller', 'self'],
204 accepts=[Parameter(str, 'A sliver/slice name.')],
205 returns=Parameter(int, '1 if successful'))
207 def Stop(sliver_name):
208 """Kill all processes belonging to the specified sliver"""
210 account.get(rec['name']).stop()
211 logger.log("api_calls: Stop %s"%rec['name'])
214 @export_to_docbook(roles=['nm-controller', 'self'],
215 accepts=[Parameter(str, 'A sliver/slice name.')],
216 returns=Parameter(int, '1 if successful'))
218 def ReCreate(sliver_name):
219 """Stop, Destroy, Create, Start sliver in order to reinstall it."""
221 account.get(rec['name']).stop()
222 account.get(rec['name']).ensure_created(rec)
223 account.get(rec['name']).start(rec)
224 logger.log("api_calls: ReCreate %s"%rec['name'])
226 @export_to_docbook(roles=['nm-controller', 'self'],
227 accepts=[Parameter(str, 'A sliver/slice name.')],
228 returns=Parameter(dict, "A resource specification"))
230 def GetEffectiveRSpec(sliver_name):
231 """Return the RSpec allocated to the specified sliver, including loans"""
233 return rec.get('_rspec', {}).copy()
236 @export_to_docbook(roles=['nm-controller', 'self'],
237 accepts=[Parameter(str, 'A sliver/slice name.')],
238 returns={"resource name" : Parameter(int, "amount")})
240 def GetRSpec(sliver_name):
241 """Return the RSpec allocated to the specified sliver, excluding loans"""
243 return rec.get('rspec', {}).copy()
246 @export_to_docbook(roles=['nm-controller', 'self'],
247 accepts=[Parameter(str, 'A sliver/slice name.')],
248 returns=[Mixed(Parameter(str, 'recipient slice name'),
249 Parameter(str, 'resource name'),
250 Parameter(int, 'resource amount'))])
253 def GetLoans(sliver_name):
254 """Return the list of loans made by the specified sliver"""
256 return rec.get('_loans', [])[:]
258 def validate_loans(loans):
259 """Check that <obj> is a list of valid loan specifications."""
260 def validate_loan(loan):
261 return (type(loan)==list or type(loan)==tuple) and len(loan)==3 \
262 and type(loan[0])==str and type(loan[1])==str and loan[1] in database.LOANABLE_RESOURCES and type(loan[2])==int and loan[2]>=0
263 return type(loans)==list and False not in [validate_loan(load) for loan in loans]
266 @export_to_docbook(roles=['nm-controller', 'self'],
267 accepts=[ Parameter(str, 'A sliver/slice name.'),
268 [Mixed(Parameter(str, 'recipient slice name'),
269 Parameter(str, 'resource name'),
270 Parameter(int, 'resource amount'))], ],
271 returns=Parameter(int, '1 if successful'))
273 def SetLoans(sliver_name, loans):
274 """Overwrite the list of loans made by the specified sliver.
276 Also, note that SetLoans will not throw an error if more capacity than the
277 RSpec is handed out, but it will silently discard those loans that would
278 put it over capacity. This behavior may be replaced with error semantics
279 in the future. As well, there is currently no asynchronous notification
280 of loss of resources."""
282 if not validate_loans(loans):
283 raise xmlrpclib.Fault(102, 'Invalid argument: the second argument must be a well-formed loan specification')
284 rec['_loans'] = loans
287 @export_to_docbook(roles=['nm-controller', 'self'],
288 returns=Parameter(dict, 'Record dictionary'))
290 def GetRecord(sliver_name):
291 """Return sliver record"""