SLA Application:
[unfold.git] / sla / slaclient / restclient.py
1 # -*- coding: utf-8 -*-
2
3 import requests
4
5 from requests.auth import HTTPBasicAuth
6
7 import xmlconverter
8 import wsag_model
9
10 from django.conf import settings
11
12
13 """REST client to SLA Manager.
14
15 Contains a generic rest client and wrappers over this generic client
16 for each resource.
17
18 Each resource client implements business-like() functions, but
19 returns a tuple (output, requests.Response)
20
21 The resource clients are initialized with the rooturl and a path, which
22 are combined to build the resource url. The path is defaulted to the known
23 resource path. So, for example, to create a agreements client:
24
25 c = Agreements("http://localhost/slagui-service")
26
27 A Factory facility is provided to create resource client instances. The
28 Factory uses "rooturl" module variable to use as rooturl parameter.
29
30 restclient.rooturl = "http://localhost/slagui-service"
31 c = restclient.Factory.agreements()
32
33 """
34
35 _PROVIDERS_PATH = "providers"
36 _AGREEMENTS_PATH = "agreements"
37 _TEMPLATES_PATH = "templates"
38 _VIOLATIONS_PATH = "violations"
39 _ENFORCEMENTJOBS_PATH = "enforcements"
40
41 rooturl = settings.SLA_MANAGER_URL
42
43
44 class Factory(object):
45     @staticmethod
46     def agreements(path):
47         """Returns a REST client for Agreements
48
49         :rtype : Agreements
50          """
51         return Agreements(rooturl, path)
52
53     @staticmethod
54     def providers():
55         """Returns a REST client for Providers
56
57         :rtype : Providers
58         """
59         return Providers(rooturl)
60
61     @staticmethod
62     def violations():
63         """Returns a REST client for Violations
64
65         :rtype : Violations
66         """
67         return Violations(rooturl)
68
69     @staticmethod
70     def templates():
71         """Returns a REST client for Violations
72
73         :rtype : Violations
74         """
75         return Templates(rooturl)
76
77     @staticmethod
78     def enforcements():
79         """Returns a REST client for Enforcements jobs
80
81         :rtype : Enforcements
82         """
83         return Enforcements(rooturl)
84
85
86 class Client(object):
87
88     def __init__(self, root_url):
89
90         """Generic rest client using requests library
91
92         Each operation mimics the corresponding "requests" operation (arguments
93         and return)
94
95         :param str root_url: this url is used as prefix in all subsequent
96             requests
97         """
98         self.rooturl = root_url
99
100     def get(self, path, **kwargs):
101         """Just a wrapper over request.get, just in case.
102
103         Returns a requests.Response
104
105         :rtype : request.Response
106         :param str path: remaining path from root url;
107             empty if desired path equal to rooturl.
108         :param kwargs: arguments to requests.get
109
110         Example:
111             c = Client("http://localhost:8080/service")
112             c.get("/resource", headers = { "accept": "application/json" })
113         """
114         url = _buildpath_(self.rooturl, path)
115         if "testbed" in kwargs:
116             url = url + "?testbed=" + kwargs["testbed"]
117         #kwargs['params']['testbed'] = 'iminds'
118
119         if "headers" not in kwargs:
120             kwargs["headers"] = {"accept": "application/xml"}
121         # kwargs["auth"] = HTTPBasicAuth(settings.SLA_MANAGER_USER,
122         #                                settings.SLA_MANAGER_PASSWORD)
123
124         for key, values in kwargs.iteritems():
125             print key, values
126
127         result = requests.get(url, **kwargs)
128         print "GET {} {} {}".format(
129             result.url, result.status_code, result.text[0:70])
130         print result.encoding
131
132         return result
133
134     def post(self, path, data=None, **kwargs):
135         """Just a wrapper over request.post, just in case
136
137         :rtype : request.Response
138         :param str path: remaining path from root url;
139             empty if desired path equal to rooturl.
140         :param dict[str, str] kwargs: arguments to requests.post
141
142         Example:
143             c = Client("http://localhost:8080/service")
144             c.post(
145                 '/resource',
146                 '{ "id": "1", "name": "provider-a" }',
147                 headers = {
148                     "content-type": "application/json",
149                     "accept": "application/xml"
150                 }
151             )
152         """
153         url = _buildpath_(self.rooturl, path)
154         url = url + "?testbed=iminds"  # TODO remove hardcoded string
155         # kwargs["auth"] = HTTPBasicAuth(settings.SLA_MANAGER_USER,
156         #                                settings.SLA_MANAGER_PASSWORD)
157         if "headers" not in kwargs:
158             kwargs = {"accept": "application/xml",
159                       "content-type": "application/xml"}
160         result = requests.post(url, data, **kwargs)
161         location = result.headers["location"] \
162             if "location" in result.headers else "<null>"
163         print "POST {} {} Location: {}".format(
164             result.url, result.status_code, location)
165         return result
166
167
168 class _Resource(object):
169
170     def __init__(self, url, converter):
171         """Provides some common operations over resources.
172
173         The operations return a structured representation of the resource.
174
175         :param str url: url to the resource
176         :param Converter converter: resouce xml converter
177
178         Some attributes are initialized to be used from the owner if needed:
179         * client: Client instance
180         * converter: resource xml converter
181         * listconverter: list of resources xml converter
182         """
183         self.client = Client(url)
184         self.converter = converter
185         self.listconverter = xmlconverter.ListConverter(self.converter)
186
187     @staticmethod
188     def _processresult(r, converter):
189
190         """Generic processing of the REST call.
191
192          If no errors, tries to convert the result to a destination entity.
193
194         :param r requests:
195         :param converter Converter:
196         """
197         if r.status_code == 404:
198             return None
199
200         content_type = r.headers.get('content-type', '')
201
202         #print("content-type = " + content_type)
203         if content_type == 'application/json':
204             result = r.json()
205         elif content_type == 'application/xml':
206             xml = r.text
207             result = xmlconverter.convertstring(converter, xml)
208         else:
209             result = r.text
210         return result
211
212     def getall(self):
213         """Get all resources
214
215         """
216         r = self.client.get("")
217         resources = self._processresult(r, self.listconverter)
218         return resources, r
219
220     def getbyid(self, id, params):
221         """Get resource 'id'"""
222         r = self.client.get(id, params=params)
223         resource = _Resource._processresult(r, self.converter)
224         return resource, r
225
226     def get(self, path, params):
227         """Generic query over resource: GET /resource?q1=v1&q2=v2...
228
229         :param dict[str,str] params: values to pass as get parameters
230         """
231         if path is None:
232             path = ""
233
234         r = self.client.get(path, params=params)
235         resources = self._processresult(r, self.listconverter)
236         return resources, r
237
238     def create(self, body, **kwargs):
239         """Creates (POST method) a resource.
240
241         It should be convenient to set content-type header.
242
243         Usage:
244             resource.create(body, headers={'content-type': 'application/xml'})
245         """
246         r = self.client.post("", body, **kwargs)
247         r.raise_for_status()
248         return r
249
250
251 class Agreements(object):
252
253     def __init__(self, root_url, path=_AGREEMENTS_PATH):
254         """Business methods for Agreement resource
255         :param str root_url: url to the root of resources
256         :param str path: path to resource from root_url
257
258         The final url to the resource is root_url + "/" + path
259         """
260         resourceurl = _buildpath_(root_url, path)
261         converter = xmlconverter.AgreementConverter()
262         self.res = _Resource(resourceurl, converter)
263
264     def getall(self):
265         """
266         Get all agreements
267
268         :rtype : list[wsag_model.Agreement]
269         """
270         return self.res.getall()
271
272     def getbyid(self, agreementid):
273         """Get an agreement
274
275         :rtype : wsag_model.Agreement
276         """
277         return self.res.getbyid(agreementid)
278
279     def getbyconsumer(self, consumerid):
280         """Get a consumer's agreements
281
282         :rtype : list[wsag_model.Agreement]
283         """
284         return self.res.get(dict(consumerId=consumerid))
285
286     def getbyprovider(self, providerid):
287         """Get the agreements served by a provider
288
289         :rtype : list[wsag_model.Agreement]
290         """
291         return self.res.get(dict(providerId=providerid))
292
293     def getstatus(self, agreementid, testbed):
294         """Get guarantee status of an agreement
295
296         :param str agreementid :
297         :rtype : wsag_model.AgreementStatus
298         """
299         path = _buildpath_(_AGREEMENTS_PATH, agreementid, "guaranteestatus")
300         r = self.res.client.get(path, headers={'accept': 'application/json'},
301                                 params={'testbed': testbed})
302
303         json_obj = r.json()
304
305         status = wsag_model.AgreementStatus.json_decode(json_obj)
306
307         return status, r
308
309     def getbyslice(self, slicename):
310         """Get the agreements corresponding to a slice
311
312         :rtype : list[wsag_model.Agreement]
313         """
314         return self.res.get(slicename, dict())
315
316     def create(self, agreement):
317         """Create a new agreement
318
319         :param str agreement: sla template in ws-agreement format.
320         """
321         return self.res.create(agreement)
322
323
324 class Templates(object):
325
326     def __init__(self, root_url, path=_TEMPLATES_PATH):
327         """Business methods for Templates resource
328         :param str root_url: url to the root of resources
329         :param str path: path to resource from root_url
330
331         The final url to the resource is root_url + "/" + path
332         """
333         resourceurl = _buildpath_(root_url, path)
334         converter = xmlconverter.AgreementConverter()
335         self.res = _Resource(resourceurl, converter)
336
337     def getall(self):
338         """ Get all templates
339
340         :rtype : list[wsag_model.Template]
341         """
342         return self.res.getall()
343
344     def getbyid(self, provider_id):
345         """Get a template
346
347         :rtype: wsag_model.Template
348         """
349         return self.res.getbyid(provider_id, None)
350
351     def create(self, template):
352         """Create a new template
353
354         :param str template: sla template in ws-agreement format.
355         """
356         self.res.create(template)
357
358
359 class Providers(object):
360
361     def __init__(self, root_url, path=_PROVIDERS_PATH):
362         """Business methods for Providers resource
363         :param str root_url: url to the root of resources
364         :param str path: path to resource from root_url
365
366         The final url to the resource is root_url + "/" + path
367         """
368         resourceurl = _buildpath_(root_url, path)
369         converter = xmlconverter.ProviderConverter()
370         self.res = _Resource(resourceurl, converter)
371
372     def getall(self):
373         """ Get all providers
374
375         :rtype : list[wsag_model.Provider]
376         """
377         return self.res.getall()
378
379     def getbyid(self, provider_id):
380         """Get a provider
381
382         :rtype: wsag_model.Provider
383         """
384         return self.res.getbyid(provider_id)
385
386     def create(self, provider):
387         """Create a new provider
388
389         :type provider: wsag_model.Provider
390         """
391         body = provider.to_xml()
392         return self.res.create(body)
393
394
395 class Violations(object):
396
397     def __init__(self, root_url, path=_VIOLATIONS_PATH):
398         """Business methods for Violation resource
399         :param str root_url: url to the root of resources
400         :param str path: path to resource from root_url
401
402         The final url to the resource is root_url + "/" + path
403         """
404         resourceurl = _buildpath_(root_url, path)
405         converter = xmlconverter.ViolationConverter()
406         self.res = _Resource(resourceurl, converter)
407
408     def getall(self):
409         """ Get all violations
410         :rtype : list[wsag_model.Violation]
411         """
412         return self.res.getall()
413
414     def getbyid(self, violationid):
415         """Get a violation
416
417         :rtype : wsag_model.Violation
418         """
419         return self.res.getbyid(violationid)
420
421     def getbyagreement(self, agreement_id, testbed, term=None):
422         """Get the violations of an agreement.
423
424         :param str agreement_id:
425         :param str term: optional GuaranteeTerm name. If not specified,
426             violations from all terms will be returned
427         :rtype: list[wsag_model.Violation]
428         """
429         return self.res.get("", params={"agreementId": agreement_id,
430                                         "guaranteeTerm": term,
431                                         "testbed": testbed})
432
433
434 class Enforcements(object):
435
436     def __init__(self, root_url, path=_ENFORCEMENTJOBS_PATH):
437         """Business methods for Violation resource
438         :param str root_url: url to the root of resources
439         :param str path: path to resource from root_url
440
441         The final url to the resource is root_url + "/" + path
442         """
443         resourceurl = _buildpath_(root_url, path)
444         converter = xmlconverter.EnforcementConverter()
445         self.res = _Resource(resourceurl, converter)
446
447     def getall(self):
448         """ Get all Enforcements
449         :rtype : list[wsag_model.Violation]
450         """
451         return self.res.getall()
452
453     def getbyagreement(self, agreement_id, testbed):
454         """Get the enforcement of an agreement.
455
456         :param str agreement_id:
457
458         :rtype: list[wsag_model.Enforcement]
459         """
460         return self.res.getbyid(agreement_id, params={"testbed": testbed})
461
462
463 def _buildpath_(*paths):
464     if "" in paths:
465         paths = [path for path in paths if path != ""]
466
467     return "/".join(paths)
468
469
470 def main():
471     #
472     # Move to test
473     #
474     global rooturl
475     rooturl = "http://127.0.0.1:8080/sla-service"
476
477     c = Factory.templates()
478     #r = c.getall()
479     #r = c.getbyid("noexiste")
480     #r = c.getstatus("agreement03")
481     #print r
482
483     #r = c.getbyconsumer('RandomClient')
484     r = c.getbyid("template02")
485
486     print r
487
488
489 if __name__ == "__main__":
490     main()