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