6 This module exposes an XMLRPC interface that allows PlanetLab users to
7 create/destroy slivers with delegated instantiation, start and stop
8 slivers, make resource loans, and examine resource allocations. The
9 XMLRPC is provided on a localhost-only TCP port as well as via a Unix
10 domain socket that is accessible by ssh-ing into a delegate account
11 with the forward_api_calls shell.
14 import SimpleXMLRPCServer
25 from PLC.Parameter import Parameter, Mixed
27 def Parameter(a = None, b = None): pass
28 def Mixed(a = None, b = None, c = None): pass
34 # TODO: These try/excepts are a hack to allow doc/DocBookLocal.py to
35 # import this file in order to extract the documentation from each
37 # A better approach will involve more extensive code splitting, I think.
39 except: import logger as database
41 except: import logger as sliver_vs
42 import ticket as ticket_module
45 deliver_ticket = None # set in slivermanager.start()
50 def export_to_api(nargs):
52 nargs_dict[method.__name__] = nargs
53 api_method_dict[method.__name__] = method
57 def export_to_docbook(**kwargs):
70 # Inspect method. Remove self from the argument list.
71 max_args = method.func_code.co_varnames[0:method.func_code.co_argcount]
72 defaults = method.func_defaults
75 min_args = max_args[0:len(max_args) - len(defaults)]
77 defaults = tuple([None for arg in min_args]) + defaults
78 return (min_args, max_args, defaults)
80 keywords['name'] = method.__name__
81 keywords['args'] = args
83 method.__setattr__(arg, keywords[arg])
86 method.__setattr__(arg, kwargs[arg])
97 @export_to_docbook(roles=['self'],
99 returns=Parameter([], 'A list of supported functions'))
102 """Get a list of functions currently supported by the Node Manager API"""
103 names=api_method_dict.keys()
105 return ''.join(['**** ' + api_method_dict[name].__name__ + '\n' + api_method_dict[name].__doc__ + '\n'
108 @export_to_docbook(roles=['self'],
109 accepts=[Parameter(str, 'A ticket returned from GetSliceTicket()')],
110 returns=Parameter(int, '1 if successful'))
113 """The Node Manager periodically polls the PLC API for a list of all
114 slices that are allowed to exist on the given node. Before
115 actions are performed on a delegated slice (such as creation),
116 a controller slice must deliver a valid slice ticket to NM.
118 This ticket is the value retured by PLC's GetSliceTicket() API call."""
120 data = ticket_module.verify(ticket)
121 name = data['slivers'][0]['name']
124 logger.log('api_calls: Ticket delivered for %s' % name)
125 Create(database.db.get(name))
126 except Exception, err:
127 raise xmlrpclib.Fault(102, 'Ticket error: ' + str(err))
129 @export_to_docbook(roles=['self'],
130 accepts=[Parameter(str, 'A ticket returned from GetSlivers()')],
131 returns=Parameter(int, '1 if successful'))
133 def AdminTicket(ticket):
134 """Admin interface to create slivers based on ticket returned by GetSlivers()."""
136 data, = xmlrpclib.loads(ticket)[0]
137 name = data['slivers'][0]['name']
140 logger.log('api_calls: Admin Ticket delivered for %s' % name)
141 Create(database.db.get(name))
142 except Exception, err:
143 raise xmlrpclib.Fault(102, 'Ticket error: ' + str(err))
146 @export_to_docbook(roles=['self'],
148 returns={'sliver_name' : Parameter(int, 'the associated xid')})
151 """Return an dictionary mapping Slice names to XIDs"""
152 return dict([(pwent[0], pwent[2]) for pwent in pwd.getpwall() if pwent[6] == sliver_vs.Sliver_VS.SHELL])
154 @export_to_docbook(roles=['self'],
156 returns={ 'sliver_name' : Parameter(str, 'the associated SSHKey')})
159 """Return an dictionary mapping slice names to SSH keys"""
161 for rec in database.db.itervalues():
163 keydict[rec['name']] = rec['keys']
167 @export_to_docbook(roles=['nm-controller', 'self'],
168 accepts=[Parameter(str, 'A sliver/slice name.')],
169 returns=Parameter(int, '1 if successful'))
171 def Create(sliver_name):
172 """Create a non-PLC-instantiated sliver"""
174 if rec['instantiation'] == 'delegated':
175 accounts.get(rec['name']).ensure_created(rec)
176 logger.log("api_calls: Create %s"%rec['name'])
178 raise Exception, "Only PLC can create non delegated slivers."
181 @export_to_docbook(roles=['nm-controller', 'self'],
182 accepts=[Parameter(str, 'A sliver/slice name.')],
183 returns=Parameter(int, '1 if successful'))
185 def Destroy(sliver_name):
186 """Destroy a non-PLC-instantiated sliver"""
188 if rec['instantiation'] == 'delegated':
189 accounts.get(rec['name']).ensure_destroyed()
190 logger.log("api_calls: Destroy %s"%rec['name'])
192 raise Exception, "Only PLC can destroy non delegated slivers."
195 @export_to_docbook(roles=['nm-controller', 'self'],
196 accepts=[Parameter(str, 'A sliver/slice name.')],
197 returns=Parameter(int, '1 if successful'))
199 def Start(sliver_name):
200 """Configure and start sliver."""
202 accounts.get(rec['name']).start(rec)
203 logger.log("api_calls: Start %s"%rec['name'])
206 @export_to_docbook(roles=['nm-controller', 'self'],
207 accepts=[Parameter(str, 'A sliver/slice name.')],
208 returns=Parameter(int, '1 if successful'))
210 def Stop(sliver_name):
211 """Kill all processes belonging to the specified sliver"""
213 accounts.get(rec['name']).stop()
214 logger.log("api_calls: Stop %s"%rec['name'])
217 @export_to_docbook(roles=['nm-controller', 'self'],
218 accepts=[Parameter(str, 'A sliver/slice name.')],
219 returns=Parameter(int, '1 if successful'))
221 def ReCreate(sliver_name):
222 """Stop, Destroy, Create, Start sliver in order to reinstall it."""
224 accounts.get(rec['name']).stop()
225 accounts.get(rec['name']).ensure_created(rec)
226 accounts.get(rec['name']).start(rec)
227 logger.log("api_calls: ReCreate %s"%rec['name'])
229 @export_to_docbook(roles=['nm-controller', 'self'],
230 accepts=[Parameter(str, 'A sliver/slice name.')],
231 returns=Parameter(dict, "A resource specification"))
233 def GetEffectiveRSpec(sliver_name):
234 """Return the RSpec allocated to the specified sliver, including loans"""
236 return rec.get('_rspec', {}).copy()
239 @export_to_docbook(roles=['nm-controller', 'self'],
240 accepts=[Parameter(str, 'A sliver/slice name.')],
241 returns={"resource name" : Parameter(int, "amount")})
243 def GetRSpec(sliver_name):
244 """Return the RSpec allocated to the specified sliver, excluding loans"""
246 return rec.get('rspec', {}).copy()
249 @export_to_docbook(roles=['nm-controller', 'self'],
250 accepts=[Parameter(str, 'A sliver/slice name.')],
251 returns=[Mixed(Parameter(str, 'recipient slice name'),
252 Parameter(str, 'resource name'),
253 Parameter(int, 'resource amount'))])
256 def GetLoans(sliver_name):
257 """Return the list of loans made by the specified sliver"""
259 return rec.get('_loans', [])[:]
261 def validate_loans(loans):
262 """Check that <obj> is a list of valid loan specifications."""
263 def validate_loan(loan):
264 return (type(loan)==list or type(loan)==tuple) and len(loan)==3 \
265 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
266 return type(loans)==list and False not in [validate_loan(load) for loan in loans]
269 @export_to_docbook(roles=['nm-controller', 'self'],
270 accepts=[ Parameter(str, 'A sliver/slice name.'),
271 [Mixed(Parameter(str, 'recipient slice name'),
272 Parameter(str, 'resource name'),
273 Parameter(int, 'resource amount'))], ],
274 returns=Parameter(int, '1 if successful'))
276 def SetLoans(sliver_name, loans):
277 """Overwrite the list of loans made by the specified sliver.
279 Also, note that SetLoans will not throw an error if more capacity than the
280 RSpec is handed out, but it will silently discard those loans that would
281 put it over capacity. This behavior may be replaced with error semantics
282 in the future. As well, there is currently no asynchronous notification
283 of loss of resources."""
285 if not validate_loans(loans):
286 raise xmlrpclib.Fault(102, 'Invalid argument: the second argument must be a well-formed loan specification')
287 rec['_loans'] = loans
290 @export_to_docbook(roles=['nm-controller', 'self'],
291 returns=Parameter(dict, 'Record dictionary'))
293 def GetRecord(sliver_name):
294 """Return sliver record"""