2 # Registry is a GeniServer that implements the Registry interface
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.record import GeniRecord
16 from geni.util.rights import RightList
17 from geni.util.genitable import GeniTable
18 from geni.util.geniticket import Ticket
19 from geni.util.excep import *
20 from geni.util.misc import *
21 from geni.util.config import *
24 # Convert geni fields to PLC fields for use when registering up updating
25 # registry record in the PLC database
27 # @param type type of record (user, slice, ...)
28 # @param hrn human readable name
29 # @param geni_fields dictionary of geni fields
30 # @param pl_fields dictionary of PLC fields (output)
32 def geni_fields_to_pl_fields(type, hrn, geni_fields, pl_fields):
34 if not "email" in pl_fields:
35 if not "email" in geni_fields:
36 raise MissingGeniInfo("email")
37 pl_fields["email"] = geni_fields["email"]
39 if not "first_name" in pl_fields:
40 pl_fields["first_name"] = "geni"
42 if not "last_name" in pl_fields:
43 pl_fields["last_name"] = hrn
46 if not "instantiation" in pl_fields:
47 pl_fields["instantiation"] = "delegated" # "plc-instantiated"
48 if not "name" in pl_fields:
49 pl_fields["name"] = hrn_to_pl_slicename(hrn)
50 if not "max_nodes" in pl_fields:
51 pl_fields["max_nodes"] = 10
54 if not "hostname" in pl_fields:
55 if not "dns" in geni_fields:
56 raise MissingGeniInfo("dns")
57 pl_fields["hostname"] = geni_fields["dns"]
59 if not "model" in pl_fields:
60 pl_fields["model"] = "geni"
63 pl_fields["login_base"] = hrn_to_pl_login_base(hrn)
65 if not "name" in pl_fields:
66 pl_fields["name"] = hrn
68 if not "abbreviated_name" in pl_fields:
69 pl_fields["abbreviated_name"] = hrn
71 if not "enabled" in pl_fields:
72 pl_fields["enabled"] = True
74 if not "is_public" in pl_fields:
75 pl_fields["is_public"] = True
78 # Registry is a GeniServer that serves registry and slice operations at PLC.
80 class Registry(GeniServer):
82 # Create a new registry object.
84 # @param ip the ip address to listen on
85 # @param port the port to listen on
86 # @param key_file private key filename of registry
87 # @param cert_file certificate filename containing public key (could be a GID file)
89 def __init__(self, ip, port, key_file, cert_file):
90 GeniServer.__init__(self, ip, port, key_file, cert_file)
92 # get PL account settings from config module
93 self.pl_auth = get_pl_auth()
95 # connect to planetlab
96 if "Url" in self.pl_auth:
97 self.connect_remote_shell()
99 self.connect_local_shell()
102 # Connect to a remote shell via XMLRPC
104 def connect_remote_shell(self):
105 from geni.util import remoteshell
106 self.shell = remoteshell.RemoteShell()
109 # Connect to a local shell via local API functions
111 def connect_local_shell(self):
113 self.shell = PLC.Shell.Shell(globals = globals())
116 # Register the server RPCs for the registry
118 def register_functions(self):
119 GeniServer.register_functions(self)
121 self.server.register_function(self.create_gid)
122 self.server.register_function(self.get_self_credential)
123 self.server.register_function(self.get_credential)
124 self.server.register_function(self.get_gid)
125 self.server.register_function(self.register)
126 self.server.register_function(self.remove)
127 self.server.register_function(self.update)
128 self.server.register_function(self.list)
129 self.server.register_function(self.resolve)
132 # Given an authority name, return the information for that authority. This
133 # is basically a stub that calls the hierarchy module.
135 # @param auth_hrn human readable name of authority
137 def get_auth_info(self, auth_hrn):
138 return self.hierarchy.get_auth_info(auth_hrn)
141 # Given an authority name, return the database table for that authority. If
142 # the database table does not exist, then one will be automatically
145 # @param auth_name human readable name of authority
147 def get_auth_table(self, auth_name):
148 auth_info = self.get_auth_info(auth_name)
150 table = GeniTable(hrn=auth_name,
151 cninfo=auth_info.get_dbinfo())
153 # if the table doesn't exist, then it means we haven't put any records
154 # into this authority yet.
156 if not table.exists():
157 print "Registry: creating table for authority", auth_name
163 # Verify that an authority belongs to this registry. This is basically left
164 # up to the implementation of the hierarchy module. If the specified name
165 # does not belong to this registry, an exception is thrown indicating the
166 # caller should contact someone else.
168 # @param auth_name human readable name of authority
170 def verify_auth_belongs_to_me(self, name):
171 # get_auth_info will throw an exception if the authority does not
173 self.get_auth_info(name)
176 # Verify that an object belongs to this registry. By extension, this implies
177 # that the authority that owns the object belongs to this registry. If the
178 # object does not belong to this registry, then an exception is thrown.
180 # @param name human readable name of object
182 def verify_object_belongs_to_me(self, name):
183 auth_name = get_authority(name)
185 # the root authority belongs to the registry by default?
186 # TODO: is this true?
188 self.verify_auth_belongs_to_me(auth_name)
191 # Verify that the object_gid that was specified in the credential allows
192 # permission to the object 'name'. This is done by a simple prefix test.
193 # For example, an object_gid for planetlab.us.arizona would match the
194 # objects planetlab.us.arizona.slice1 and planetlab.us.arizona.
196 # @param name human readable name to test
198 def verify_object_permission(self, name):
199 object_hrn = self.object_gid.get_hrn()
200 if object_hrn == name:
202 if name.startswith(object_hrn + "."):
204 raise PermissionError(name)
207 # Fill in the planetlab-specific fields of a Geni record. This involves
208 # calling the appropriate PLC methods to retrieve the database record for
211 # PLC data is filled into the pl_info field of the record.
213 # @param record record to fill in fields (in/out param)
215 def fill_record_pl_info(self, record):
216 type = record.get_type()
217 pointer = record.get_pointer()
219 # records with pointer==-1 do not have plc info associated with them.
220 # for example, the top level authority records which are
221 # authorities, but not PL "sites"
223 record.set_pl_info({})
226 if (type == "sa") or (type == "ma"):
227 pl_res = self.shell.GetSites(self.pl_auth, [pointer])
228 elif (type == "slice"):
229 pl_res = self.shell.GetSlices(self.pl_auth, [pointer])
230 elif (type == "user"):
231 pl_res = self.shell.GetPersons(self.pl_auth, [pointer])
232 elif (type == "node"):
233 pl_res = self.shell.GetNodes(self.pl_auth, [pointer])
235 raise UnknownGeniType(type)
238 # the planetlab record no longer exists
239 # TODO: delete the geni record ?
240 raise PlanetLabRecordDoesNotExist(record.get_name())
242 record.set_pl_info(pl_res[0])
245 # Look up user records given PLC user-ids. This is used as part of the
246 # process for reverse-mapping PLC records into Geni records.
248 # @param auth_table database table for the authority that holds the user records
249 # @param user_id_list list of user ids
250 # @param role either "*" or a string describing the role to look for ("pi", "user", ...)
252 # TODO: This function currently only searches one authority because it would
253 # be inefficient to brute-force search all authorities for a user id. The
254 # solution would likely be to implement a reverse mapping of user-id to
257 def lookup_users(self, auth_table, user_id_list, role="*"):
259 for person_id in user_id_list:
260 user_records = auth_table.find("user", person_id, "pointer")
261 for user_record in user_records:
262 self.fill_record_info(user_record)
264 user_roles = user_record.get_pl_info().get("roles")
265 if (role=="*") or (role in user_roles):
266 record_list.append(user_record.get_name())
270 # Fill in the geni-specific fields of the record.
272 # Note: It is assumed the fill_record_pl_info() has already been performed
275 def fill_record_geni_info(self, record):
277 type = record.get_type()
279 if (type == "slice"):
280 auth_table = self.get_auth_table(get_authority(record.get_name()))
281 person_ids = record.pl_info.get("person_ids", [])
282 researchers = self.lookup_users(auth_table, person_ids)
283 geni_info['researcher'] = researchers
286 auth_table = self.get_auth_table(record.get_name())
287 person_ids = record.pl_info.get("person_ids", [])
288 pis = self.lookup_users(auth_table, person_ids, "pi")
289 geni_info['pi'] = pis
290 # TODO: OrganizationName
293 auth_table = self.get_auth_table(record.get_name())
294 person_ids = record.pl_info.get("person_ids", [])
295 operators = self.lookup_users(auth_table, person_ids, "tech")
296 geni_info['operator'] = operators
297 # TODO: OrganizationName
299 auth_table = self.get_auth_table(record.get_name())
300 person_ids = record.pl_info.get("person_ids", [])
301 owners = self.lookup_users(auth_table, person_ids, "admin")
302 geni_info['owner'] = owners
304 elif (type == "node"):
305 geni_info['dns'] = record.pl_info.get("hostname", "")
306 # TODO: URI, LatLong, IP, DNS
308 elif (type == "user"):
309 geni_info['email'] = record.pl_info.get("email", "")
310 # TODO: PostalAddress, Phone
312 record.set_geni_info(geni_info)
315 # Given a Geni record, fill in the PLC-specific and Geni-specific fields
318 def fill_record_info(self, record):
319 self.fill_record_pl_info(record)
320 self.fill_record_geni_info(record)
325 # Register an object with the registry. In addition to being stored in the
326 # Geni database, the appropriate records will also be created in the
329 # @param cred credential string
330 # @param record_dict dictionary containing record fields
332 def register(self, cred, record_dict):
333 self.decode_authentication(cred, "register")
335 record = GeniRecord(dict = record_dict)
336 type = record.get_type()
337 name = record.get_name()
339 auth_name = get_authority(name)
340 self.verify_object_permission(auth_name)
341 auth_info = self.get_auth_info(auth_name)
342 table = self.get_auth_table(auth_name)
346 # check if record already exists
347 existing_records = table.resolve(type, name)
349 raise ExistingRecord(name)
351 if (type == "sa") or (type=="ma"):
353 if not self.hierarchy.auth_exists(name):
354 self.hierarchy.create_auth(name)
356 # authorities are special since they are managed by the registry
357 # rather than by the caller. We create our own GID for the
358 # authority rather than relying on the caller to supply one.
360 # get the GID from the newly created authority
361 child_auth_info = self.get_auth_info(name)
362 gid = auth_info.get_gid_object()
363 record.set_gid(gid.save_to_string(save_parents=True))
365 geni_fields = record.get_geni_info()
366 site_fields = record.get_pl_info()
368 # if registering a sa, see if a ma already exists
369 # if registering a ma, see if a sa already exists
371 other_rec = table.resolve("ma", record.get_name())
373 other_rec = table.resolve("sa", record.get_name())
376 print "linking ma and sa to the same plc site"
377 pointer = other_rec[0].get_pointer()
379 geni_fields_to_pl_fields(type, name, geni_fields, site_fields)
380 print "adding site with fields", site_fields
381 pointer = self.shell.AddSite(self.pl_auth, site_fields)
383 record.set_pointer(pointer)
385 elif (type == "slice"):
386 geni_fields = record.get_geni_info()
387 slice_fields = record.get_pl_info()
389 geni_fields_to_pl_fields(type, name, geni_fields, slice_fields)
391 pointer = self.shell.AddSlice(self.pl_auth, slice_fields)
392 record.set_pointer(pointer)
394 elif (type == "user"):
395 geni_fields = record.get_geni_info()
396 user_fields = record.get_pl_info()
398 geni_fields_to_pl_fields(type, name, geni_fields, user_fields)
400 pointer = self.shell.AddPerson(self.pl_auth, user_fields)
401 record.set_pointer(pointer)
403 elif (type == "node"):
404 geni_fields = record.get_geni_info()
405 node_fields = record.get_pl_info()
407 geni_fields_to_pl_fields(type, name, geni_fields, node_fields)
409 login_base = hrn_to_pl_login_base(auth_name)
411 print "calling addnode with", login_base, node_fields
412 pointer = self.shell.AddNode(self.pl_auth, login_base, node_fields)
413 record.set_pointer(pointer)
416 raise UnknownGeniType(type)
420 return record.get_gid_object().save_to_string(save_parents=True)
425 # Remove an object from the registry. If the object represents a PLC object,
426 # then the PLC records will also be removed.
428 # @param cred credential string
429 # @param record_dict dictionary containing record fields. The only relevant
430 # fields of the record are 'name' and 'type', which are used to lookup
431 # the current copy of the record in the Geni database, to make sure
432 # that the appopriate record is removed.
434 def remove(self, cred, type, hrn):
435 self.decode_authentication(cred, "remove")
437 self.verify_object_permission(hrn)
439 auth_name = get_authority(hrn)
440 table = self.get_auth_table(auth_name)
442 record_list = table.resolve(type, hrn)
444 raise RecordNotFound(name)
445 record = record_list[0]
449 self.shell.DeletePerson(self.pl_auth, record.get_pointer())
450 elif type == "slice":
451 self.shell.DeleteSlice(self.pl_auth, record.get_pointer())
453 self.shell.DeleteNode(self.pl_auth, record.get_pointer())
454 elif (type == "sa") or (type == "ma"):
456 other_rec = table.resolve("ma", record.get_name())
458 other_rec = table.resolve("sa", record.get_name())
461 # sa and ma both map to a site, so if we are deleting one
462 # but the other still exists, then do not delete the site
463 print "not removing site", record.get_name(), "because either sa or ma still exists"
466 print "removing site", record.get_name()
467 self.shell.DeleteSite(self.pl_auth, record.get_pointer())
469 raise UnknownGeniType(type)
478 # Update an object in the registry. Currently, this only updates the
479 # PLC information associated with the record. The Geni fields (name, type,
482 # The record is expected to have the pl_info field filled in with the data
483 # that should be updated.
485 # TODO: The geni_info member of the record should be parsed and the pl_info
486 # adjusted as necessary (add/remove users from a slice, etc)
488 # @param cred credential string specifying rights of the caller
489 # @param record a record dictionary to be updated
491 def update(self, cred, record_dict):
492 self.decode_authentication(cred, "update")
494 record = GeniRecord(dict = record_dict)
495 type = record.get_type()
497 self.verify_object_permission(record.get_name())
499 auth_name = get_authority(record.get_name())
500 table = self.get_auth_table(auth_name)
502 # make sure the record exists
503 existing_record_list = table.resolve(type, record.get_name())
504 if not existing_record_list:
505 raise RecordNotFound(record.get_name())
507 existing_record = existing_record_list[0]
508 pointer = existing_record.get_pointer()
510 # update the PLC information that was specified with the record
512 if (type == "sa") or (type == "ma"):
513 self.shell.UpdateSite(self.pl_auth, pointer, record.get_pl_info())
515 elif type == "slice":
516 self.shell.UpdateSlice(self.pl_auth, pointer, record.get_pl_info())
519 # SMBAKER: UpdatePerson only allows a limited set of fields to be
520 # updated. Ideally we should have a more generic way of doing
521 # this. I copied the field names from UpdatePerson.py...
523 all_fields = record.get_pl_info()
524 for key in all_fields.keys():
525 if key in ['first_name', 'last_name', 'title', 'email',
526 'password', 'phone', 'url', 'bio', 'accepted_aup',
\r
528 update_fields[key] = all_fields[key]
529 self.shell.UpdatePerson(self.pl_auth, pointer, update_fields)
532 self.shell.UpdateNode(self.pl_auth, pointer, record.get_pl_info())
535 raise UnknownGeniType(type)
538 # List the records in an authority. The objectGID in the supplied credential
539 # should name the authority that will be listed.
541 # TODO: List doesn't take an hrn and uses the hrn contained in the
542 # objectGid of the credential. Does this mean the only way to list an
543 # authority is by having a credential for that authority?
545 # @param cred credential string specifying rights of the caller
547 # @return list of record dictionaries
548 def list(self, cred, auth_hrn):
549 self.decode_authentication(cred, "list")
551 if not self.hierarchy.auth_exists(auth_hrn):
552 raise MissingAuthority(auth_hrn)
554 table = self.get_auth_table(auth_hrn)
556 records = table.list()
559 for record in records:
561 self.fill_record_info(record)
562 good_records.append(record)
563 except PlanetLabRecordDoesNotExist:
564 # silently drop the ones that are missing in PL.
565 # is this the right thing to do?
566 print "ignoring geni record ", record.get_name(), " because pl record does not exist"
570 for record in good_records:
571 dicts.append(record.as_dict())
578 # Resolve a record. This is an internal version of the Resolve API call
579 # and returns records in record object format rather than dictionaries
580 # that may be sent over XMLRPC.
582 # @param type type of record to resolve (user | sa | ma | slice | node)
583 # @param name human readable name of object
584 # @param must_exist if True, throw an exception if no records are found
586 # @return a list of record objects, or an empty list []
588 def resolve_raw(self, type, name, must_exist=True):
589 auth_name = get_authority(name)
591 table = self.get_auth_table(auth_name)
593 records = table.resolve(type, name)
595 if (not records) and must_exist:
596 raise RecordNotFound(name)
599 for record in records:
601 self.fill_record_info(record)
602 good_records.append(record)
603 except PlanetLabRecordDoesNotExist:
604 # silently drop the ones that are missing in PL.
605 # is this the right thing to do?
606 print "ignoring geni record ", record.get_name(), "because pl record does not exist"
614 # This is a wrapper around resolve_raw that converts records objects into
615 # dictionaries before returning them to the user.
617 # @param cred credential string authorizing the caller
618 # @param name human readable name to resolve
620 # @return a list of record dictionaries, or an empty list
622 def resolve(self, cred, name):
623 self.decode_authentication(cred, "resolve")
625 records = self.resolve_raw("*", name)
627 for record in records:
628 dicts.append(record.as_dict())
635 # Retrieve the GID for an object. This function looks up a record in the
636 # registry and returns the GID of the record if it exists.
637 # TODO: Is this function needed? It's a shortcut for Resolve()
639 # @param name hrn to look up
641 # @return the string representation of a GID object
643 def get_gid(self, name):
644 self.verify_object_belongs_to_me(name)
645 records = self.resolve_raw("*", name)
647 for record in records:
648 gid = record.get_gid()
649 gid_string_list.append(gid.save_to_string(save_parents=True))
650 return gid_string_list
653 # Determine tje rights that an object should have. The rights are entirely
654 # dependent on the type of the object. For example, users automatically
655 # get "refresh", "resolve", and "info".
657 # @param type the type of the object (user | sa | ma | slice | node)
658 # @param name human readable name of the object (not used at this time)
660 # @return RightList object containing rights
662 def determine_rights(self, type, name):
665 # rights seem to be somewhat redundant with the type of the credential.
666 # For example, a "sa" credential implies the authority right, because
667 # a sa credential cannot be issued to a user who is not an owner of
678 elif type == "slice":
684 elif type == "component":
690 # GENI API: Get_self_credential
692 # Get_self_credential a degenerate version of get_credential used by a
693 # client to get his initial credential when he doesn't have one. This is
694 # the same as get_credential(..., cred=None,...).
696 # The registry ensures that the client is the principal that is named by
697 # (type, name) by comparing the public key in the record's GID to the
698 # private key used to encrypt the client-side of the HTTPS connection. Thus
699 # it is impossible for one principal to retrieve another principal's
700 # credential without having the appropriate private key.
702 # @param type type of object (user | slice | sa | ma | node
703 # @param name human readable name of object
705 # @return the string representation of a credential object
707 def get_self_credential(self, type, name):
708 self.verify_object_belongs_to_me(name)
710 auth_hrn = get_authority(name)
711 auth_info = self.get_auth_info(auth_hrn)
713 # find a record that matches
714 records = self.resolve_raw(type, name, must_exist=True)
717 gid = record.get_gid_object()
718 peer_cert = self.server.peer_cert
719 if not peer_cert.is_pubkey(gid.get_pubkey()):
720 raise ConnectionKeyGIDMismatch(gid.get_subject())
722 # create the credential
723 gid = record.get_gid_object()
724 cred = Credential(subject = gid.get_subject())
725 cred.set_gid_caller(gid)
726 cred.set_gid_object(gid)
727 cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
728 cred.set_pubkey(gid.get_pubkey())
730 rl = self.determine_rights(type, name)
731 cred.set_privileges(rl)
733 cred.set_parent(self.hierarchy.get_auth_cred(auth_hrn))
738 return cred.save_to_string(save_parents=True)
741 # GENI API: Get_credential
743 # Retrieve a credential for an object.
745 # If cred==None, then the behavior reverts to get_self_credential()
747 # @param cred credential object specifying rights of the caller
748 # @param type type of object (user | slice | sa | ma | node)
749 # @param name human readable name of object
751 # @return the string representation of a credental object
753 def get_credential(self, cred, type, name):
755 return get_self_credential(self, type, name)
757 self.decode_authentication(cred, "getcredential")
759 self.verify_object_belongs_to_me(name)
761 auth_hrn = get_authority(name)
762 auth_info = self.get_auth_info(auth_hrn)
764 records = self.resolve_raw(type, name, must_exist=True)
767 # TODO: Check permission that self.client_cred can access the object
769 object_gid = record.get_gid_object()
770 new_cred = Credential(subject = object_gid.get_subject())
771 new_cred.set_gid_caller(self.client_gid)
772 new_cred.set_gid_object(object_gid)
773 new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
774 new_cred.set_pubkey(object_gid.get_pubkey())
776 rl = self.determine_rights(type, name)
777 new_cred.set_privileges(rl)
779 new_cred.set_parent(self.hierarchy.get_auth_cred(auth_hrn))
784 return new_cred.save_to_string(save_parents=True)
787 # GENI_API: Create_gid
789 # Create a new GID. For MAs and SAs that are physically located on the
790 # registry, this allows a owner/operator/PI to create a new GID and have it
791 # signed by his respective authority.
793 # @param cred credential of caller
794 # @param name hrn for new GID
795 # @param uuid unique identifier for new GID
796 # @param pkey_string public-key string (TODO: why is this a string and not a keypair object?)
798 # @return the string representation of a GID object
800 def create_gid(self, cred, name, uuid, pubkey_str):
801 self.decode_authentication(cred, "getcredential")
803 self.verify_object_belongs_to_me(name)
805 self.verify_object_permission(name)
811 pkey.load_pubkey_from_string(pubkey_str)
812 gid = self.hierarchy.create_gid(name, uuid, pkey)
814 return gid.save_to_string(save_parents=True)