a8c465465c31a72005c38a2ad85e1af1bed7c6e6
[sfa.git] / geni / registry.py
1 ##
2 # Registry is a GeniServer that implements the Registry interface
3
4 import tempfile
5 import os
6 import time
7 import sys
8
9 from geni.util.credential import Credential
10 from geni.util.hierarchy import Hierarchy
11 from geni.util.trustedroot import TrustedRootList
12 from geni.util.cert import Keypair, Certificate
13 from geni.util.gid import GID, create_uuid
14 from geni.util.geniserver import GeniServer
15 from geni.util.geniclient import GeniClient
16 from geni.util.record import GeniRecord
17 from geni.util.rights import RightList
18 from geni.util.genitable import GeniTable
19 from geni.util.geniticket import Ticket
20 from geni.util.excep import *
21 from geni.util.misc import *
22 from geni.util.config import *
23 from geni.util.storage import *
24
25 ##
26 # Convert geni fields to PLC fields for use when registering up updating
27 # registry record in the PLC database
28 #
29 # @param type type of record (user, slice, ...)
30 # @param hrn human readable name
31 # @param geni_fields dictionary of geni fields
32 # @param pl_fields dictionary of PLC fields (output)
33
34 def geni_fields_to_pl_fields(type, hrn, geni_fields, pl_fields):
35     if type == "user":
36         if not "email" in pl_fields:
37             if not "email" in geni_fields:
38                 raise MissingGeniInfo("email")
39             pl_fields["email"] = geni_fields["email"]
40
41         if not "first_name" in pl_fields:
42             pl_fields["first_name"] = "geni"
43
44         if not "last_name" in pl_fields:
45             pl_fields["last_name"] = hrn
46
47     elif type == "slice":
48         if not "instantiation" in pl_fields:
49             pl_fields["instantiation"] = "delegated"  # "plc-instantiated"
50         if not "name" in pl_fields:
51             pl_fields["name"] = hrn_to_pl_slicename(hrn)
52         if not "max_nodes" in pl_fields:
53             pl_fields["max_nodes"] = 10
54
55     elif type == "node":
56         if not "hostname" in pl_fields:
57             if not "dns" in geni_fields:
58                 raise MissingGeniInfo("dns")
59             pl_fields["hostname"] = geni_fields["dns"]
60
61         if not "model" in pl_fields:
62             pl_fields["model"] = "geni"
63
64     elif type == "sa":
65         pl_fields["login_base"] = hrn_to_pl_login_base(hrn)
66
67         if not "name" in pl_fields:
68             pl_fields["name"] = hrn
69
70         if not "abbreviated_name" in pl_fields:
71             pl_fields["abbreviated_name"] = hrn
72
73         if not "enabled" in pl_fields:
74             pl_fields["enabled"] = True
75
76         if not "is_public" in pl_fields:
77             pl_fields["is_public"] = True
78
79 ##
80 # Registry is a GeniServer that serves registry and slice operations at PLC.
81
82 class Registry(GeniServer):
83     ##
84     # Create a new registry object.
85     #
86     # @param ip the ip address to listen on
87     # @param port the port to listen on
88     # @param key_file private key filename of registry
89     # @param cert_file certificate filename containing public key (could be a GID file)
90
91     def __init__(self, ip, port, key_file, cert_file, config = '/usr/share/geniwrapper/geni/util/geni_config'):
92         GeniServer.__init__(self, ip, port, key_file, cert_file)
93
94         # get PL account settings from config module
95         self.pl_auth = get_pl_auth()
96
97         # connect to planetlab
98         if "Url" in self.pl_auth:
99             self.connect_remote_shell()
100         else:
101             self.connect_local_shell()
102
103         self.key_file = key_file
104         self.cert_file = cert_file
105         self.config = Config(config)
106         self.basedir = self.config.GENI_BASE_DIR + os.sep
107         self.server_basedir = self.basedir + os.sep + "geni" + os.sep
108         self.hrn = self.config.GENI_INTERFACE_HRN
109
110         # get peer registry information
111         registries_file = self.server_basedir + os.sep + 'registries.xml'
112         connection_dict = {'hrn': '', 'addr': '', 'port': ''} 
113         self.registry_info = XmlStorage(registries_file, {'registries': {'registry': [connection_dict]}})
114         self.registry_info.load()
115         self.connectRegistry()
116         self.connectRegistries()
117         
118  
119     ##
120     # Connect to a remote shell via XMLRPC
121
122     def connect_remote_shell(self):
123         from geni.util import remoteshell
124         self.shell = remoteshell.RemoteShell()
125
126     ##
127     # Connect to a local shell via local API functions
128
129     def connect_local_shell(self):
130         import PLC.Shell
131         self.shell = PLC.Shell.Shell(globals = globals())
132
133     ##
134     # Register the server RPCs for the registry
135
136     def register_functions(self):
137         GeniServer.register_functions(self)
138         # registry interface
139         self.server.register_function(self.create_gid)
140         self.server.register_function(self.get_self_credential)
141         self.server.register_function(self.get_credential)
142         self.server.register_function(self.get_gid)
143         self.server.register_function(self.get_ticket)
144         self.server.register_function(self.register)
145         self.server.register_function(self.remove)
146         self.server.register_function(self.update)
147         self.server.register_function(self.list)
148         self.server.register_function(self.resolve)
149
150
151     def loadCredential(self):
152         """
153         Attempt to load credential from file if it exists. If it doesnt get
154         credential from registry.
155         """
156
157         # see if this file exists
158         ma_cred_filename = self.server_basedir + os.sep + "agg." + self.hrn + ".ma.cred"
159         try:
160             self.credential = Credential(filename = ma_cred_filename)
161         except IOError:
162             self.credential = self.getCredentialFromRegistry()
163
164     def getCredentialFromRegistry(self):
165         """
166         Get our current credential from the registry.
167         """
168         # get self credential
169         self_cred_filename = self.server_basedir + os.sep + "smgr." + self.hrn + ".cred"
170         self_cred = self.registry.get_credential(None, 'ma', self.hrn)
171         self_cred.save_to_file(self_cred_filename, save_parents=True)
172
173         # get ma credential
174         ma_cred_filename = self.server_basedir + os.sep + "smgr." + self.hrn + ".sa.cred"
175         ma_cred = self.registry.get_credential(self_cred, 'sa', self.hrn)
176         ma_cred.save_to_file(ma_cred_filename, save_parents=True)
177         return ma_cred
178
179     def connectRegistry(self):
180         """
181         Connect to the registry
182         """
183         # connect to registry using GeniClient
184         address = self.config.GENI_REGISTRY_HOSTNAME
185         port = self.config.GENI_REGISTRY_PORT
186         url = 'http://%(address)s:%(port)s' % locals()
187         self.registry = GeniClient(url, self.key_file, self.cert_file)
188
189     def connectRegistries(self):
190         """
191         Get connection details for the trusted peer registries from file and 
192         create an GeniClient connection to each. 
193         """
194         self.registries= {}
195         registries = self.registry_info['registries']['registry']
196         if isinstance(registries, dict):
197             registries = [registries]
198         if isinstance(registries, list):
199             for registry in registries:
200                 # create xmlrpc connection using GeniClient
201                 hrn, address, port = registry['hrn'], registry['addr'], registry['port']
202                 url = 'http://%(address)s:%(port)s' % locals()
203                 self.registries[hrn] = GeniClient(url, self.key_file, self.cert_file)
204
205     ##
206     # Given an authority name, return the information for that authority. This
207     # is basically a stub that calls the hierarchy module.
208     #
209     # @param auth_hrn human readable name of authority
210
211     def get_auth_info(self, auth_hrn):
212         return self.hierarchy.get_auth_info(auth_hrn)
213
214     ##
215     # Given an authority name, return the database table for that authority. If
216     # the database table does not exist, then one will be automatically
217     # created.
218     #
219     # @param auth_name human readable name of authority
220
221     def get_auth_table(self, auth_name):
222         auth_info = self.get_auth_info(auth_name)
223
224         table = GeniTable(hrn=auth_name,
225                           cninfo=auth_info.get_dbinfo())
226
227         # if the table doesn't exist, then it means we haven't put any records
228         # into this authority yet.
229
230         if not table.exists():
231             print "Registry: creating table for authority", auth_name
232             table.create()
233
234         return table
235
236     ##
237     # Verify that an authority belongs to this registry. This is basically left
238     # up to the implementation of the hierarchy module. If the specified name
239     # does not belong to this registry, an exception is thrown indicating the
240     # caller should contact someone else.
241     #
242     # @param auth_name human readable name of authority
243
244     def verify_auth_belongs_to_me(self, name):
245         # get_auth_info will throw an exception if the authority does not
246         # exist
247         self.get_auth_info(name)
248
249     ##
250     # Verify that an object belongs to this registry. By extension, this implies
251     # that the authority that owns the object belongs to this registry. If the
252     # object does not belong to this registry, then an exception is thrown.
253     #
254     # @param name human readable name of object
255
256     def verify_object_belongs_to_me(self, name):
257         auth_name = get_authority(name)
258         if not auth_name:
259             # the root authority belongs to the registry by default?
260             # TODO: is this true?
261             return
262         self.verify_auth_belongs_to_me(auth_name)
263
264     ##
265     # Verify that the object_gid that was specified in the credential allows
266     # permission to the object 'name'. This is done by a simple prefix test.
267     # For example, an object_gid for planetlab.us.arizona would match the
268     # objects planetlab.us.arizona.slice1 and planetlab.us.arizona.
269     #
270     # @param name human readable name to test
271
272     def verify_object_permission(self, name):
273         object_hrn = self.object_gid.get_hrn()
274         if object_hrn == name:
275             return
276         if name.startswith(object_hrn + "."):
277             return
278         raise PermissionError(name)
279
280     ##
281     # Fill in the planetlab-specific fields of a Geni record. This involves
282     # calling the appropriate PLC methods to retrieve the database record for
283     # the object.
284     #
285     # PLC data is filled into the pl_info field of the record.
286     #
287     # @param record record to fill in fields (in/out param)
288
289     def fill_record_pl_info(self, record):
290         type = record.get_type()
291         pointer = record.get_pointer()
292
293         # records with pointer==-1 do not have plc info associated with them.
294         # for example, the top level authority records which are
295         # authorities, but not PL "sites"
296         if pointer == -1:
297             record.set_pl_info({})
298             return
299
300         if (type == "sa") or (type == "ma"):
301             pl_res = self.shell.GetSites(self.pl_auth, [pointer])
302         elif (type == "slice"):
303             pl_res = self.shell.GetSlices(self.pl_auth, [pointer])
304         elif (type == "user"):
305             pl_res = self.shell.GetPersons(self.pl_auth, [pointer])
306         elif (type == "node"):
307             pl_res = self.shell.GetNodes(self.pl_auth, [pointer])
308         else:
309             raise UnknownGeniType(type)
310
311         if not pl_res:
312             # the planetlab record no longer exists
313             # TODO: delete the geni record ?
314             raise PlanetLabRecordDoesNotExist(record.get_name())
315
316         record.set_pl_info(pl_res[0])
317
318     ##
319     # Look up user records given PLC user-ids. This is used as part of the
320     # process for reverse-mapping PLC records into Geni records.
321     #
322     # @param auth_table database table for the authority that holds the user records
323     # @param user_id_list list of user ids
324     # @param role either "*" or a string describing the role to look for ("pi", "user", ...)
325     #
326     # TODO: This function currently only searches one authority because it would
327     # be inefficient to brute-force search all authorities for a user id. The
328     # solution would likely be to implement a reverse mapping of user-id to
329     # (type, hrn) pairs.
330
331     def lookup_users(self, auth_table, user_id_list, role="*"):
332         record_list = []
333         for person_id in user_id_list:
334             user_records = auth_table.find("user", person_id, "pointer")
335             for user_record in user_records:
336                 self.fill_record_info(user_record)
337
338                 user_roles = user_record.get_pl_info().get("roles")
339                 if (role=="*") or (role in user_roles):
340                     record_list.append(user_record.get_name())
341         return record_list
342
343     ##
344     # Fill in the geni-specific fields of the record.
345     #
346     # Note: It is assumed the fill_record_pl_info() has already been performed
347     # on the record.
348
349     def fill_record_geni_info(self, record):
350         geni_info = {}
351         type = record.get_type()
352
353         if (type == "slice"):
354             auth_table = self.get_auth_table(get_authority(record.get_name()))
355             person_ids = record.pl_info.get("person_ids", [])
356             researchers = self.lookup_users(auth_table, person_ids)
357             geni_info['researcher'] = researchers
358
359         elif (type == "sa"):
360             auth_table = self.get_auth_table(record.get_name())
361             person_ids = record.pl_info.get("person_ids", [])
362             pis = self.lookup_users(auth_table, person_ids, "pi")
363             geni_info['pi'] = pis
364             # TODO: OrganizationName
365
366         elif (type == "ma"):
367             auth_table = self.get_auth_table(record.get_name())
368             person_ids = record.pl_info.get("person_ids", [])
369             operators = self.lookup_users(auth_table, person_ids, "tech")
370             geni_info['operator'] = operators
371             # TODO: OrganizationName
372
373             auth_table = self.get_auth_table(record.get_name())
374             person_ids = record.pl_info.get("person_ids", [])
375             owners = self.lookup_users(auth_table, person_ids, "admin")
376             geni_info['owner'] = owners
377
378         elif (type == "node"):
379             geni_info['dns'] = record.pl_info.get("hostname", "")
380             # TODO: URI, LatLong, IP, DNS
381
382         elif (type == "user"):
383             geni_info['email'] = record.pl_info.get("email", "")
384             # TODO: PostalAddress, Phone
385
386         record.set_geni_info(geni_info)
387
388     ##
389     # Given a Geni record, fill in the PLC-specific and Geni-specific fields
390     # in the record.
391
392     def fill_record_info(self, record):
393         self.fill_record_pl_info(record)
394         self.fill_record_geni_info(record)
395
396     def update_membership_list(self, oldRecord, record, listName, addFunc, delFunc):
397         # get a list of the HRNs tht are members of the old and new records\r
398         if oldRecord:\r
399             if oldRecord.pl_info == None:\r
400                 oldRecord.pl_info = {}\r
401             oldList = oldRecord.get_geni_info().get(listName, [])\r
402         else:\r
403             oldList = []\r
404         newList = record.get_geni_info().get(listName, [])\r
405 \r
406         # if the lists are the same, then we don't have to update anything\r
407         if (oldList == newList):\r
408             return\r
409 \r
410         # build a list of the new person ids, by looking up each person to get\r
411         # their pointer\r
412         newIdList = []\r
413         for hrn in newList:\r
414             userRecord = self.resolve_raw("user", hrn)[0]\r
415             newIdList.append(userRecord.get_pointer())\r
416 \r
417         # build a list of the old person ids from the person_ids field of the\r
418         # pl_info\r
419         if oldRecord:\r
420             oldIdList = oldRecord.pl_info.get("person_ids", [])\r
421             containerId = oldRecord.get_pointer()\r
422         else:\r
423             # if oldRecord==None, then we are doing a Register, instead of an\r
424             # update.\r
425             oldIdList = []\r
426             containerId = record.get_pointer()\r
427 \r
428         # add people who are in the new list, but not the oldList\r
429         for personId in newIdList:\r
430             if not (personId in oldIdList):\r
431                 print "adding id", personId, "to", record.get_name()\r
432                 addFunc(self.pl_auth, personId, containerId)\r
433 \r
434         # remove people who are in the old list, but not the new list\r
435         for personId in oldIdList:\r
436             if not (personId in newIdList):\r
437                 print "removing id", personId, "from", record.get_name()\r
438                 delFunc(self.pl_auth, personId, containerId)\r
439 \r
440     def update_membership(self, oldRecord, record):\r
441         if record.type == "slice":\r
442             self.update_membership_list(oldRecord, record, 'researcher',\r
443                                         self.shell.AddPersonToSlice,\r
444                                         self.shell.DeletePersonFromSlice)\r
445         elif record.type == "sa":\r
446             # TODO\r
447             pass\r
448         elif record.type == "ma":\r
449             # TODO\r
450             pass
451
452     ##
453     # GENI API: register
454     #
455     # Register an object with the registry. In addition to being stored in the
456     # Geni database, the appropriate records will also be created in the
457     # PLC databases
458     #
459     # @param cred credential string
460     # @param record_dict dictionary containing record fields
461
462     def register(self, cred, record_dict):
463         self.decode_authentication(cred, "register")
464
465         record = GeniRecord(dict = record_dict)
466         type = record.get_type()
467         name = record.get_name()
468
469         auth_name = get_authority(name)
470         self.verify_object_permission(auth_name)
471         auth_info = self.get_auth_info(auth_name)
472         table = self.get_auth_table(auth_name)
473
474         pkey = None
475
476         # check if record already exists
477         existing_records = table.resolve(type, name)
478         if existing_records:
479             raise ExistingRecord(name)
480
481         if (type == "sa") or (type=="ma"):
482             # update the tree
483             if not self.hierarchy.auth_exists(name):
484                 self.hierarchy.create_auth(name)
485
486             # authorities are special since they are managed by the registry
487             # rather than by the caller. We create our own GID for the
488             # authority rather than relying on the caller to supply one.
489
490             # get the GID from the newly created authority
491             child_auth_info = self.get_auth_info(name)
492             gid = auth_info.get_gid_object()
493             record.set_gid(gid.save_to_string(save_parents=True))
494
495             geni_fields = record.get_geni_info()
496             site_fields = record.get_pl_info()
497
498             # if registering a sa, see if a ma already exists
499             # if registering a ma, see if a sa already exists
500             if (type == "sa"):
501                 other_rec = table.resolve("ma", record.get_name())
502             elif (type == "ma"):
503                 other_rec = table.resolve("sa", record.get_name())
504
505             if other_rec:
506                 print "linking ma and sa to the same plc site"
507                 pointer = other_rec[0].get_pointer()
508             else:
509                 geni_fields_to_pl_fields(type, name, geni_fields, site_fields)
510                 print "adding site with fields", site_fields
511                 pointer = self.shell.AddSite(self.pl_auth, site_fields)
512
513             record.set_pointer(pointer)
514
515         elif (type == "slice"):
516             geni_fields = record.get_geni_info()
517             slice_fields = record.get_pl_info()
518
519             geni_fields_to_pl_fields(type, name, geni_fields, slice_fields)
520
521             pointer = self.shell.AddSlice(self.pl_auth, slice_fields)
522             record.set_pointer(pointer)
523
524         elif (type == "user"):
525             geni_fields = record.get_geni_info()
526             user_fields = record.get_pl_info()
527
528             geni_fields_to_pl_fields(type, name, geni_fields, user_fields)
529
530             pointer = self.shell.AddPerson(self.pl_auth, user_fields)
531             record.set_pointer(pointer)
532
533         elif (type == "node"):
534             geni_fields = record.get_geni_info()
535             node_fields = record.get_pl_info()
536
537             geni_fields_to_pl_fields(type, name, geni_fields, node_fields)
538
539             login_base = hrn_to_pl_login_base(auth_name)
540
541             print "calling addnode with", login_base, node_fields
542             pointer = self.shell.AddNode(self.pl_auth, login_base, node_fields)
543             record.set_pointer(pointer)
544
545         else:
546             raise UnknownGeniType(type)
547
548         table.insert(record)
549
550         # update membership for researchers, pis, owners, operators
551         self.update_membership(None, record)
552
553         return record.get_gid_object().save_to_string(save_parents=True)
554
555     ##
556     # GENI API: remove
557     #
558     # Remove an object from the registry. If the object represents a PLC object,
559     # then the PLC records will also be removed.
560     #
561     # @param cred credential string
562     # @param record_dict dictionary containing record fields. The only relevant
563     #     fields of the record are 'name' and 'type', which are used to lookup
564     #     the current copy of the record in the Geni database, to make sure
565     #     that the appopriate record is removed.
566
567     def remove(self, cred, type, hrn):
568         self.decode_authentication(cred, "remove")
569
570         self.verify_object_permission(hrn)
571
572         auth_name = get_authority(hrn)
573         table = self.get_auth_table(auth_name)
574
575         record_list = table.resolve(type, hrn)
576         if not record_list:
577             raise RecordNotFound(hrn)
578         record = record_list[0]
579
580         # TODO: sa, ma
581         if type == "user":
582             self.shell.DeletePerson(self.pl_auth, record.get_pointer())
583         elif type == "slice":
584             self.shell.DeleteSlice(self.pl_auth, record.get_pointer())
585         elif type == "node":
586             self.shell.DeleteNode(self.pl_auth, record.get_pointer())
587         elif (type == "sa") or (type == "ma"):
588             if (type == "sa"):
589                 other_rec = table.resolve("ma", record.get_name())
590             elif (type == "ma"):
591                 other_rec = table.resolve("sa", record.get_name())
592
593             if other_rec:
594                 # sa and ma both map to a site, so if we are deleting one
595                 # but the other still exists, then do not delete the site
596                 print "not removing site", record.get_name(), "because either sa or ma still exists"
597                 pass
598             else:
599                 print "removing site", record.get_name()
600                 self.shell.DeleteSite(self.pl_auth, record.get_pointer())
601         else:
602             raise UnknownGeniType(type)
603
604         table.remove(record)
605
606         return True
607
608     ##
609     # GENI API: Update
610     #
611     # Update an object in the registry. Currently, this only updates the
612     # PLC information associated with the record. The Geni fields (name, type,
613     # GID) are fixed.
614     #
615     # The record is expected to have the pl_info field filled in with the data
616     # that should be updated.
617     #
618     # TODO: The geni_info member of the record should be parsed and the pl_info
619     # adjusted as necessary (add/remove users from a slice, etc)
620     #
621     # @param cred credential string specifying rights of the caller
622     # @param record a record dictionary to be updated
623
624     def update(self, cred, record_dict):
625         self.decode_authentication(cred, "update")
626
627         record = GeniRecord(dict = record_dict)
628         type = record.get_type()
629
630         self.verify_object_permission(record.get_name())
631
632         auth_name = get_authority(record.get_name())
633         if not auth_name:
634             auth_name = record.get_name()
635         table = self.get_auth_table(auth_name)
636
637         # make sure the record exists
638         existing_record_list = table.resolve(type, record.get_name())
639         if not existing_record_list:
640             raise RecordNotFound(record.get_name())
641         existing_record = existing_record_list[0]
642
643         # Update_membership needs the membership lists in the existing record
644         # filled in, so it can see if members were added or removed
645         self.fill_record_info(existing_record)
646
647         # Use the pointer from the existing record, not the one that the user
648         # gave us. This prevents the user from inserting a forged pointer
649         pointer = existing_record.get_pointer()
650
651         # update the PLC information that was specified with the record
652
653         if (type == "sa") or (type == "ma"):
654             self.shell.UpdateSite(self.pl_auth, pointer, record.get_pl_info())
655
656         elif type == "slice":
657             self.shell.UpdateSlice(self.pl_auth, pointer, record.get_pl_info())
658
659         elif type == "user":
660             # SMBAKER: UpdatePerson only allows a limited set of fields to be
661             #    updated. Ideally we should have a more generic way of doing
662             #    this. I copied the field names from UpdatePerson.py...
663             update_fields = {}
664             all_fields = record.get_pl_info()
665             for key in all_fields.keys():
666                 if key in ['first_name', 'last_name', 'title', 'email',
667                            'password', 'phone', 'url', 'bio', 'accepted_aup',\r
668                            'enabled']:
669                     update_fields[key] = all_fields[key]
670             self.shell.UpdatePerson(self.pl_auth, pointer, update_fields)
671
672         elif type == "node":
673             self.shell.UpdateNode(self.pl_auth, pointer, record.get_pl_info())
674
675         else:
676             raise UnknownGeniType(type)
677
678         # update membership for researchers, pis, owners, operators\r
679         self.update_membership(existing_record, record)
680
681     ##
682     # List the records in an authority. The objectGID in the supplied credential
683     # should name the authority that will be listed.
684     #
685     # TODO: List doesn't take an hrn and uses the hrn contained in the
686     #    objectGid of the credential. Does this mean the only way to list an
687     #    authority is by having a credential for that authority?
688     #
689     # @param cred credential string specifying rights of the caller
690     #
691     # @return list of record dictionaries
692     def list(self, cred, auth_hrn):
693         self.decode_authentication(cred, "list")
694
695         if not self.hierarchy.auth_exists(auth_hrn):
696             raise MissingAuthority(auth_hrn)
697
698         table = self.get_auth_table(auth_hrn)
699
700         records = table.list()
701
702         good_records = []
703         for record in records:
704             try:
705                 self.fill_record_info(record)
706                 good_records.append(record)
707             except PlanetLabRecordDoesNotExist:
708                 # silently drop the ones that are missing in PL.
709                 # is this the right thing to do?
710                 print "ignoring geni record ", record.get_name(), " because pl record does not exist"
711                 table.remove(record)
712
713         dicts = []
714         for record in good_records:
715             dicts.append(record.as_dict())
716
717         return dicts
718
719         return dict_list
720
721     ##
722     # Resolve a record. This is an internal version of the Resolve API call
723     # and returns records in record object format rather than dictionaries
724     # that may be sent over XMLRPC.
725     #
726     # @param type type of record to resolve (user | sa | ma | slice | node)
727     # @param name human readable name of object
728     # @param must_exist if True, throw an exception if no records are found
729     #
730     # @return a list of record objects, or an empty list []
731
732     def resolve_raw(self, type, name, must_exist=True):
733         auth_name = get_authority(name)
734         if not auth_name:
735             auth_name = name
736         table = self.get_auth_table(auth_name)
737         records = table.resolve(type, name)
738         if (not records) and must_exist:
739             raise RecordNotFound(name)
740
741         good_records = []
742         for record in records:
743             try:
744                 self.fill_record_info(record)
745                 good_records.append(record)
746             except PlanetLabRecordDoesNotExist:
747                 # silently drop the ones that are missing in PL.
748                 # is this the right thing to do?
749                 print "ignoring geni record ", record.get_name(), "because pl record does not exist"
750                 table.remove(record)
751
752         return good_records
753
754     ##
755     # GENI API: Resolve
756     #
757     # This is a wrapper around resolve_raw that converts records objects into
758     # dictionaries before returning them to the user.
759     #
760     # @param cred credential string authorizing the caller
761     # @param name human readable name to resolve
762     #
763     # @return a list of record dictionaries, or an empty list
764
765     def resolve(self, cred, name):
766         self.decode_authentication(cred, "resolve")
767         
768         try:
769             records = self.resolve_raw("*", name)
770         except:
771             records = []
772             for registry in self.registries:
773                 if name.startswith(registry):
774                     records = self.registries[registry].resolve(self.credential, name)
775                 
776
777         dicts = []
778         for record in records:
779             dicts.append(record.as_dict())
780
781         return dicts
782
783     ##
784     # GENI API: get_gid
785     #
786     # Retrieve the GID for an object. This function looks up a record in the
787     # registry and returns the GID of the record if it exists.
788     # TODO: Is this function needed? It's a shortcut for Resolve()
789     #
790     # @param name hrn to look up
791     #
792     # @return the string representation of a GID object
793
794     def get_gid(self, name):
795         self.verify_object_belongs_to_me(name)
796         records = self.resolve_raw("*", name)
797         gid_string_list = []
798         for record in records:
799             gid = record.get_gid_object()
800             gid_string_list.append(gid.save_to_string(save_parents=True))
801         return gid_string_list
802
803     ##
804     # Determine tje rights that an object should have. The rights are entirely
805     # dependent on the type of the object. For example, users automatically
806     # get "refresh", "resolve", and "info".
807     #
808     # @param type the type of the object (user | sa | ma | slice | node)
809     # @param name human readable name of the object (not used at this time)
810     #
811     # @return RightList object containing rights
812
813     def determine_rights(self, type, name):
814         rl = RightList()
815
816         # rights seem to be somewhat redundant with the type of the credential.
817         # For example, a "sa" credential implies the authority right, because
818         # a sa credential cannot be issued to a user who is not an owner of
819         # the authority
820
821         if type == "user":
822             rl.add("refresh")
823             rl.add("resolve")
824             rl.add("info")
825         elif type == "sa":
826             rl.add("authority,sa")
827         elif type == "ma":
828             rl.add("authority,ma")
829         elif type == "slice":
830             rl.add("refresh")
831             rl.add("embed")
832             rl.add("bind")
833             rl.add("control")
834             rl.add("info")
835         elif type == "component":
836             rl.add("operator")
837
838         return rl
839
840     ##
841     # GENI API: Get_self_credential
842     #
843     # Get_self_credential a degenerate version of get_credential used by a
844     # client to get his initial credential when he doesn't have one. This is
845     # the same as get_credential(..., cred=None,...).
846     #
847     # The registry ensures that the client is the principal that is named by
848     # (type, name) by comparing the public key in the record's GID to the
849     # private key used to encrypt the client-side of the HTTPS connection. Thus
850     # it is impossible for one principal to retrieve another principal's
851     # credential without having the appropriate private key.
852     #
853     # @param type type of object (user | slice | sa | ma | node
854     # @param name human readable name of object
855     #
856     # @return the string representation of a credential object
857
858     def get_self_credential(self, type, name):
859         self.verify_object_belongs_to_me(name)
860
861         auth_hrn = get_authority(name)
862         if not auth_hrn:
863             auth_hrn = name
864         auth_info = self.get_auth_info(auth_hrn)
865         # find a record that matches
866         records = self.resolve_raw(type, name, must_exist=True)
867         record = records[0]
868
869         gid = record.get_gid_object()
870         peer_cert = self.server.peer_cert
871         if not peer_cert.is_pubkey(gid.get_pubkey()):
872            raise ConnectionKeyGIDMismatch(gid.get_subject())
873
874         # create the credential
875         gid = record.get_gid_object()
876         cred = Credential(subject = gid.get_subject())
877         cred.set_gid_caller(gid)
878         cred.set_gid_object(gid)
879         cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
880         cred.set_pubkey(gid.get_pubkey())
881
882         rl = self.determine_rights(type, name)
883         cred.set_privileges(rl)
884
885         # determine the type of credential that we want to use as a parent for
886         # this credential.
887
888         if (type == "ma") or (type == "node"):
889             auth_kind = "authority,ma"
890         else: # user, slice, sa
891             auth_kind = "authority,sa"
892
893         cred.set_parent(self.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
894
895         cred.encode()
896         cred.sign()
897
898         return cred.save_to_string(save_parents=True)
899
900     ##
901     # verify_cancreate_credential
902     #
903     # Verify that a user can retrieve a particular type of credential. For
904     # slices, the user must be on the researcher list. For SA and MA the user
905     # must be on the pi and operator lists respectively.
906
907     def verify_cancreate_credential(self, src_cred, record):
908         type = record.get_type()
909         cred_object_hrn = src_cred.get_gid_object().get_hrn()
910         config = Config()
911         if cred_object_hrn in [config.GENI_REGISTRY_ROOT_AUTH]:
912             return
913         if type=="slice":
914             researchers = record.get_geni_info().get("researcher", [])
915             if not (cred_object_hrn in researchers):
916                 raise PermissionError(cred_object_hrn + " is not in researcher list for " + record.get_name())
917         elif type == "sa":
918             pis = record.get_geni_info().get("pi", [])
919             if not (cred_object_hrn in pis):
920                 raise PermissionError(cred_object_hrn + " is not in pi list for " + record.get_name())
921         elif type == "ma":
922             operators = record.get_geni_info().get("operator", [])
923             if not (cred_object_hrn in operators):
924                 raise PermissionError(cred_object_hrn + " is not in operator list for " + record.get_name())
925
926     ##
927     # GENI API: Get_credential
928     #
929     # Retrieve a credential for an object.
930     #
931     # If cred==None, then the behavior reverts to get_self_credential()
932     #
933     # @param cred credential object specifying rights of the caller
934     # @param type type of object (user | slice | sa | ma | node)
935     # @param name human readable name of object
936     #
937     # @return the string representation of a credental object
938
939     def get_credential(self, cred, type, name):
940         if not cred:
941             return self.get_self_credential(type, name)
942
943         self.decode_authentication(cred, "getcredential")
944
945         self.verify_object_belongs_to_me(name)
946
947         auth_hrn = get_authority(name)
948         if not auth_hrn:
949             auth_hrn = name
950         auth_info = self.get_auth_info(auth_hrn)
951
952         records = self.resolve_raw(type, name, must_exist=True)
953         record = records[0]
954
955         # verify_cancreate_credential requires that the member lists
956         # (researchers, pis, etc) be filled in
957         self.fill_record_info(record)
958
959         self.verify_cancreate_credential(self.client_cred, record)
960
961         # TODO: Check permission that self.client_cred can access the object
962
963         object_gid = record.get_gid_object()
964         new_cred = Credential(subject = object_gid.get_subject())
965         new_cred.set_gid_caller(self.client_gid)
966         new_cred.set_gid_object(object_gid)
967         new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
968         new_cred.set_pubkey(object_gid.get_pubkey())
969
970         rl = self.determine_rights(type, name)
971         new_cred.set_privileges(rl)
972
973         # determine the type of credential that we want to use as a parent for
974         # this credential.
975
976         if (type == "ma") or (type == "node"):
977             auth_kind = "authority,ma"
978         else: # user, slice, sa
979             auth_kind = "authority,sa"
980
981         new_cred.set_parent(self.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
982
983         new_cred.encode()
984         new_cred.sign()
985
986         return new_cred.save_to_string(save_parents=True)
987
988     ##
989     # GENI API: get_ticket
990     #
991     # Retrieve a ticket. This operation is currently implemented on PLC
992     # only (see SFA, engineering decisions); it is not implemented on
993     # components.
994     #
995     # The ticket is filled in with information from the PLC database. This
996     # information includes resources, and attributes such as user keys and
997     # initscripts.
998     #
999     # @param cred credential string
1000     # @param name name of the slice to retrieve a ticket for
1001     # @param rspec resource specification dictionary
1002     #
1003     # @return the string representation of a ticket object
1004
1005     def get_ticket(self, cred, name, rspec):
1006         self.decode_authentication(cred, "getticket")
1007
1008         self.verify_object_belongs_to_me(name)
1009
1010         self.verify_object_permission(name)
1011
1012         # XXX much of this code looks like get_credential... are they so similar
1013         # that they should be combined?
1014
1015         auth_hrn = get_authority(name)
1016         if not auth_hrn:
1017             auth_hrn = name
1018         auth_info = self.get_auth_info(auth_hrn)
1019
1020         records = self.resolve_raw("slice", name, must_exist=True)
1021         record = records[0]
1022
1023         object_gid = record.get_gid_object()
1024         new_ticket = Ticket(subject = object_gid.get_subject())
1025         new_ticket.set_gid_caller(self.client_gid)
1026         new_ticket.set_gid_object(object_gid)
1027         new_ticket.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
1028         new_ticket.set_pubkey(object_gid.get_pubkey())
1029
1030         self.fill_record_info(record)
1031
1032         (attributes, rspec) = self.record_to_slice_info(record)
1033
1034         new_ticket.set_attributes(attributes)
1035         new_ticket.set_rspec(rspec)
1036
1037         new_ticket.set_parent(AuthHierarchy.get_auth_ticket(auth_hrn))
1038
1039         new_ticket.encode()
1040         new_ticket.sign()
1041
1042         return new_ticket.save_to_string(save_parents=True)
1043
1044     ##
1045     # GENI_API: Create_gid
1046     #
1047     # Create a new GID. For MAs and SAs that are physically located on the
1048     # registry, this allows a owner/operator/PI to create a new GID and have it
1049     # signed by his respective authority.
1050     #
1051     # @param cred credential of caller
1052     # @param name hrn for new GID
1053     # @param uuid unique identifier for new GID
1054     # @param pkey_string public-key string (TODO: why is this a string and not a keypair object?)
1055     #
1056     # @return the string representation of a GID object
1057
1058     def create_gid(self, cred, name, uuid, pubkey_str):
1059         self.decode_authentication(cred, "getcredential")
1060
1061         self.verify_object_belongs_to_me(name)
1062
1063         self.verify_object_permission(name)
1064
1065         if uuid == None:
1066             uuid = create_uuid()
1067
1068         pkey = Keypair()
1069         pkey.load_pubkey_from_string(pubkey_str)
1070         gid = self.hierarchy.create_gid(name, uuid, pkey)
1071
1072         return gid.save_to_string(save_parents=True)
1073