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