3 This module exposes an XMLRPC interface that allows PlanetLab users to
4 create/destroy slivers with delegated instantiation, start and stop
5 slivers, make resource loans, and examine resource allocations. The
6 XMLRPC is provided on a localhost-only TCP port as well as via a Unix
7 domain socket that is accessible by ssh-ing into a delegate account
8 with the forward_api_calls shell.
11 import SimpleXMLRPCServer
22 from PLC.Parameter import Parameter, Mixed
24 def Parameter(a = None, b = None): pass
25 def Mixed(a = None, b = None, c = None): pass
31 # TODO: These try/excepts are a hack to allow doc/DocBookLocal.py to
32 # import this file in order to extrac the documentation from each
33 # exported function. A better approach will involve more extensive code
36 except: import logger as database
38 except: import logger as sliver_vs
39 import ticket as ticket_module
42 deliver_ticket = None # set in sm.py: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 return ''.join([method.__doc__ + '\n' for method in api_method_dict.itervalues()])
102 @export_to_docbook(roles=['self'],
103 accepts=[Parameter(str, 'A ticket returned from GetSliceTicket()')],
104 returns=Parameter(int, '1 if successful'))
107 """The Node Manager periodically polls the PLC API for a list of all
108 slices that are allowed to exist on the given node. Before
109 actions are performed on a delegated slice (such as creation),
110 a controller slice must deliver a valid slice ticket to NM.
112 This ticket is the value retured by PLC's GetSliceTicket() API call,
115 data = ticket_module.verify(ticket)
116 name = data['slivers'][0]['name']
119 logger.log('Ticket delivered for %s' % name)
120 Create(database.db.get(name))
121 except Exception, err:
122 raise xmlrpclib.Fault(102, 'Ticket error: ' + str(err))
124 @export_to_docbook(roles=['self'],
125 accepts=[Parameter(str, 'A ticket returned from GetSlivers()')],
126 returns=Parameter(int, '1 if successful'))
128 def AdminTicket(ticket):
129 """Admin interface to create slivers based on ticket returned by GetSlivers().
132 data, = xmlrpclib.loads(ticket)[0]
133 name = data['slivers'][0]['name']
136 logger.log('Admin Ticket delivered for %s' % name)
137 Create(database.db.get(name))
138 except Exception, err:
139 raise xmlrpclib.Fault(102, 'Ticket error: ' + str(err))
142 @export_to_docbook(roles=['self'],
144 returns={'sliver_name' : Parameter(int, 'the associated xid')})
147 """Return an dictionary mapping Slice names to XIDs"""
148 return dict([(pwent[0], pwent[2]) for pwent in pwd.getpwall() if pwent[6] == sliver_vs.Sliver_VS.SHELL])
150 @export_to_docbook(roles=['self'],
152 returns={ 'sliver_name' : Parameter(str, 'the associated SSHKey')})
155 """Return an dictionary mapping slice names to SSH keys"""
157 for rec in database.db.itervalues():
159 keydict[rec['name']] = rec['keys']
163 @export_to_docbook(roles=['nm-controller', 'self'],
164 accepts=[Parameter(str, 'A sliver/slice name.')],
165 returns=Parameter(int, '1 if successful'))
167 def Create(sliver_name):
168 """Create a non-PLC-instantiated sliver"""
170 if rec['instantiation'] == 'delegated': accounts.get(rec['name']).ensure_created(rec)
171 else: raise Exception, "Only PLC can create non delegated slivers."
174 @export_to_docbook(roles=['nm-controller', 'self'],
175 accepts=[Parameter(str, 'A sliver/slice name.')],
176 returns=Parameter(int, '1 if successful'))
178 def Destroy(sliver_name):
179 """Destroy a non-PLC-instantiated sliver"""
181 if rec['instantiation'] == 'delegated': accounts.get(rec['name']).ensure_destroyed()
182 else: raise Exception, "Only PLC can destroy non delegated slivers."
185 @export_to_docbook(roles=['nm-controller', 'self'],
186 accepts=[Parameter(str, 'A sliver/slice name.')],
187 returns=Parameter(int, '1 if successful'))
189 def Start(sliver_name):
190 """Configure and start sliver."""
192 accounts.get(rec['name']).start(rec)
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 Stop(sliver_name):
200 """Kill all processes belonging to the specified sliver"""
202 accounts.get(rec['name']).stop()
205 @export_to_docbook(roles=['nm-controller', 'self'],
206 accepts=[Parameter(str, 'A sliver/slice name.')],
207 returns=Parameter(int, '1 if successful'))
209 def ReCreate(sliver_name):
210 """Stop, Destroy, Create, Start sliver in order to reinstall it."""
212 accounts.get(rec['name']).stop()
213 accounts.get(rec['name']).ensure_created(rec)
214 accounts.get(rec['name']).start(rec)
217 @export_to_docbook(roles=['nm-controller', 'self'],
218 accepts=[Parameter(str, 'A sliver/slice name.')],
219 returns=Parameter(dict, "A resource specification"))
221 def GetEffectiveRSpec(sliver_name):
222 """Return the RSpec allocated to the specified sliver, including loans"""
224 return rec.get('_rspec', {}).copy()
227 @export_to_docbook(roles=['nm-controller', 'self'],
228 accepts=[Parameter(str, 'A sliver/slice name.')],
229 returns={"resource name" : Parameter(int, "amount")})
231 def GetRSpec(sliver_name):
232 """Return the RSpec allocated to the specified sliver, excluding 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=[Mixed(Parameter(str, 'recipient slice name'),
240 Parameter(str, 'resource name'),
241 Parameter(int, 'resource amount'))])
244 def GetLoans(sliver_name):
245 """Return the list of loans made by the specified sliver"""
247 return rec.get('_loans', [])[:]
249 def validate_loans(obj):
250 """Check that <obj> is a valid loan specification."""
251 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
252 return type(obj)==list and False not in map(validate_loan, obj)
254 @export_to_docbook(roles=['nm-controller', 'self'],
255 accepts=[ Parameter(str, 'A sliver/slice name.'),
256 [Mixed(Parameter(str, 'recipient slice name'),
257 Parameter(str, 'resource name'),
258 Parameter(int, 'resource amount'))] ],
259 returns=Parameter(int, '1 if successful'))
261 def SetLoans(sliver_name, loans):
262 """Overwrite the list of loans made by the specified sliver.
264 Also, note that SetLoans will not throw an error if more capacity than the
265 RSpec is handed out, but it will silently discard those loans that would
266 put it over capacity. This behavior may be replaced with error semantics
267 in the future. As well, there is currently no asynchronous notification
268 of loss of resources.
271 if not validate_loans(loans): raise xmlrpclib.Fault(102, 'Invalid argument: the second argument must be a well-formed loan specification')
272 rec['_loans'] = loans
275 @export_to_docbook(roles=['nm-controller', 'self'],
276 returns=Parameter(dict, 'Record dictionary'))
278 def GetRecord(sliver_name):
279 """Return sliver record"""