steps towards computing wsdl files at build-time:
[sfa.git] / sfa / util / geniclient.py
1 ##
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.
5 #
6 # TODO: Investigate ways to combine this with existing PLC API?
7 ##
8
9 ### $Id$
10 ### $URL$
11
12 import xmlrpclib
13
14 from sfa.trust.gid import *
15 from sfa.trust.credential import *
16 from sfa.util.record import *
17 from sfa.util.geniticket import *
18
19 ##
20 # ServerException, ExceptionUnmarshaller
21 #
22 # Used to convert server exception strings back to an exception.
23 #    from usenet, Raghuram Devarakonda
24
25 class ServerException(Exception):
26     pass
27
28 class ExceptionUnmarshaller(xmlrpclib.Unmarshaller):
29     def close(self):
30         try:
31             return xmlrpclib.Unmarshaller.close(self)
32         except xmlrpclib.Fault, e:
33             raise ServerException(e.faultString)
34
35 ##
36 # GeniTransport
37 #
38 # A transport for XMLRPC that works on top of HTTPS
39
40 class GeniTransport(xmlrpclib.Transport):
41     key_file = None
42     cert_file = None
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
46         import httplib
47         host, extra_headers, x509 = self.get_host_info(host)
48         try:
49             HTTPS = httplib.HTTPS()
50         except AttributeError:
51             raise NotImplementedError(
52                 "your version of httplib doesn't support HTTPS"
53                 )
54         else:
55             return httplib.HTTPS(host, None, key_file=self.key_file, cert_file=self.cert_file) #**(x509 or {}))
56
57     def getparser(self):
58         unmarshaller = ExceptionUnmarshaller()
59         parser = xmlrpclib.ExpatParser(unmarshaller)
60         return parser, unmarshaller
61
62 ##
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.
66 #
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.
71
72 class GeniClient:
73     ##
74     # Create a new GeniClient object.
75     #
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.
80
81     def __init__(self, url, key_file, cert_file):
82        self.url = url
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)
89
90     # -------------------------------------------------------------------------
91     # Registry Interface
92     # -------------------------------------------------------------------------
93
94     ##
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.
98     #
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?)
103     #
104     # @return a GID object
105
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)
109
110     ##
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()
114     #
115     # @param name hrn to look up
116     #
117     # @return a GID object
118
119     def get_gid(self, name):
120        gid_str_list = self.server.get_gid(name)
121        gid_list = []
122        for str in gid_str_list:
123            gid_list.append(GID(string=str))
124        return gid_list
125
126     ##
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,...).
130     #
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.
136     #
137     # @param type type of object (user | slice | sa | ma | node
138     # @param name human readable name of object
139     #
140     # @return a credential object
141
142     def get_self_credential(self, type, name):
143         cred_str = self.server.get_self_credential(type, name)
144         return Credential(string = cred_str)
145
146     ##
147     # Retrieve a credential for an object.
148     #
149     # If cred==None, then the behavior reverts to get_self_credential()
150     #
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
154     #
155     # @return a credental object
156
157     def get_credential(self, cred, type, name):
158         if cred:
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)
162
163     ##
164     # List the records in an authority. The objectGID in the supplied credential
165     # should name the authority that will be listed.
166     #
167     # @param cred credential object specifying rights of the caller
168     #
169     # @return list of record objects
170
171     def list(self, cred, auth_hrn):
172         result_dict_list = self.server.list(cred.save_to_string(save_parents=True), auth_hrn)
173         result_rec_list = []
174         for dict in result_dict_list:
175              result_rec_list.append(GeniRecord(dict=dict))
176         return result_rec_list
177
178     ##
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
181     # PLC databases.
182     #
183     #
184     #
185     # @param cred credential object specifying rights of the caller
186     # @return record to register
187     #
188     # @return GID object for the newly-registered record
189
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)
193
194     ##
195     # Remove an object from the registry. If the object represents a PLC object,
196     # then the PLC records will also be removed.
197     #
198     # @param cred credential object specifying rights of the caller
199     # @param type
200     # @param hrn
201
202     def remove(self, cred, type, hrn):
203         result = self.server.remove(cred.save_to_string(save_parents=True), type, hrn)
204         return result
205
206     ##
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.
211     #
212     # @param cred credential object specifying rights of the caller
213     # @param name human readable name of object
214
215     def resolve(self, cred, name):
216         result_dict_list = self.server.resolve(cred.save_to_string(save_parents=True), name)
217         result_rec_list = []
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))
227             else:
228                 result_rec_list.append(GeniRecord(dict=dict))
229         return result_rec_list
230
231     ##
232     # Update an object in the registry. Currently, this only updates the
233     # PLC information associated with the record. The Geni fields (name, type,
234     # GID) are fixed.
235     #
236     #
237     #
238     # @param cred credential object specifying rights of the caller
239     # @param record a record object to be updated
240
241     def update(self, cred, record):
242         result = self.server.update(cred.save_to_string(save_parents=True), record.as_dict())
243         return result
244
245
246     #-------------------------------------------------------------------------
247     # Aggregate Interface
248     #-------------------------------------------------------------------------
249     
250     ## list resources
251     #
252     # @param cred a credential
253     # @param hrn slice hrn
254
255     def get_resources(self, cred, hrn=None):
256         result = self.server.get_resources(cred.save_to_string(save_parents=True), hrn)
257         return result
258
259     def get_aggregates(self, cred, hrn=None):
260         result = self.server.get_resources(cred.save_to_string(save_parents=True), hrn)
261         return result
262
263     ## get policy
264     #
265     # @param cred a credential
266
267     def get_policy(self, cred):
268         result = self.server.get_policy(cred.save_to_string(save_parents=True))
269         return result
270
271     ## create slice
272     #
273     # @param cred a credential
274     # @param rspec resource specification defining how to instantiate the slice
275     
276     def create_slice(self, cred, hrn, rspec):
277         result = self.server.create_slice(cred.save_to_string(save_parents=True), hrn, rspec)
278         return result
279
280
281     ## delete slice
282     #
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)
287         return result    
288
289     # ------------------------------------------------------------------------
290     # Slice Interface
291     # ------------------------------------------------------------------------
292
293     ##
294     # Start a slice.
295     #
296     # @param cred a credential identifying the caller (callerGID) and the slice
297     #     (objectGID)
298
299     def start_slice(self, cred, hrn):
300         result = self.server.start_slice(cred.save_to_string(save_parents=True), hrn)
301         return result
302
303     ##
304     # Stop a slice.
305     #
306     # @param cred a credential identifying the caller (callerGID) and the slice
307     #     (objectGID)
308
309     def stop_slice(self, cred, hrn):
310         result = self.server.stop_slice(cred.save_to_string(save_parents=True), hrn)
311         return result
312
313     ##
314     # Reset a slice.
315     #
316     # @param cred a credential identifying the caller (callerGID) and the slice
317     #     (objectGID)
318
319     def reset_slice(self, cred, hrn):
320         result = self.server.reset_slice(cred.save_to_string(save_parents=True), hrn)
321         return result
322
323     ##
324     # Delete a slice.
325     #
326     # @param cred a credential identifying the caller (callerGID) and the slice
327     #     (objectGID)
328
329     def delete_slice(self, cred, hrn):
330         result = self.server.delete_slice(cred.save_to_string(save_parents=True), hrn)
331         return result
332
333     ##
334     # List the slices on a component.
335     #
336     # @param cred credential object that authorizes the caller
337     #
338     # @return a list of slice names
339
340     def get_slices(self, cred):
341         result = self.server.get_slices(cred.save_to_string(save_parents=True))
342         return result
343
344     ##
345     # Retrieve a ticket. This operation is currently implemented on the
346     # registry (see SFA, engineering decisions), and is not implemented on
347     # components.
348     #
349     # The ticket is filled in with information from the PLC database. This
350     # information includes resources, and attributes such as user keys and
351     # initscripts.
352     #
353     # @param cred credential object
354     # @param name name of the slice to retrieve a ticket for
355     # @param rspec resource specification dictionary
356     #
357     # @return a ticket object
358
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 = Ticket(string=ticket_str)
362         return ticket
363
364     ##
365     # Redeem a ticket. This operation is currently implemented on the
366     # component.
367     #
368     # The ticket is submitted to the node manager, and the slice is instantiated
369     # or updated as appropriate.
370     #
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.
374     #
375     # @param ticket a ticket object containing the ticket
376
377     def redeem_ticket(self, ticket):
378         result = self.server.redeem_ticket(ticket.save_to_string(save_parents=True))
379         return result
380
381