Cleaned code and improved communication with SLA Collector
[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=_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
118         if "headers" not in kwargs:
119             kwargs["headers"] = {"accept": "application/xml"}
120
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         
155         if "testbed" in kwargs:
156             url = url + "?testbed=" + kwargs["testbed"]
157
158         if "headers" not in kwargs:
159             kwargs["headers"] = {"accept": "application/xml",
160                                  "content-type": "application/xml"}
161
162         kwargs["auth"] = HTTPBasicAuth(settings.SLA_MANAGER_USER,
163                                settings.SLA_MANAGER_PASSWORD)
164
165         result = requests.post(url, data, **kwargs)
166         location = result.headers["location"] \
167             if "location" in result.headers else "<null>"
168         print "POST {} {} Location: {}".format(
169             result.url, result.status_code, location)
170         return result
171
172
173 class _Resource(object):
174
175     def __init__(self, url, converter):
176         """Provides some common operations over resources.
177
178         The operations return a structured representation of the resource.
179
180         :param str url: url to the resource
181         :param Converter converter: resouce xml converter
182
183         Some attributes are initialized to be used from the owner if needed:
184         * client: Client instance
185         * converter: resource xml converter
186         * listconverter: list of resources xml converter
187         """
188         self.client = Client(url)
189         self.converter = converter
190         self.listconverter = xmlconverter.ListConverter(self.converter)
191
192     @staticmethod
193     def _processresult(r, converter):
194
195         """Generic processing of the REST call.
196
197          If no errors, tries to convert the result to a destination entity.
198
199         :param r requests:
200         :param converter Converter:
201         """
202         if r.status_code == 404:
203             return None
204
205         content_type = r.headers.get('content-type', '')
206
207         #print("content-type = " + content_type)
208         if content_type == 'application/json':
209             result = r.json()
210         elif content_type == 'application/xml':
211             xml = r.text
212             result = xmlconverter.convertstring(converter, xml)
213         else:
214             result = r.text
215         return result
216
217     def getall(self):
218         """Get all resources
219
220         """
221         r = self.client.get("")
222         resources = self._processresult(r, self.listconverter)
223         return resources, r
224
225     def getbyid(self, id, params):
226         """Get resource 'id'"""
227         r = self.client.get(id, params=params)
228         resource = _Resource._processresult(r, self.converter)
229         return resource, r
230
231     def get(self, path, params):
232         """Generic query over resource: GET /resource?q1=v1&q2=v2...
233
234         :param dict[str,str] params: values to pass as get parameters
235         """
236         if path is None:
237             path = ""
238
239         r = self.client.get(path, params=params)
240         resources = self._processresult(r, self.listconverter)
241         return resources, r
242
243     def create(self, body, **kwargs):
244         """Creates (POST method) a resource.
245
246         It should be convenient to set content-type header.
247
248         Usage:
249             resource.create(body, headers={'content-type': 'application/xml'})
250         """
251         r = self.client.post("", body, **kwargs)
252         r.raise_for_status()
253         return r
254
255
256 class Agreements(object):
257
258     def __init__(self, root_url, path=_AGREEMENTS_PATH):
259         """Business methods for Agreement resource
260         :param str root_url: url to the root of resources
261         :param str path: path to resource from root_url
262
263         The final url to the resource is root_url + "/" + path
264         """
265         resourceurl = _buildpath_(root_url, path)
266         converter = xmlconverter.AgreementConverter()
267         self.res = _Resource(resourceurl, converter)
268
269     def getall(self):
270         """
271         Get all agreements
272
273         :rtype : list[wsag_model.Agreement]
274         """
275         return self.res.getall()
276
277     def getbyid(self, agreementid):
278         """Get an agreement
279
280         :rtype : wsag_model.Agreement
281         """
282         return self.res.getbyid(agreementid)
283
284     def getbyconsumer(self, consumerid):
285         """Get a consumer's agreements
286
287         :rtype : list[wsag_model.Agreement]
288         """
289         return self.res.get(dict(consumerId=consumerid))
290
291     def getbyprovider(self, providerid):
292         """Get the agreements served by a provider
293
294         :rtype : list[wsag_model.Agreement]
295         """
296         return self.res.get(dict(providerId=providerid))
297
298     def getstatus(self, agreementid, testbed):
299         """Get guarantee status of an agreement
300
301         :param str agreementid :
302         :rtype : wsag_model.AgreementStatus
303         """
304         path = _buildpath_(_AGREEMENTS_PATH, agreementid, "guaranteestatus")
305         r = self.res.client.get(path, headers={'accept': 'application/json'},
306                                 params={'testbed': testbed})
307
308         json_obj = r.json()
309
310         status = wsag_model.AgreementStatus.json_decode(json_obj)
311
312         return status, r
313
314     def getbyslice(self, slicename):
315         """Get the agreements corresponding to a slice
316
317         :rtype : list[wsag_model.Agreement]
318         """
319         return self.res.get(slicename, dict())
320
321     def create(self, agreement, testbed):
322         """Create a new agreement
323
324         :param str agreement: sla template in ws-agreement format.
325         """
326         return self.res.create(agreement, params={'testbed': testbed})
327
328
329 class Templates(object):
330
331     def __init__(self, root_url, path=_TEMPLATES_PATH):
332         """Business methods for Templates resource
333         :param str root_url: url to the root of resources
334         :param str path: path to resource from root_url
335
336         The final url to the resource is root_url + "/" + path
337         """
338         resourceurl = _buildpath_(root_url, path)
339         converter = xmlconverter.AgreementConverter()
340         self.res = _Resource(resourceurl, converter)
341
342     def getall(self):
343         """ Get all templates
344
345         :rtype : list[wsag_model.Template]
346         """
347         return self.res.getall()
348
349     def getbyid(self, provider_id, testbed):
350         """Get a template
351
352         :rtype: wsag_model.Template
353         """
354         return self.res.getbyid(provider_id, {"testbed": testbed})
355
356     def create(self, template):
357         """Create a new template
358
359         :param str template: sla template in ws-agreement format.
360         """
361         self.res.create(template)
362
363
364 class Providers(object):
365
366     def __init__(self, root_url, path=_PROVIDERS_PATH):
367         """Business methods for Providers resource
368         :param str root_url: url to the root of resources
369         :param str path: path to resource from root_url
370
371         The final url to the resource is root_url + "/" + path
372         """
373         resourceurl = _buildpath_(root_url, path)
374         converter = xmlconverter.ProviderConverter()
375         self.res = _Resource(resourceurl, converter)
376
377     def getall(self):
378         """ Get all providers
379
380         :rtype : list[wsag_model.Provider]
381         """
382         return self.res.getall()
383
384     def getbyid(self, provider_id):
385         """Get a provider
386
387         :rtype: wsag_model.Provider
388         """
389         return self.res.getbyid(provider_id)
390
391     def create(self, provider):
392         """Create a new provider
393
394         :type provider: wsag_model.Provider
395         """
396         body = provider.to_xml()
397         return self.res.create(body)
398
399
400 class Violations(object):
401
402     def __init__(self, root_url, path=_VIOLATIONS_PATH):
403         """Business methods for Violation resource
404         :param str root_url: url to the root of resources
405         :param str path: path to resource from root_url
406
407         The final url to the resource is root_url + "/" + path
408         """
409         resourceurl = _buildpath_(root_url, path)
410         converter = xmlconverter.ViolationConverter()
411         self.res = _Resource(resourceurl, converter)
412
413     def getall(self):
414         """ Get all violations
415         :rtype : list[wsag_model.Violation]
416         """
417         return self.res.getall()
418
419     def getbyid(self, violationid):
420         """Get a violation
421
422         :rtype : wsag_model.Violation
423         """
424         return self.res.getbyid(violationid)
425
426     def getbyagreement(self, agreement_id, testbed, term=None):
427         """Get the violations of an agreement.
428
429         :param str agreement_id:
430         :param str term: optional GuaranteeTerm name. If not specified,
431             violations from all terms will be returned
432         :rtype: list[wsag_model.Violation]
433         """
434         return self.res.get("", params={"agreementId": agreement_id,
435                                         "guaranteeTerm": term,
436                                         "testbed": testbed})
437
438
439 class Enforcements(object):
440
441     def __init__(self, root_url, path=_ENFORCEMENTJOBS_PATH):
442         """Business methods for Violation resource
443         :param str root_url: url to the root of resources
444         :param str path: path to resource from root_url
445
446         The final url to the resource is root_url + "/" + path
447         """
448         resourceurl = _buildpath_(root_url, path)
449         converter = xmlconverter.EnforcementConverter()
450         self.res = _Resource(resourceurl, converter)
451
452     def getall(self):
453         """ Get all Enforcements
454         :rtype : list[wsag_model.Violation]
455         """
456         return self.res.getall()
457
458     def getbyagreement(self, agreement_id, testbed):
459         """Get the enforcement of an agreement.
460
461         :param str agreement_id:
462
463         :rtype: list[wsag_model.Enforcement]
464         """
465         return self.res.getbyid(agreement_id, params={"testbed": testbed})
466
467
468 def _buildpath_(*paths):
469     if "" in paths:
470         paths = [path for path in paths if path != ""]
471
472     return "/".join(paths)
473
474
475 def main():
476     #
477     # Move to test
478     #
479     global rooturl
480     rooturl = "http://127.0.0.1:8080/sla-service"
481
482     c = Factory.templates()
483     #r = c.getall()
484     #r = c.getbyid("noexiste")
485     #r = c.getstatus("agreement03")
486     #print r
487
488     #r = c.getbyconsumer('RandomClient')
489     r = c.getbyid("template02")
490
491     print r
492
493
494 if __name__ == "__main__":
495     main()