2 # This module implements the client-side of the Geni API. Stubs are provided
3 # that convert the supplied parameters to the necessary format and send them
4 # via XMLRPC to a Geni Server.
6 # TODO: Investigate ways to combine this with existing PLC API?
14 from sfa.trust.gid import *
15 from sfa.trust.credential import *
16 from sfa.util.record import *
17 from sfa.util.sfaticket import SfaTicket
20 # ServerException, ExceptionUnmarshaller
22 # Used to convert server exception strings back to an exception.
23 # from usenet, Raghuram Devarakonda
25 class ServerException(Exception):
28 class ExceptionUnmarshaller(xmlrpclib.Unmarshaller):
31 return xmlrpclib.Unmarshaller.close(self)
32 except xmlrpclib.Fault, e:
33 raise ServerException(e.faultString)
38 # A transport for XMLRPC that works on top of HTTPS
40 class GeniTransport(xmlrpclib.Transport):
43 def make_connection(self, host):
44 # create a HTTPS connection object from a host descriptor
45 # host may be a string, or a (host, x509-dict) tuple
47 host, extra_headers, x509 = self.get_host_info(host)
49 HTTPS = httplib.HTTPS()
50 except AttributeError:
51 raise NotImplementedError(
52 "your version of httplib doesn't support HTTPS"
55 return httplib.HTTPS(host, None, key_file=self.key_file, cert_file=self.cert_file) #**(x509 or {}))
58 unmarshaller = ExceptionUnmarshaller()
59 parser = xmlrpclib.ExpatParser(unmarshaller)
60 return parser, unmarshaller
63 # The GeniClient class provides stubs for executing Geni operations. A given
64 # client object connects to one server. To connect to multiple servers, create
65 # multiple GeniClient objects.
67 # The Geni protocol uses an HTTPS connection, and the client's side of the
68 # connection uses his private key. Generally, this private key must match the
69 # public key that is containing in the GID that the client is providing for
70 # those functions that take a GID.
74 # Create a new GeniClient object.
76 # @param url is the url of the server
77 # @param key_file = private key file of client
78 # @param cert_file = x.509 cert containing the client's public key. This
79 # could be a GID certificate, or any x.509 cert.
81 def __init__(self, url, key_file, cert_file):
83 self.key_file = key_file
84 self.cert_file = cert_file
85 self.transport = GeniTransport()
86 self.transport.key_file = self.key_file
87 self.transport.cert_file = self.cert_file
88 self.server = xmlrpclib.ServerProxy(self.url, self.transport, allow_none=True)
90 # -------------------------------------------------------------------------
92 # -------------------------------------------------------------------------
95 # Create a new GID. For MAs and SAs that are physically located on the
96 # registry, this allows a owner/operator/PI to create a new GID and have it
97 # signed by his respective authority.
99 # @param cred credential of caller
100 # @param name hrn for new GID
101 # @param uuid unique identifier for new GID
102 # @param pkey_string public-key string (TODO: why is this a string and not a keypair object?)
104 # @return a GID object
106 def create_gid(self, cred, name, uuid, pkey_string):
107 gid_str = self.server.create_gid(cred.save_to_string(save_parents=True), name, uuid, pkey_string)
108 return GID(string=gid_str)
111 # Retrieve the GID for an object. This function looks up a record in the
112 # registry and returns the GID of the record if it exists.
113 # TODO: Is this function needed? It's a shortcut for Resolve()
115 # @param name hrn to look up
117 # @return a GID object
119 def get_gid(self, name):
120 gid_str_list = self.server.get_gid(name)
122 for str in gid_str_list:
123 gid_list.append(GID(string=str))
127 # Get_self_credential a degenerate version of get_credential used by a
128 # client to get his initial credential when he doesn't have one. This is
129 # the same as get_credential(..., cred=None,...).
131 # The registry ensures that the client is the principal that is named by
132 # (type, name) by comparing the public key in the record's GID to the
133 # private key used to encrypt the client-side of the HTTPS connection. Thus
134 # it is impossible for one principal to retrieve another principal's
135 # credential without having the appropriate private key.
137 # @param type type of object (user | slice | sa | ma | node
138 # @param name human readable name of object
140 # @return a credential object
142 def get_self_credential(self, type, name):
143 cred_str = self.server.get_self_credential(type, name)
144 return Credential(string = cred_str)
147 # Retrieve a credential for an object.
149 # If cred==None, then the behavior reverts to get_self_credential()
151 # @param cred credential object specifying rights of the caller
152 # @param type type of object (user | slice | sa | ma | node)
153 # @param name human readable name of object
155 # @return a credental object
157 def get_credential(self, cred, type, name):
159 cred = cred.save_to_string(save_parents=True)
160 cred_str = self.server.get_credential(cred, type, name)
161 return Credential(string = cred_str)
164 # List the records in an authority. The objectGID in the supplied credential
165 # should name the authority that will be listed.
167 # @param cred credential object specifying rights of the caller
169 # @return list of record objects
171 def list(self, cred, auth_hrn):
172 result_dict_list = self.server.list(cred.save_to_string(save_parents=True), auth_hrn)
174 for dict in result_dict_list:
175 result_rec_list.append(GeniRecord(dict=dict))
176 return result_rec_list
179 # Register an object with the registry. In addition to being stored in the
180 # Geni database, the appropriate records will also be created in the
185 # @param cred credential object specifying rights of the caller
186 # @return record to register
188 # @return GID object for the newly-registered record
190 def register(self, cred, record):
191 gid_str = self.server.register(cred.save_to_string(save_parents=True), record.as_dict())
192 return GID(string = gid_str)
195 # Remove an object from the registry. If the object represents a PLC object,
196 # then the PLC records will also be removed.
198 # @param cred credential object specifying rights of the caller
202 def remove(self, cred, type, hrn):
203 result = self.server.remove(cred.save_to_string(save_parents=True), type, hrn)
207 # Resolve an object in the registry. A given HRN may have multiple records
208 # associated with it, and therefore multiple records may be returned. The
209 # caller should check the type fields of the records to find the one that
210 # he is interested in.
212 # @param cred credential object specifying rights of the caller
213 # @param name human readable name of object
215 def resolve(self, cred, name):
216 result_dict_list = self.server.resolve(cred.save_to_string(save_parents=True), name)
218 for dict in result_dict_list:
219 if dict['type'] in ['authority']:
220 result_rec_list.append(AuthorityRecord(dict=dict))
221 elif dict['type'] in ['node']:
222 result_rec_list.append(NodeRecord(dict=dict))
223 elif dict['type'] in ['slice']:
224 result_rec_list.append(SliceRecord(dict=dict))
225 elif dict['type'] in ['user']:
226 result_rec_list.append(UserRecord(dict=dict))
228 result_rec_list.append(GeniRecord(dict=dict))
229 return result_rec_list
232 # Update an object in the registry. Currently, this only updates the
233 # PLC information associated with the record. The Geni fields (name, type,
238 # @param cred credential object specifying rights of the caller
239 # @param record a record object to be updated
241 def update(self, cred, record):
242 result = self.server.update(cred.save_to_string(save_parents=True), record.as_dict())
246 #-------------------------------------------------------------------------
247 # Aggregate Interface
248 #-------------------------------------------------------------------------
252 # @param cred a credential
253 # @param hrn slice hrn
255 def get_resources(self, cred, hrn=None):
256 result = self.server.get_resources(cred.save_to_string(save_parents=True), hrn)
259 def get_aggregates(self, cred, hrn=None):
260 result = self.server.get_resources(cred.save_to_string(save_parents=True), hrn)
265 # @param cred a credential
267 def get_policy(self, cred):
268 result = self.server.get_policy(cred.save_to_string(save_parents=True))
273 # @param cred a credential
274 # @param rspec resource specification defining how to instantiate the slice
276 def create_slice(self, cred, hrn, rspec):
277 result = self.server.create_slice(cred.save_to_string(save_parents=True), hrn, rspec)
283 # @param cred a credential
284 # @param hrn slice to delete
285 def delete_slice(self, cred, hrn):
286 result = self.server.delete_slice(cred.save_to_string(save_parents=True), hrn)
289 # ------------------------------------------------------------------------
291 # ------------------------------------------------------------------------
296 # @param cred a credential identifying the caller (callerGID) and the slice
299 def start_slice(self, cred, hrn):
300 result = self.server.start_slice(cred.save_to_string(save_parents=True), hrn)
306 # @param cred a credential identifying the caller (callerGID) and the slice
309 def stop_slice(self, cred, hrn):
310 result = self.server.stop_slice(cred.save_to_string(save_parents=True), hrn)
316 # @param cred a credential identifying the caller (callerGID) and the slice
319 def reset_slice(self, cred, hrn):
320 result = self.server.reset_slice(cred.save_to_string(save_parents=True), hrn)
326 # @param cred a credential identifying the caller (callerGID) and the slice
329 def delete_slice(self, cred, hrn):
330 result = self.server.delete_slice(cred.save_to_string(save_parents=True), hrn)
334 # List the slices on a component.
336 # @param cred credential object that authorizes the caller
338 # @return a list of slice names
340 def get_slices(self, cred):
341 result = self.server.get_slices(cred.save_to_string(save_parents=True))
345 # Retrieve a ticket. This operation is currently implemented on the
346 # registry (see SFA, engineering decisions), and is not implemented on
349 # The ticket is filled in with information from the PLC database. This
350 # information includes resources, and attributes such as user keys and
353 # @param cred credential object
354 # @param name name of the slice to retrieve a ticket for
355 # @param rspec resource specification dictionary
357 # @return a ticket object
359 def get_ticket(self, cred, name, rspec):
360 ticket_str = self.server.get_ticket(cred.save_to_string(save_parents=True), name, rspec)
361 ticket = SfaTicket(string=ticket_str)
365 # Redeem a ticket. This operation is currently implemented on the
368 # The ticket is submitted to the node manager, and the slice is instantiated
369 # or updated as appropriate.
371 # TODO: This operation should return a sliver credential and indicate
372 # whether or not the component will accept only sliver credentials, or
373 # will accept both sliver and slice credentials.
375 # @param ticket a ticket object containing the ticket
377 def redeem_ticket(self, ticket):
378 result = self.server.redeem_ticket(ticket.save_to_string(save_parents=True))