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