+##
+# Geni Registry Wrapper
+#
+# This wrapper implements the Geni Registry.
+#
+# There are several items that need to be done before starting the registry.
+#
+# 1) Import the existing planetlab database, creating the
+# appropriate geni records. This is done by running the "import.py" tool.
+#
+# 2) Create a "trusted_roots" directory and place the certificate of the root
+# authority in that directory. Given the defaults in import.py, this certificate
+# would be named "planetlab.gid". For example,
+#
+# mkdir trusted_roots; cp authorities/planetlab.gid trusted_roots/
+##
+
import tempfile
import os
import time
from genitable import *
from geniticket import *
+##
+# Convert geni fields to PLC fields for use when registering up updating
+# registry record in the PLC database
+#
+# @params type type of record (user, slice, ...)
+# @params hrn human readable name
+# @params geni_fields dictionary of geni fields
+# @params pl_fields dictionary of PLC fields (output)
+
def geni_fields_to_pl_fields(type, hrn, geni_fields, pl_fields):
if type == "user":
if not "email" in pl_fields:
if not "is_public" in pl_fields:
pl_fields["is_public"] = True
+##
+# Registry is a GeniServer that serves registry requests. It also serves
+# component and slice operations that are implemented on the registry
+# due to SFA engineering decisions
+#
+
class Registry(GeniServer):
+ ##
+ # Create a new registry object.
+ #
+ # @param ip the ip address to listen on
+ # @param port the port to listen on
+ # @param key_file private key filename of registry
+ # @param cert_file certificate filename containing public key (could be a GID file)
+
def __init__(self, ip, port, key_file, cert_file):
GeniServer.__init__(self, ip, port, key_file, cert_file)
else:
self.connect_local_shell()
+ ##
+ # Connect to a remote shell via XMLRPC
+
def connect_remote_shell(self):
import remoteshell
self.shell = remoteshell.RemoteShell()
+ ##
+ # Connect to a local shell via local API functions
+
def connect_local_shell(self):
import PLC.Shell
self.shell = PLC.Shell.Shell(globals = globals())
+ ##
+ # Register the server RPCs for the registry
+
def register_functions(self):
GeniServer.register_functions(self)
# registry interface
# component interface
self.server.register_function(self.get_ticket)
- def get_auth_info(self, name):
- return AuthHierarchy.get_auth_info(name)
+ ##
+ # Given an authority name, return the information for that authority. This
+ # is basically a stub that calls the hierarchy module.
+ #
+ # @param auth_hrn human readable name of authority
+
+ def get_auth_info(self, auth_hrn):
+ return AuthHierarchy.get_auth_info(auth_hrn)
+
+ ##
+ # Given an authority name, return the database table for that authority. If
+ # the database table does not exist, then one will be automatically
+ # created.
+ #
+ # @param auth_name human readable name of authority
def get_auth_table(self, auth_name):
auth_info = self.get_auth_info(auth_name)
return table
+ ##
+ # Verify that an authority belongs to this registry. This is basically left
+ # up to the implementation of the hierarchy module. If the specified name
+ # does not belong to this registry, an exception is thrown indicating the
+ # caller should contact someone else.
+ #
+ # @param auth_name human readable name of authority
+
def verify_auth_belongs_to_me(self, name):
# get_auth_info will throw an exception if the authority does not
# exist
self.get_auth_info(name)
+ ##
+ # Verify that an object belongs to this registry. By extension, this implies
+ # that the authority that owns the object belongs to this registry. If the
+ # object does not belong to this registry, then an exception is thrown.
+ #
+ # @param name human readable name of object
+
def verify_object_belongs_to_me(self, name):
auth_name = get_authority(name)
if not auth_name:
return
self.verify_auth_belongs_to_me(auth_name)
+ ##
+ # Verify that the object_gid that was specified in the credential allows
+ # permission to the object 'name'. This is done by a simple prefix test.
+ # For example, an object_gid for planetlab.us.arizona would match the
+ # objects planetlab.us.arizona.slice1 and planetlab.us.arizona.
+ #
+ # @param name human readable name to test
+
def verify_object_permission(self, name):
object_hrn = self.object_gid.get_hrn()
if object_hrn == name:
return
raise PermissionError(name)
+ ##
+ # Fill in the planetlab-specific fields of a Geni record. This involves
+ # calling the appropriate PLC methods to retrieve the database record for
+ # the object.
+ #
+ # PLC data is filled into the pl_info field of the record.
+ #
+ # @param record record to fill in fields (in/out param)
+
def fill_record_pl_info(self, record):
type = record.get_type()
pointer = record.get_pointer()
record.set_pl_info(pl_res[0])
+ ##
+ # Look up user records given PLC user-ids. This is used as part of the
+ # process for reverse-mapping PLC records into Geni records.
+ #
+ # @param auth_table database table for the authority that holds the user records
+ # @param user_id_list list of user ids
+ # @param role either "*" or a string describing the role to look for ("pi", "user", ...)
+ #
+ # TODO: This function currently only searches one authority because it would
+ # be inefficient to brute-force search all authorities for a user id. The
+ # solution would likely be to implement a reverse mapping of user-id to
+ # (type, hrn) pairs.
+
+ def lookup_users(self, auth_table, user_id_list, role="*"):
+ record_list = []
+ for person_id in user_id_list:
+ user_records = auth_table.find("user", person_id, "pointer")
+ for user_record in user_records:
+ self.fill_record_info(user_record)
+
+ user_roles = user_record.get_pl_info().get("roles")
+ if (role=="*") or (role in user_roles):
+ record_list.append(user_record.get_name())
+ return record_list
+
+ ##
+ # Fill in the geni-specific fields of the record.
+ #
+ # Note: It is assumed the fill_record_pl_info() has already been performed
+ # on the record.
+
def fill_record_geni_info(self, record):
- pass
+ geni_info = {}
+ type = record.get_type()
+
+ if (type == "slice"):
+ auth_table = self.get_auth_table(get_authority(record.get_name()))
+ person_ids = record.pl_info.get("person_ids", [])
+ researchers = self.lookup_users(auth_table, person_ids)
+ geni_info['researcher'] = researchers
+
+ elif (type == "sa"):
+ auth_table = self.get_auth_table(record.get_name())
+ person_ids = record.pl_info.get("person_ids", [])
+ pis = self.lookup_users(auth_table, person_ids, "pi")
+ geni_info['pi'] = pis
+ # TODO: OrganizationName
+
+ elif (type == "ma"):
+ auth_table = self.get_auth_table(record.get_name())
+ person_ids = record.pl_info.get("person_ids", [])
+ operators = self.lookup_users(auth_table, person_ids, "tech")
+ geni_info['operator'] = operators
+ # TODO: OrganizationName
+
+ auth_table = self.get_auth_table(record.get_name())
+ person_ids = record.pl_info.get("person_ids", [])
+ owners = self.lookup_users(auth_table, person_ids, "admin")
+ geni_info['owner'] = owners
+
+ elif (type == "node"):
+ geni_info['dns'] = record.pl_info.get("hostname", "")
+ # TODO: URI, LatLong, IP, DNS
+
+ elif (type == "user"):
+ geni_info['email'] = record.pl_info.get("email", "")
+ # TODO: PostalAddress, Phone
+
+ record.set_geni_info(geni_info)
+
+ ##
+ # Given a Geni record, fill in the PLC-specific and Geni-specific fields
+ # in the record.
def fill_record_info(self, record):
self.fill_record_pl_info(record)
self.fill_record_geni_info(record)
+ ##
+ # GENI API: register
+ #
+ # Register an object with the registry. In addition to being stored in the
+ # Geni database, the appropriate records will also be created in the
+ # PLC databases
+ #
+ # @param cred credential string
+ # @param record_dict dictionary containing record fields
+
def register(self, cred, record_dict):
self.decode_authentication(cred, "register")
return record.get_gid_object().save_to_string(save_parents=True)
+ ##
+ # GENI API: remove
+ #
+ # Remove an object from the registry. If the object represents a PLC object,
+ # then the PLC records will also be removed.
+ #
+ # @param cred credential string
+ # @param record_dict dictionary containing record fields. The only relevant
+ # fields of the record are 'name' and 'type', which are used to lookup
+ # the current copy of the record in the Geni database, to make sure
+ # that the appopriate record is removed.
+
def remove(self, cred, record_dict):
self.decode_authentication(cred, "remove")
return True
+ ##
+ # GENI API: Register
+ #
+ # Update an object in the registry. Currently, this only updates the
+ # PLC information associated with the record. The Geni fields (name, type,
+ # GID) are fixed.
+ #
+ # The record is expected to have the pl_info field filled in with the data
+ # that should be updated.
+ #
+ # TODO: The geni_info member of the record should be parsed and the pl_info
+ # adjusted as necessary (add/remove users from a slice, etc)
+ #
+ # @param cred credential string specifying rights of the caller
+ # @param record a record dictionary to be updated
+
def update(self, cred, record_dict):
self.decode_authentication(cred, "update")
existing_record = existing_record_list[0]
pointer = existing_record.get_pointer()
+ # update the PLC information that was specified with the record
+
if (type == "sa") or (type == "ma"):
self.shell.UpdateSite(self.pl_auth, pointer, record.get_pl_info())
else:
raise UnknownGeniType(type)
+ ##
+ # List the records in an authority. The objectGID in the supplied credential
+ # should name the authority that will be listed.
+ #
# TODO: List doesn't take an hrn and uses the hrn contained in the
# objectGid of the credential. Does this mean the only way to list an
- # authority is by having a credential for that authority?
+ # authority is by having a credential for that authority?
+ #
+ # @param cred credential string specifying rights of the caller
+ #
+ # @return list of record dictionaries
def list(self, cred):
self.decode_authentication(cred, "list")
return dict_list
+ ##
+ # Resolve a record. This is an internal version of the Resolve API call
+ # and returns records in record object format rather than dictionaries
+ # that may be sent over XMLRPC.
+ #
+ # @param type type of record to resolve (user | sa | ma | slice | node)
+ # @param name human readable name of object
+ # @param must_exist if True, throw an exception if no records are found
+ #
+ # @return a list of record objects, or an empty list []
+
def resolve_raw(self, type, name, must_exist=True):
auth_name = get_authority(name)
return good_records
+ ##
+ # GENI API: Resolve
+ #
+ # This is a wrapper around resolve_raw that converts records objects into
+ # dictionaries before returning them to the user.
+ #
+ # @param cred credential string authorizing the caller
+ # @param name human readable name to resolve
+ #
+ # @return a list of record dictionaries, or an empty list
+
def resolve(self, cred, name):
self.decode_authentication(cred, "resolve")
return dicts
+ ##
+ # GENI API: get_gid
+ #
+ # Retrieve the GID for an object. This function looks up a record in the
+ # registry and returns the GID of the record if it exists.
+ # TODO: Is this function needed? It's a shortcut for Resolve()
+ #
+ # @param name hrn to look up
+ #
+ # @return the string representation of a GID object
+
def get_gid(self, name):
self.verify_object_belongs_to_me(name)
records = self.resolve_raw("*", name)
gid_string_list.append(gid.save_to_string(save_parents=True))
return gid_string_list
+ ##
+ # Determine tje rights that an object should have. The rights are entirely
+ # dependent on the type of the object. For example, users automatically
+ # get "refresh", "resolve", and "info".
+ #
+ # @param type the type of the object (user | sa | ma | slice | node)
+ # @param name human readable name of the object (not used at this time)
+ #
+ # @return RightList object containing rights
+
def determine_rights(self, type, name):
rl = RightList()
return rl
+ ##
+ # GENI API: Get_self_credential
+ #
+ # Get_self_credential a degenerate version of get_credential used by a
+ # client to get his initial credential when he doesn't have one. This is
+ # the same as get_credential(..., cred=None,...).
+ #
+ # The registry ensures that the client is the principal that is named by
+ # (type, name) by comparing the public key in the record's GID to the
+ # private key used to encrypt the client-side of the HTTPS connection. Thus
+ # it is impossible for one principal to retrieve another principal's
+ # credential without having the appropriate private key.
+ #
+ # @param type type of object (user | slice | sa | ma | node
+ # @param name human readable name of object
+ #
+ # @return the string representation of a credential object
def get_self_credential(self, type, name):
self.verify_object_belongs_to_me(name)
return cred.save_to_string(save_parents=True)
+ ##
+ # GENI API: Get_credential
+ #
+ # Retrieve a credential for an object.
+ #
+ # If cred==None, then the behavior reverts to get_self_credential()
+ #
+ # @param cred credential object specifying rights of the caller
+ # @param type type of object (user | slice | sa | ma | node)
+ # @param name human readable name of object
+ #
+ # @return the string representation of a credental object
+
def get_credential(self, cred, type, name):
if not cred:
return get_self_credential(self, type, name)
return new_cred.save_to_string(save_parents=True)
+ ##
+ # GENI_API: Create_gid
+ #
+ # Create a new GID. For MAs and SAs that are physically located on the
+ # registry, this allows a owner/operator/PI to create a new GID and have it
+ # signed by his respective authority.
+ #
+ # @param cred credential of caller
+ # @param name hrn for new GID
+ # @param uuid unique identifier for new GID
+ # @param pkey_string public-key string (TODO: why is this a string and not a keypair object?)
+ #
+ # @return the string representation of a GID object
+
def create_gid(self, cred, name, uuid, pubkey_str):
self.decode_authentication(cred, "getcredential")
# ------------------------------------------------------------------------
# Component Interface
+ ##
+ # Convert a PLC record into the slice information that will be stored in
+ # a ticket. There are two parts to this information: attributes and
+ # rspec.
+ #
+ # Attributes are non-resource items, such as keys and the initscript
+ # Rspec is a set of resource specifications
+ #
+ # @param record a record object
+ #
+ # @return a tuple (attrs, rspec) of dictionaries
+
def record_to_slice_info(self, record):
# get the user keys from the slice
return (attributes, rspec)
+ ##
+ # GENI API: get_ticket
+ #
+ # Retrieve a ticket. This operation is currently implemented on the
+ # registry (see SFA, engineering decisions), and is not implemented on
+ # components.
+ #
+ # The ticket is filled in with information from the PLC database. This
+ # information includes resources, and attributes such as user keys and
+ # initscripts.
+ #
+ # @param cred credential string
+ # @param name name of the slice to retrieve a ticket for
+ # @param rspec resource specification dictionary
+ #
+ # @return the string representation of a ticket object
def get_ticket(self, cred, name, rspec):
self.decode_authentication(cred, "getticket")