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
46 def export_to_api(nargs):
48 nargs_dict[method.__name__] = nargs
49 api_method_dict[method.__name__] = method
53 def export_to_docbook(**kwargs):
66 # Inspect method. Remove self from the argument list.
67 max_args = method.func_code.co_varnames[0:method.func_code.co_argcount]
68 defaults = method.func_defaults
71 min_args = max_args[0:len(max_args) - len(defaults)]
73 defaults = tuple([None for arg in min_args]) + defaults
74 return (min_args, max_args, defaults)
76 keywords['name'] = method.__name__
77 keywords['args'] = args
79 method.__setattr__(arg, keywords[arg])
82 method.__setattr__(arg, kwargs[arg])
93 @export_to_docbook(roles=['self'],
95 returns=Parameter([], 'A list of supported functions'))
98 """Get a list of functions currently supported by the Node Manager API"""
99 return ''.join([method.__doc__ + '\n' for method in api_method_dict.itervalues()])
101 @export_to_docbook(roles=['self'],
102 accepts=[Parameter(str, 'A ticket returned from GetSliceTicket()')],
103 returns=Parameter(int, '1 if successful'))
106 """The Node Manager periodically polls the PLC API for a list of all
107 slices that are allowed to exist on the given node. Before
108 actions are performed on a delegated slice (such as creation),
109 a controller slice must deliver a valid slice ticket to NM.
111 This ticket is the value retured by PLC's GetSliceTicket() API call,
114 data = ticket_module.verify(ticket)
115 name = data['slivers'][0]['name']
118 logger.log('Ticket delivered for %s' % name)
119 Create(database.db.get(name))
120 except Exception, err:
121 raise xmlrpclib.Fault(102, 'Ticket error: ' + str(err))
123 @export_to_docbook(roles=['self'],
125 returns={'sliver_name' : Parameter(int, 'the associated xid')})
128 """Return an dictionary mapping Slice names to XIDs"""
129 return dict([(pwent[0], pwent[2]) for pwent in pwd.getpwall() if pwent[6] == sliver_vs.Sliver_VS.SHELL])
131 @export_to_docbook(roles=['self'],
133 returns={ 'sliver_name' : Parameter(str, 'the associated SSHKey')})
136 """Return an dictionary mapping slice names to SSH keys"""
138 for rec in database.db.itervalues():
140 keydict[rec['name']] = rec['keys']
143 @export_to_docbook(roles=['nm-controller', 'self'],
144 accepts=[Parameter(str, 'A sliver/slice name.')],
145 returns=Parameter(int, '1 if successful'))
147 def Create(sliver_name):
148 """Create a non-PLC-instantiated sliver"""
150 if rec['instantiation'] == 'delegated': accounts.get(rec['name']).ensure_created(rec)
152 @export_to_docbook(roles=['nm-controller', 'self'],
153 accepts=[Parameter(str, 'A sliver/slice name.')],
154 returns=Parameter(int, '1 if successful'))
156 def Destroy(sliver_name):
157 """Destroy a non-PLC-instantiated sliver"""
159 if rec['instantiation'] == 'delegated': accounts.get(rec['name']).ensure_destroyed()
161 @export_to_docbook(roles=['nm-controller', 'self'],
162 accepts=[Parameter(str, 'A sliver/slice name.')],
163 returns=Parameter(int, '1 if successful'))
165 def Start(sliver_name):
166 """Run start scripts belonging to the specified sliver"""
168 accounts.get(rec['name']).start()
170 @export_to_docbook(roles=['nm-controller', 'self'],
171 accepts=[Parameter(str, 'A sliver/slice name.')],
172 returns=Parameter(int, '1 if successful'))
174 def Stop(sliver_name):
175 """Kill all processes belonging to the specified sliver"""
177 accounts.get(rec['name']).stop()
179 @export_to_docbook(roles=['nm-controller', 'self'],
180 accepts=[Parameter(str, 'A sliver/slice name.')],
181 returns=Parameter(int, '1 if successful'))
184 def ReCreate(sliver_name):
185 """Stop, Destroy, Create, Start sliver in order to reinstall it."""
191 @export_to_docbook(roles=['nm-controller', 'self'],
192 accepts=[Parameter(str, 'A sliver/slice name.')],
193 returns=Parameter(dict, "A resource specification"))
195 def GetEffectiveRSpec(sliver_name):
196 """Return the RSpec allocated to the specified sliver, including loans"""
198 return rec.get('_rspec', {}).copy()
200 @export_to_docbook(roles=['nm-controller', 'self'],
201 accepts=[Parameter(str, 'A sliver/slice name.')],
203 "resource name" : Parameter(int, "amount")
207 def GetRSpec(sliver_name):
208 """Return the RSpec allocated to the specified sliver, excluding loans"""
210 return rec.get('rspec', {}).copy()
212 @export_to_docbook(roles=['nm-controller', 'self'],
213 accepts=[Parameter(str, 'A sliver/slice name.')],
214 returns=[Mixed(Parameter(str, 'recipient slice name'),
215 Parameter(str, 'resource name'),
216 Parameter(int, 'resource amount'))]
219 def GetLoans(sliver_name):
220 """Return the list of loans made by the specified sliver"""
222 return rec.get('_loans', [])[:]
224 def validate_loans(obj):
225 """Check that <obj> is a valid loan specification."""
226 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
227 return type(obj)==list and False not in map(validate_loan, obj)
229 @export_to_docbook(roles=['nm-controller', 'self'],
230 accepts=[ Parameter(str, 'A sliver/slice name.'),
231 [Mixed(Parameter(str, 'recipient slice name'),
232 Parameter(str, 'resource name'),
233 Parameter(int, 'resource amount'))] ],
234 returns=Parameter(int, '1 if successful'))
236 def SetLoans(sliver_name, loans):
237 """Overwrite the list of loans made by the specified sliver.
239 Also, note that SetLoans will not throw an error if more capacity than the
240 RSpec is handed out, but it will silently discard those loans that would
241 put it over capacity. This behavior may be replaced with error semantics
242 in the future. As well, there is currently no asynchronous notification
243 of loss of resources.
246 if not validate_loans(loans): raise xmlrpclib.Fault(102, 'Invalid argument: the second argument must be a well-formed loan specification')
247 rec['_loans'] = loans