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
23 from PLC.Parameter import Parameter, Mixed
25 def Parameter(a = None, b = None): pass
26 def Mixed(a = None, b = None, c = None): pass
32 # TODO: These try/excepts are a hack to allow doc/DocBookLocal.py to
33 # import this file in order to extract the documentation from each
35 # A better approach will involve more extensive code splitting, I think.
37 except: import logger as database
39 except: import logger as sliver_vs
40 import ticket as ticket_module
43 deliver_ticket = None # set in slivermanager.start()
48 def export_to_api(nargs):
50 nargs_dict[method.__name__] = nargs
51 api_method_dict[method.__name__] = method
55 def export_to_docbook(**kwargs):
68 # Inspect method. Remove self from the argument list.
69 max_args = method.func_code.co_varnames[0:method.func_code.co_argcount]
70 defaults = method.func_defaults
73 min_args = max_args[0:len(max_args) - len(defaults)]
75 defaults = tuple([None for arg in min_args]) + defaults
76 return (min_args, max_args, defaults)
78 keywords['name'] = method.__name__
79 keywords['args'] = args
81 method.__setattr__(arg, keywords[arg])
84 method.__setattr__(arg, kwargs[arg])
95 @export_to_docbook(roles=['self'],
97 returns=Parameter([], 'A list of supported functions'))
100 """Get a list of functions currently supported by the Node Manager API"""
101 names=api_method_dict.keys()
103 return ''.join(['**** ' + api_method_dict[name].__name__ + '\n' + api_method_dict[name].__doc__ + '\n'
106 @export_to_docbook(roles=['self'],
107 accepts=[Parameter(str, 'A ticket returned from GetSliceTicket()')],
108 returns=Parameter(int, '1 if successful'))
111 """The Node Manager periodically polls the PLC API for a list of all
112 slices that are allowed to exist on the given node. Before
113 actions are performed on a delegated slice (such as creation),
114 a controller slice must deliver a valid slice ticket to NM.
116 This ticket is the value retured by PLC's GetSliceTicket() API call."""
118 data = ticket_module.verify(ticket)
119 name = data['slivers'][0]['name']
122 logger.log('api_calls: Ticket delivered for %s' % name)
123 Create(database.db.get(name))
124 except Exception, err:
125 raise xmlrpclib.Fault(102, 'Ticket error: ' + str(err))
127 @export_to_docbook(roles=['self'],
128 accepts=[Parameter(str, 'A ticket returned from GetSlivers()')],
129 returns=Parameter(int, '1 if successful'))
131 def AdminTicket(ticket):
132 """Admin interface to create slivers based on ticket returned by GetSlivers()."""
134 data, = xmlrpclib.loads(ticket)[0]
135 name = data['slivers'][0]['name']
138 logger.log('api_calls: Admin Ticket delivered for %s' % name)
139 Create(database.db.get(name))
140 except Exception, err:
141 raise xmlrpclib.Fault(102, 'Ticket error: ' + str(err))
144 @export_to_docbook(roles=['self'],
146 returns={'sliver_name' : Parameter(int, 'the associated xid')})
149 """Return an dictionary mapping Slice names to XIDs"""
150 return dict([(pwent[0], pwent[2]) for pwent in pwd.getpwall() if pwent[6] == sliver_vs.Sliver_VS.SHELL])
152 @export_to_docbook(roles=['self'],
154 returns={ 'sliver_name' : Parameter(str, 'the associated SSHKey')})
157 """Return an dictionary mapping slice names to SSH keys"""
159 for rec in database.db.itervalues():
161 keydict[rec['name']] = rec['keys']
165 @export_to_docbook(roles=['nm-controller', 'self'],
166 accepts=[Parameter(str, 'A sliver/slice name.')],
167 returns=Parameter(int, '1 if successful'))
169 def Create(sliver_name):
170 """Create a non-PLC-instantiated sliver"""
172 if rec['instantiation'] == 'delegated':
173 accounts.get(rec['name']).ensure_created(rec)
174 logger.log("api_calls: Create %s"%rec['name'])
176 raise Exception, "Only PLC can create non delegated slivers."
179 @export_to_docbook(roles=['nm-controller', 'self'],
180 accepts=[Parameter(str, 'A sliver/slice name.')],
181 returns=Parameter(int, '1 if successful'))
183 def Destroy(sliver_name):
184 """Destroy a non-PLC-instantiated sliver"""
186 if rec['instantiation'] == 'delegated':
187 accounts.get(rec['name']).ensure_destroyed()
188 logger.log("api_calls: Destroy %s"%rec['name'])
190 raise Exception, "Only PLC can destroy non delegated slivers."
193 @export_to_docbook(roles=['nm-controller', 'self'],
194 accepts=[Parameter(str, 'A sliver/slice name.')],
195 returns=Parameter(int, '1 if successful'))
197 def Start(sliver_name):
198 """Configure and start sliver."""
200 accounts.get(rec['name']).start(rec)
201 logger.log("api_calls: Start %s"%rec['name'])
204 @export_to_docbook(roles=['nm-controller', 'self'],
205 accepts=[Parameter(str, 'A sliver/slice name.')],
206 returns=Parameter(int, '1 if successful'))
208 def Stop(sliver_name):
209 """Kill all processes belonging to the specified sliver"""
211 accounts.get(rec['name']).stop()
212 logger.log("api_calls: Stop %s"%rec['name'])
215 @export_to_docbook(roles=['nm-controller', 'self'],
216 accepts=[Parameter(str, 'A sliver/slice name.')],
217 returns=Parameter(int, '1 if successful'))
219 def ReCreate(sliver_name):
220 """Stop, Destroy, Create, Start sliver in order to reinstall it."""
222 accounts.get(rec['name']).stop()
223 accounts.get(rec['name']).ensure_created(rec)
224 accounts.get(rec['name']).start(rec)
225 logger.log("api_calls: ReCreate %s"%rec['name'])
227 @export_to_docbook(roles=['nm-controller', 'self'],
228 accepts=[Parameter(str, 'A sliver/slice name.')],
229 returns=Parameter(dict, "A resource specification"))
231 def GetEffectiveRSpec(sliver_name):
232 """Return the RSpec allocated to the specified sliver, including loans"""
234 return rec.get('_rspec', {}).copy()
237 @export_to_docbook(roles=['nm-controller', 'self'],
238 accepts=[Parameter(str, 'A sliver/slice name.')],
239 returns={"resource name" : Parameter(int, "amount")})
241 def GetRSpec(sliver_name):
242 """Return the RSpec allocated to the specified sliver, excluding loans"""
244 return rec.get('rspec', {}).copy()
247 @export_to_docbook(roles=['nm-controller', 'self'],
248 accepts=[Parameter(str, 'A sliver/slice name.')],
249 returns=[Mixed(Parameter(str, 'recipient slice name'),
250 Parameter(str, 'resource name'),
251 Parameter(int, 'resource amount'))])
254 def GetLoans(sliver_name):
255 """Return the list of loans made by the specified sliver"""
257 return rec.get('_loans', [])[:]
259 def validate_loans(loans):
260 """Check that <obj> is a list of valid loan specifications."""
261 def validate_loan(loan):
262 return (type(loan)==list or type(loan)==tuple) and len(loan)==3 \
263 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
264 return type(loans)==list and False not in [validate_loan(load) for loan in loans]
267 @export_to_docbook(roles=['nm-controller', 'self'],
268 accepts=[ Parameter(str, 'A sliver/slice name.'),
269 [Mixed(Parameter(str, 'recipient slice name'),
270 Parameter(str, 'resource name'),
271 Parameter(int, 'resource amount'))], ],
272 returns=Parameter(int, '1 if successful'))
274 def SetLoans(sliver_name, loans):
275 """Overwrite the list of loans made by the specified sliver.
277 Also, note that SetLoans will not throw an error if more capacity than the
278 RSpec is handed out, but it will silently discard those loans that would
279 put it over capacity. This behavior may be replaced with error semantics
280 in the future. As well, there is currently no asynchronous notification
281 of loss of resources."""
283 if not validate_loans(loans):
284 raise xmlrpclib.Fault(102, 'Invalid argument: the second argument must be a well-formed loan specification')
285 rec['_loans'] = loans
288 @export_to_docbook(roles=['nm-controller', 'self'],
289 returns=Parameter(dict, 'Record dictionary'))
291 def GetRecord(sliver_name):
292 """Return sliver record"""