new registry
[sfa.git] / plc / aggregate.py
1 ##
2 # Aggregate is a GeniServer that implements the Slice interface at PLC
3
4 import tempfile
5 import os
6 import time
7 import sys
8
9 from util.hierarchy import Hierarchy
10 from util.trustedroot import TrustedRootList
11 from util.cert import Keypair, Certificate
12 from util.gid import GID
13 from util.geniserver import GeniServer
14 from util.record import GeniRecord
15 from util.genitable import GeniTable
16 from util.geniticket import Ticket
17 from util.excep import *
18 from util.misc import *
19
20 ##
21 # Aggregate class extends GeniServer class
22
23 class Aggregate(GeniServer):
24     ##
25     # Create a new aggregate object.
26     #
27     # @param ip the ip address to listen on
28     # @param port the port to listen on
29     # @param key_file private key filename of registry
30     # @param cert_file certificate filename containing public key (could be a GID file)
31
32     def __init__(self, ip, port, key_file, cert_file):
33         GeniServer.__init__(self, ip, port, key_file, cert_file)
34
35         # get PL account settings from config module
36         self.pl_auth = get_pl_auth()
37
38         # connect to planetlab
39         if "Url" in self.pl_auth:
40             self.connect_remote_shell()
41         else:
42             self.connect_local_shell()
43
44     ##
45     # Connect to a remote shell via XMLRPC
46
47     def connect_remote_shell(self):
48         import remoteshell
49         self.shell = remoteshell.RemoteShell()
50
51     ##
52     # Connect to a local shell via local API functions
53
54     def connect_local_shell(self):
55         import PLC.Shell
56         self.shell = PLC.Shell.Shell(globals = globals())
57
58     ##
59     # Register the server RPCs for the slice interface
60
61     def register_functions(self):
62         GeniServer.register_functions(self)
63         self.server.register_function(self.create_slice)
64         self.server.register_function(self.get_ticket)
65         self.server.register_function(self.redeem_ticket)
66         self.server.register_function(self.start_slice)
67         self.server.register_function(self.stop_slice)
68         self.server.register_function(self.reset_slice)
69         self.server.register_function(self.delete_slice)
70         self.server.register_function(self.get_slice_resources)
71         self.server.register_function(self.list_slices)
72         self.server.register_function(self.list_nodes)
73
74     ##
75     # Given an authority name, return the information for that authority. This
76     # is basically a stub that calls the hierarchy module.
77     #
78     # @param auth_hrn human readable name of authority
79
80     def get_auth_info(self, auth_hrn):
81         return AuthHierarchy.get_auth_info(auth_hrn)
82
83     ##
84     # Given an authority name, return the database table for that authority. If
85     # the database table does not exist, then one will be automatically
86     # created.
87     #
88     # @param auth_name human readable name of authority
89
90     def get_auth_table(self, auth_name):
91         auth_info = self.get_auth_info(auth_name)
92
93         table = GeniTable(hrn=auth_name,
94                           cninfo=auth_info.get_dbinfo())
95
96         # if the table doesn't exist, then it means we haven't put any records
97         # into this authority yet.
98
99         if not table.exists():
100             report.trace("Registry: creating table for authority " + auth_name)
101             table.create()
102
103         return table
104
105     ##
106     # Verify that an authority belongs to this registry. This is basically left
107     # up to the implementation of the hierarchy module. If the specified name
108     # does not belong to this registry, an exception is thrown indicating the
109     # caller should contact someone else.
110     #
111     # @param auth_name human readable name of authority
112
113     def verify_auth_belongs_to_me(self, name):
114         # get_auth_info will throw an exception if the authority does not
115         # exist
116         self.get_auth_info(name)
117
118     ##
119     # Verify that an object belongs to this registry. By extension, this implies
120     # that the authority that owns the object belongs to this registry. If the
121     # object does not belong to this registry, then an exception is thrown.
122     #
123     # @param name human readable name of object
124
125     def verify_object_belongs_to_me(self, name):
126         auth_name = get_authority(name)
127         if not auth_name:
128             # the root authority belongs to the registry by default?
129             # TODO: is this true?
130             return
131         self.verify_auth_belongs_to_me(auth_name)
132
133     ##
134     # Verify that the object_gid that was specified in the credential allows
135     # permission to the object 'name'. This is done by a simple prefix test.
136     # For example, an object_gid for planetlab.us.arizona would match the
137     # objects planetlab.us.arizona.slice1 and planetlab.us.arizona.
138     #
139     # @param name human readable name to test
140
141     def verify_object_permission(self, name):
142         object_hrn = self.object_gid.get_hrn()
143         if object_hrn == name:
144             return
145         if name.startswith(object_hrn + "."):
146             return
147         raise PermissionError(name)
148
149     ##
150     # Convert a PLC record into the slice information that will be stored in
151     # a ticket. There are two parts to this information: attributes and
152     # rspec.
153     #
154     # Attributes are non-resource items, such as keys and the initscript
155     # Rspec is a set of resource specifications
156     #
157     # @param record a record object
158     #
159     # @return a tuple (attrs, rspec) of dictionaries
160
161     def record_to_slice_info(self, record):
162
163         # get the user keys from the slice
164         keys = []
165         persons = self.shell.GetPersons(self.pl_auth, record.pl_info['person_ids'])
166         for person in persons:
167             person_keys = self.shell.GetKeys(self.pl_auth, person["key_ids"])
168             for person_key in person_keys:
169                 keys = keys + [person_key['key']]
170
171         attributes={}
172         attributes['name'] = record.pl_info['name']
173         attributes['keys'] = keys
174         attributes['instantiation'] = record.pl_info['instantiation']
175         attributes['vref'] = 'default'
176         attributes['timestamp'] = time.time()
177         attributes['slice_id'] = record.pl_info['slice_id']
178
179         rspec = {}
180
181         # get the PLC attributes and separate them into slice attributes and
182         # rspec attributes
183         filter = {}
184         filter['slice_id'] = record.pl_info['slice_id']
185         plc_attrs = self.shell.GetSliceAttributes(self.pl_auth, filter)
186         for attr in plc_attrs:
187             name = attr['name']
188
189             # initscripts: lookup the contents of the initscript and store it
190             # in the ticket attributes
191             if (name == "initscript"):
192                 filter={'name': attr['value']}
193                 initscripts = self.shell.GetInitScripts(self.pl_auth, filter)
194                 if initscripts:
195                     attributes['initscript'] = initscripts[0]['script']
196             else:
197                 rspec[name] = attr['value']
198
199         return (attributes, rspec)
200
201     ##
202     # create_slice: Create (instantiate) a slice. 
203     #
204     # @param cred credential string
205     # @param name name of the slice to retrieve a ticket for
206     # @param rspec resource specification dictionary
207     #
208     # @return the string representation of a ticket object
209
210     def create_slice(self, cred, name, rspec):
211         self.decode_authentication(cred, "createslice")
212         slicename = hrn_to_pl_slicename(self.object_gid.get_hrn())
213         # TODO: create a slice
214
215     ##
216     # get_ticket: Retrieve a ticket. 
217     #
218     # This operation is currently implemented on PLC only (see SFA,
219     # engineering decisions); it is not implemented on components.
220     #
221     # The ticket is filled in with information from the PLC database. This
222     # information includes resources, and attributes such as user keys and
223     # initscripts.
224     #
225     # @param cred credential string
226     # @param name name of the slice to retrieve a ticket for
227     # @param rspec resource specification dictionary
228     #
229     # @return the string representation of a ticket object
230
231     def get_ticket(self, cred, name, rspec):
232         self.decode_authentication(cred, "getticket")
233
234         self.verify_object_belongs_to_me(name)
235
236         self.verify_object_permission(name)
237
238         auth_hrn = get_authority(name)
239         auth_info = self.get_auth_info(auth_hrn)
240
241         records = self.resolve_raw("slice", name, must_exist=True)
242         record = records[0]
243
244         object_gid = record.get_gid_object()
245         new_ticket = Ticket(subject = object_gid.get_subject())
246         new_ticket.set_gid_caller(self.client_gid)
247         new_ticket.set_gid_object(object_gid)
248         new_ticket.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
249         new_ticket.set_pubkey(object_gid.get_pubkey())
250
251         self.fill_record_info(record)
252
253         (attributes, rspec) = self.record_to_slice_info(record)
254
255         new_ticket.set_attributes(attributes)
256         new_ticket.set_rspec(rspec)
257
258         new_ticket.set_parent(AuthHierarchy.get_auth_ticket(auth_hrn))
259
260         new_ticket.encode()
261         new_ticket.sign()
262
263         return new_ticket.save_to_string(save_parents=True)
264
265     ##
266     # redeem_ticket: Redeem a ticket.
267     #
268     # Not supported at a PLC aggregate.
269     #
270     # @param ...not sure...
271
272     def redeem_ticket(self, whatever):
273         return anything
274
275     ##
276     # stop_slice: Stop a slice.
277     #
278     # @param cred a credential identifying the caller (callerGID) and the slice
279     #     (objectGID)
280
281     def stop_slice(self, cred_str):
282         self.decode_authentication(cred_str, "stopslice")
283         slicename = hrn_to_pl_slicename(self.object_gid.get_hrn())
284         # TODO: stop the slice
285
286     ##
287     # start_slice: Start a slice.
288     #
289     # @param cred a credential identifying the caller (callerGID) and the slice
290     #     (objectGID)
291
292     def start_slice(self, cred_str):
293         self.decode_authentication(cred_str, "startslice")
294         slicename = hrn_to_pl_slicename(self.object_gid.get_hrn())
295         # TODO: start the slice
296
297     ##
298     # reset_slice: Reset a slice.
299     #
300     # @param cred a credential identifying the caller (callerGID) and the slice
301     #     (objectGID)
302
303     def reset_slice(self, cred_str):
304         self.decode_authentication(cred_str, "resetslice")
305         slicename = hrn_to_pl_slicename(self.object_gid.get_hrn())
306         # TODO: reset the slice
307
308     ##
309     # delete_slice: Delete a slice.
310     #
311     # @param cred a credential identifying the caller (callerGID) and the slice
312     #     (objectGID)
313
314     def delete_slice(self, cred_str):
315         self.decode_authentication(cred_str, "deleteslice")
316         slicename = hrn_to_pl_slicename(self.object_gid.get_hrn())
317         # TODO: delete the slice
318
319     ##
320     # get_resources: Get resources allocated to slice
321     #
322     # @param cred a credential identifying the caller (callerGID) and the slice
323     #     (objectGID)
324
325     def get_slice_resources(self, cred_str):
326         self.decode_authentication(cred_str, "getsliceresources")
327         slicename = hrn_to_pl_slicename(self.object_gid.get_hrn())
328         # TODO: get resources allocated to slice
329
330     ##
331     # list_slices: List hosted slices.
332     #
333     # @param cred a credential identifying the caller (callerGID)
334
335     def list_slices(self, cred_str):
336         self.decode_authentication(cred_str, "listslices")
337         # TODO: list hosted slices
338
339     ##
340     # list_nodes: List available nodes.
341     #
342     # @param cred a credential identifying the caller (callerGID)
343
344     def list_nodes(self, cred_str):
345         self.decode_authentication(cred_str, "listnodes")
346         # TODO: list available nodes
347
348