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 extrac the documentation from each
36 # exported function. A better approach will involve more extensive code
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 sm.py: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 return ''.join([method.__doc__ + '\n' for method in api_method_dict.itervalues()])
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,
118 data = ticket_module.verify(ticket)
119 name = data['slivers'][0]['name']
122 logger.log('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().
135 data, = xmlrpclib.loads(ticket)[0]
136 name = data['slivers'][0]['name']
139 logger.log('Admin Ticket delivered for %s' % name)
140 Create(database.db.get(name))
141 except Exception, err:
142 raise xmlrpclib.Fault(102, 'Ticket error: ' + str(err))
145 @export_to_docbook(roles=['self'],
147 returns={'sliver_name' : Parameter(int, 'the associated xid')})
150 """Return an dictionary mapping Slice names to XIDs"""
151 return dict([(pwent[0], pwent[2]) for pwent in pwd.getpwall() if pwent[6] == sliver_vs.Sliver_VS.SHELL])
153 @export_to_docbook(roles=['self'],
155 returns={ 'sliver_name' : Parameter(str, 'the associated SSHKey')})
158 """Return an dictionary mapping slice names to SSH keys"""
160 for rec in database.db.itervalues():
162 keydict[rec['name']] = rec['keys']
166 @export_to_docbook(roles=['nm-controller', 'self'],
167 accepts=[Parameter(str, 'A sliver/slice name.')],
168 returns=Parameter(int, '1 if successful'))
170 def Create(sliver_name):
171 """Create a non-PLC-instantiated sliver"""
173 if rec['instantiation'] == 'delegated': accounts.get(rec['name']).ensure_created(rec)
174 else: raise Exception, "Only PLC can create non delegated slivers."
177 @export_to_docbook(roles=['nm-controller', 'self'],
178 accepts=[Parameter(str, 'A sliver/slice name.')],
179 returns=Parameter(int, '1 if successful'))
181 def Destroy(sliver_name):
182 """Destroy a non-PLC-instantiated sliver"""
184 if rec['instantiation'] == 'delegated': accounts.get(rec['name']).ensure_destroyed()
185 else: raise Exception, "Only PLC can destroy non delegated slivers."
188 @export_to_docbook(roles=['nm-controller', 'self'],
189 accepts=[Parameter(str, 'A sliver/slice name.')],
190 returns=Parameter(int, '1 if successful'))
192 def Start(sliver_name):
193 """Configure and start sliver."""
195 accounts.get(rec['name']).start(rec)
198 @export_to_docbook(roles=['nm-controller', 'self'],
199 accepts=[Parameter(str, 'A sliver/slice name.')],
200 returns=Parameter(int, '1 if successful'))
202 def Stop(sliver_name):
203 """Kill all processes belonging to the specified sliver"""
205 accounts.get(rec['name']).stop()
208 @export_to_docbook(roles=['nm-controller', 'self'],
209 accepts=[Parameter(str, 'A sliver/slice name.')],
210 returns=Parameter(int, '1 if successful'))
212 def ReCreate(sliver_name):
213 """Stop, Destroy, Create, Start sliver in order to reinstall it."""
215 accounts.get(rec['name']).stop()
216 accounts.get(rec['name']).ensure_created(rec)
217 accounts.get(rec['name']).start(rec)
220 @export_to_docbook(roles=['nm-controller', 'self'],
221 accepts=[Parameter(str, 'A sliver/slice name.')],
222 returns=Parameter(dict, "A resource specification"))
224 def GetEffectiveRSpec(sliver_name):
225 """Return the RSpec allocated to the specified sliver, including loans"""
227 return rec.get('_rspec', {}).copy()
230 @export_to_docbook(roles=['nm-controller', 'self'],
231 accepts=[Parameter(str, 'A sliver/slice name.')],
232 returns={"resource name" : Parameter(int, "amount")})
234 def GetRSpec(sliver_name):
235 """Return the RSpec allocated to the specified sliver, excluding loans"""
237 return rec.get('rspec', {}).copy()
240 @export_to_docbook(roles=['nm-controller', 'self'],
241 accepts=[Parameter(str, 'A sliver/slice name.')],
242 returns=[Mixed(Parameter(str, 'recipient slice name'),
243 Parameter(str, 'resource name'),
244 Parameter(int, 'resource amount'))])
247 def GetLoans(sliver_name):
248 """Return the list of loans made by the specified sliver"""
250 return rec.get('_loans', [])[:]
252 def validate_loans(obj):
253 """Check that <obj> is a valid loan specification."""
254 def validate_loan(obj): return (type(obj)==list or type(obj)==tuple) and len(obj)==3 and type(obj[0])==str and type(obj[1])==str and obj[1] in database.LOANABLE_RESOURCES and type(obj[2])==int and obj[2]>=0
255 return type(obj)==list and False not in map(validate_loan, obj)
257 @export_to_docbook(roles=['nm-controller', 'self'],
258 accepts=[ Parameter(str, 'A sliver/slice name.'),
259 [Mixed(Parameter(str, 'recipient slice name'),
260 Parameter(str, 'resource name'),
261 Parameter(int, 'resource amount'))] ],
262 returns=Parameter(int, '1 if successful'))
264 def SetLoans(sliver_name, loans):
265 """Overwrite the list of loans made by the specified sliver.
267 Also, note that SetLoans will not throw an error if more capacity than the
268 RSpec is handed out, but it will silently discard those loans that would
269 put it over capacity. This behavior may be replaced with error semantics
270 in the future. As well, there is currently no asynchronous notification
271 of loss of resources.
274 if not validate_loans(loans): raise xmlrpclib.Fault(102, 'Invalid argument: the second argument must be a well-formed loan specification')
275 rec['_loans'] = loans
278 @export_to_docbook(roles=['nm-controller', 'self'],
279 returns=Parameter(dict, 'Record dictionary'))
281 def GetRecord(sliver_name):
282 """Return sliver record"""