-# geniclient.py
-#
-# geni client
-#
-# implements the client-side of the GENI API.
+##
+# This module implements the client-side of the Geni API. Stubs are provided
+# that convert the supplied parameters to the necessary format and send them
+# via XMLRPC to a Geni Server.
#
# TODO: Investigate ways to combine this with existing PLC API?
+##
import xmlrpclib
from gid import *
from credential import *
from record import *
+from geniticket import *
+
+##
+# ServerException, ExceptionUnmarshaller
+#
+# Used to convert server exception strings back to an exception.
+# from usenet, Raghuram Devarakonda
+
+class ServerException(Exception):
+ pass
+class ExceptionUnmarshaller(xmlrpclib.Unmarshaller):
+ def close(self):\r
+ try:\r
+ return xmlrpclib.Unmarshaller.close(self)\r
+ except xmlrpclib.Fault, e:\r
+ raise ServerException(e.faultString)
+
+##
# GeniTransport
#
# A transport for XMLRPC that works on top of HTTPS
else:\r
return httplib.HTTPS(host, None, key_file=self.key_file, cert_file=self.cert_file) #**(x509 or {}))\r
\r
-# GeniClient:\r
+ def getparser(self):\r
+ unmarshaller = ExceptionUnmarshaller()\r
+ parser = xmlrpclib.ExpatParser(unmarshaller)\r
+ return parser, unmarshaller\r
+\r
+##\r
+# The GeniClient class provides stubs for executing Geni operations. A given\r
+# client object connects to one server. To connect to multiple servers, create\r
+# multiple GeniClient objects.\r
#\r
-# Class for performing GeniClient operations.\r
+# The Geni protocol uses an HTTPS connection, and the client's side of the\r
+# connection uses his private key. Generally, this private key must match the\r
+# public key that is containing in the GID that the client is providing for\r
+# those functions that take a GID.\r
\r
class GeniClient():
- # url = url of server
- # key_file = private key file of client
- # cert_file = x.509 cert of client
+ ##
+ # Create a new GeniClient object.
+ #
+ # @param url is the url of the server
+ # @param key_file = private key file of client
+ # @param cert_file = x.509 cert containing the client's public key. This
+ # could be a GID certificate, or any x.509 cert.
+
def __init__(self, url, key_file, cert_file):
self.url = url
self.key_file = key_file
self.transport = GeniTransport()
self.transport.key_file = self.key_file
self.transport.cert_file = self.cert_file
- self.server = xmlrpclib.ServerProxy(self.url, self.transport)
+ self.server = xmlrpclib.ServerProxy(self.url, self.transport, allow_none=True)
+
+ # -------------------------------------------------------------------------
+ # Registry Interface
+ # -------------------------------------------------------------------------
+
+ ##
+ # 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 a GID object
+
+ def create_gid(self, cred, name, uuid, pkey_string):
+ gid_str = self.server.create_gid(cred.save_to_string(save_parents=True), name, uuid, pkey_string)
+ return GID(string=gid_str)
+
+ ##
+ # 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 a GID object
def get_gid(self, name):
gid_str_list = self.server.get_gid(name)
gid_list.append(GID(string=str))
return gid_list
- # 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,...).
#
- # a degenerate version of get_credential used by a client to get his
- # initial credential when he doesn't have one. The same as calling
- # 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 a credential object
def get_self_credential(self, type, name):
- cred_str = self.server.get_self_credential(type, name)
- return Credential(string = cred_str)
+ cred_str = self.server.get_self_credential(type, name)
+ return Credential(string = cred_str)
+
+ ##
+ # 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 a credental object
def get_credential(self, cred, type, name):
- cred_str = self.server.get_credential(cred.save_to_string(), type, name)
- return Credential(string = cred_str)
+ if cred == None:
+ return self.get_self_credential(type, name)
+ cred_str = self.server.get_credential(cred.save_to_string(save_parents=True), type, name)
+ return Credential(string = cred_str)
+
+ ##
+ # List the records in an authority. The objectGID in the supplied credential
+ # should name the authority that will be listed.
+ #
+ # @param cred credential object specifying rights of the caller
+ #
+ # @return list of record objects
+
+ def list(self, cred):
+ result_dict_list = self.server.list(cred.save_to_string(save_parents=True))
+ result_rec_list = []
+ for dict in result_dict_list:
+ result_rec_list.append(GeniRecord(dict=dict))
+ return result_rec_list
+
+ ##
+ # 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.
+ #
+ # The geni_info and/or pl_info fields must in the record must be filled
+ # out correctly depending on the type of record that is being registered.
+ #
+ # 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 object specifying rights of the caller
+ # @return record to register
+ #
+ # @return GID object for the newly-registered record
+
+ def register(self, cred, record):
+ gid_str = self.server.register(cred.save_to_string(save_parents=True), record.as_dict())
+ return GID(string = gid_str)
+
+ ##
+ # Remove an object from the registry. If the object represents a PLC object,
+ # then the PLC records will also be removed.
+ #
+ # @param cred credential object specifying rights of the caller
+ # @param record record to register. 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):
+ result = self.server.remove(cred.save_to_string(save_parents=True), record.as_dict())
+ return result
+
+ ##
+ # Resolve an object in the registry. A given HRN may have multiple records
+ # associated with it, and therefore multiple records may be returned. The
+ # caller should check the type fields of the records to find the one that
+ # he is interested in.
+ #
+ # @param cred credential object specifying rights of the caller
+ # @param name human readable name of object
def resolve(self, cred, name):
- result_dict_list = self.server.resolve(cred.save_to_string(), name)
- result_rec_list = []
- for dict in result_dict_list:
- result_rec_list.append(GeniRecord(dict=dict))
- return result_rec_list
+ result_dict_list = self.server.resolve(cred.save_to_string(save_parents=True), name)
+ result_rec_list = []
+ for dict in result_dict_list:
+ result_rec_list.append(GeniRecord(dict=dict))
+ return result_rec_list
+
+ ##
+ # 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 object specifying rights of the caller
+ # @param record a record object to be updated
+
+ def update(self, cred, record):
+ result = self.server.update(cred.save_to_string(save_parents=True), record.as_dict())
+ return result
+
+ # ------------------------------------------------------------------------
+ # Slice Interface
+ # ------------------------------------------------------------------------
+
+ ##
+ # Start a slice.
+ #
+ # @param cred a credential identifying the caller (callerGID) and the slice
+ # (objectGID)
+
+ def start_slice(self, cred):
+ result = self.server.start_slice(cred.save_to_string(save_parents=True))
+ return result
+
+ ##
+ # Stop a slice.
+ #
+ # @param cred a credential identifying the caller (callerGID) and the slice
+ # (objectGID)
+
+ def stop_slice(self, cred):
+ result = self.server.stop_slice(cred.save_to_string(save_parents=True))
+ return result
+
+ ##
+ # Reset a slice.
+ #
+ # @param cred a credential identifying the caller (callerGID) and the slice
+ # (objectGID)
+
+ def reset_slice(self, cred):
+ result = self.server.reset_slice(cred.save_to_string(save_parents=True))
+ return result
+
+ ##
+ # Delete a slice.
+ #
+ # @param cred a credential identifying the caller (callerGID) and the slice
+ # (objectGID)
+
+ def delete_slice(self, cred):
+ result = self.server.delete_slice(cred.save_to_string(save_parents=True))
+ return result
+
+ ##
+ # List the slices on a component.
+ #
+ # @param cred credential object that authorizes the caller
+ #
+ # @return a list of slice names
+
+ def list_slices(self, cred):
+ result = self.server.list_slices(cred.save_to_string(save_parents=True))
+ return result
+
+ ##
+ # 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 object
+ # @param name name of the slice to retrieve a ticket for
+ # @param rspec resource specification dictionary
+ #
+ # @return a ticket object
+
+ def get_ticket(self, cred, name, rspec):
+ ticket_str = self.server.get_ticket(cred.save_to_string(save_parents=True), name, rspec)
+ ticket = Ticket(string=ticket_str)
+ return ticket
+
+ ##
+ # Redeem a ticket. This operation is currently implemented on the
+ # component.
+ #
+ # The ticket is submitted to the node manager, and the slice is instantiated
+ # or updated as appropriate.
+ #
+ # TODO: This operation should return a sliver credential and indicate
+ # whether or not the component will accept only sliver credentials, or
+ # will accept both sliver and slice credentials.
+ #
+ # @param ticket a ticket object containing the ticket
+
+ def redeem_ticket(self, ticket):
+ result = self.server.redeem_ticket(ticket.save_to_string(save_parents=True))
+ return result
+