Cleaned code and improved communication with SLA Collector
[unfold.git] / sla / slaclient / xmlconverter.py
1 # -*- coding: utf-8 -*-
2
3 """Converts from XML to objects for ws-agreement agreements/templates or any
4 other xml returned by SLA Manager.
5
6 This module offers a set of converters from xml formats returned by SLA Manager
7 to a more-friendly POJO instances.
8
9 The converters are designed to be pluggable: see ListConverter.
10
11
12 Usage:
13 c = AnyConverter() or
14 c = ListConverter(AnyOtherConverter())
15
16 convertstring(c, "<?xml ... </>")
17
18 convertfile(c, "file.xml")
19
20 root = ElementTree.parse("file.xml")
21 c.convert(root.getroot())
22
23 """
24
25 try:
26     # Much faster and lighter library (C implementation)
27     from xml.etree import cElementTree as ElementTree
28 except ImportError:
29     from xml.etree import ElementTree
30
31 from xml.etree.ElementTree import QName
32
33 import dateutil.parser
34
35 from wsag_model import Agreement
36 from wsag_model import Template
37 from wsag_model import Violation
38 from wsag_model import Provider
39 from wsag_model import Enforcement
40
41
42 def convertfile(converter, f):
43     """Reads and converts a xml file
44
45     :rtype : object
46     :param Converter converter:
47     :param str f: file to read
48     """
49     tree = ElementTree.parse(f)
50     result = converter.convert(tree.getroot())
51     return result
52
53
54 def convertstring(converter, string):
55     """Converts a string
56
57     :rtype : object
58     :param Converter converter:
59     :param str string: contains the xml to convert
60     """
61     root = ElementTree.fromstring(string)
62     result = converter.convert(root)
63     return result
64
65
66 class Converter(object):
67
68     def __init__(self):
69         """Base class for converters
70         """
71         pass
72
73     def convert(self, xmlroot):
74         """Converts the given xml in an object
75
76         :rtype : Object that represents the xml
77         :param Element xmlroot: root element of xml to convert.
78         """
79         return None
80
81
82 class ListConverter(Converter):
83     def __init__(self, innerconverter):
84         super(ListConverter, self).__init__()
85         self.innerconverter = innerconverter
86
87     def convert(self, xmlroot):
88         result = []
89
90         # Converter for the old xml structure
91         # for item in xmlroot.find("items"): # loop through "items" children
92         #     inner = self.innerconverter.convert(item)
93         #     result.append(inner)
94         # return result
95
96         for item in xmlroot:      # loop through children
97             inner = self.innerconverter.convert(item)
98             result.append(inner)
99         return result
100
101
102 class ProviderConverter(Converter):
103     """Converter for a provider.
104
105     Input:
106     <provider>
107         <uuid>1ad9acb9-8dbc-4fe6-9a0b-4244ab6455da</uuid>
108         <name>Provider2</name>
109     </provider>
110
111     Output:
112     wsag_model.Provider
113     """
114
115     def __init__(self):
116         super(ProviderConverter, self).__init__()
117
118     def convert(self, xmlroot):
119         result = Provider()
120         result.uuid = xmlroot.find("uuid").text
121         result.name = xmlroot.find("name").text
122         return result
123
124
125 class EnforcementConverter(Converter):
126     """Converter for an Enforcement job.
127
128     Input:
129     <enforcement_job>
130         <agreement_id>agreement03</agreement_id>
131         <enabled>false</enabled>
132     </enforcement_job>
133
134     Output:
135     wsag_model.Enforcement
136     """
137
138     def __init__(self):
139         super(EnforcementConverter, self).__init__()
140
141     def convert(self, xmlroot):
142         result = Enforcement()
143         result.agreement_id = xmlroot.find("agreement_id").text
144         result.enabled = xmlroot.find("enabled").text
145         return result
146
147
148 class ViolationConverter(Converter):
149     """Converter for a violation.
150
151     Input:
152     <violation>
153         <uuid>1d94627e-c318-41ba-9c45-42c95b67cc32</uuid>
154         <contract_uuid>26e5d5b6-f5a1-4eb3-bc91-606e8f24fb09</contract_uuid>
155         <service_name>servicename1</service_name>
156         <service_scope>test1</service_scope>
157         <metric_name>UpTime</metric_name>
158         <datetime>2014-07-17T09:32:00+02:00</datetime>
159         <actual_value>0.0</actual_value>
160     </violation>
161
162     Output:
163         wsag_model.Violation
164     """
165     def __init__(self):
166         super(ViolationConverter, self).__init__()
167
168     def convert(self, xmlroot):
169         result = Violation()
170         result.uuid = xmlroot.find("uuid").text
171         result.contract_uuid = xmlroot.find("contract_uuid").text
172         result.service_name = xmlroot.find("service_name").text
173         result.service_scope = xmlroot.find("service_scope").text
174         result.metric_name = xmlroot.find("metric_name").text
175         result.actual_value = xmlroot.find("actual_value").text
176         dt_str = xmlroot.find("datetime").text
177         result.datetime = dateutil.parser.parse(dt_str)
178
179         return result
180
181
182 class AgreementConverter(Converter):
183     def __init__(self):
184         """Converter for an ws-agreement agreement or template.
185         """
186         super(AgreementConverter, self).__init__()
187         self._namespaces = {
188             "wsag": "http://www.ggf.org/namespaces/ws-agreement",
189             "sla": "http://sla.atos.eu",
190         }
191         self.agreement_tags = (
192             "{{{}}}Agreement".format(self._namespaces["wsag"]),
193         )
194         self.template_tags = (
195             "{{{}}}Template".format(self._namespaces["wsag"]),
196         )
197
198     def convert(self, xmlroot):
199         """
200         :param Element xmlroot: root element of xml to convert.
201         :rtype: wsag_model.Agreement
202         """
203         for name, value in xmlroot.attrib.items():
204             print '{0}="{1}"'.format(name, value)
205
206         if xmlroot.tag in self.agreement_tags:
207             result = Agreement()
208             agreementId = str(QName(self._namespaces["wsag"], "AgreementId"))
209             result.agreement_id = xmlroot.attrib[agreementId]
210         elif xmlroot.tag in self.template_tags:
211             result = Template()
212             templateId = str(QName(self._namespaces["wsag"], "TemplateId"))
213             result.template_id = xmlroot.attrib[templateId]
214         else:
215             raise ValueError("Not valid root element name: " + xmlroot.tag)
216
217         context = xmlroot.find("wsag:Context", self._namespaces)
218         result.context = self._parse_context(context)
219
220         terms = xmlroot.find("wsag:Terms/wsag:All", self._namespaces)
221
222         properties = terms.findall("wsag:ServiceProperties", self._namespaces)
223         result.variables = self._parse_properties(properties)
224
225         guarantees = terms.findall("wsag:GuaranteeTerm", self._namespaces)
226         result.guaranteeterms = self._parse_guarantees(guarantees)
227
228         return result
229
230     def _parse_context(self, element):
231         nss = self._namespaces
232         result = Agreement.Context()
233
234         result.template_id = self._find_text(element, "wsag:TemplateId")
235         result.expirationtime = self._find_text(element, "wsag:ExpirationTime")
236
237         service_elem = element.find("sla:Service", nss)
238         result.service = \
239             service_elem.text if service_elem is not None else "<servicename>"
240
241         initiator = self._find_text(element, "wsag:AgreementInitiator")
242         responder = self._find_text(element, "wsag:AgreementResponder")
243         serviceprovider_elem = self._find_text(element, "wsag:ServiceProvider")
244
245         #
246         # Deloop the initiator-responder indirection.
247         #
248         if serviceprovider_elem == "AgreementResponder":
249             consumer = initiator
250             provider = responder
251         elif serviceprovider_elem == "AgreementInitiator":
252             consumer = responder
253             provider = initiator
254         else:
255             raise ValueError(
256                 "Invalid value for wsag:ServiceProvider : " +
257                 serviceprovider_elem)
258
259         result.initiator = initiator
260         result.responder = responder
261         result.provider = provider
262         result.consumer = consumer
263
264         return result
265
266     def _parse_property(self, element, servicename):
267         nss = self._namespaces
268
269         key = _get_attribute(element, "Name")
270         value = Agreement.Property()
271         value.servicename = servicename
272         value.name = key
273         value.metric = _get_attribute(element, "Metric")
274         value.location = element.find("wsag:Location", nss).text
275
276         return key, value
277
278     def _parse_properties(self, elements):
279         result = {}
280         nss = self._namespaces
281         for element in elements:
282             servicename = _get_attribute(element, "ServiceName")
283             for var in element.findall("wsag:Variables/wsag:Variable", nss):
284                 key, value = self._parse_property(var, servicename)
285                 result[key] = value
286
287         return result
288
289     def _parse_guarantee_scope(self, element):
290         result = Agreement.GuaranteeTerm.GuaranteeScope()
291         result.servicename = _get_attribute(element, "ServiceName")
292         result.scope = element.text
293         return result
294
295     def _parse_guarantee_scopes(self, elements):
296         result = []
297         for scope in elements:
298             result.append(self._parse_guarantee_scope(scope))
299         return result
300
301     def _parse_guarantee(self, element):
302         nss = self._namespaces
303
304         result = Agreement.GuaranteeTerm()
305         name = _get_attribute(element, "Name")
306         result.name = name
307         scopes = element.findall("wsag:ServiceScope", nss)
308         result.scopes = self._parse_guarantee_scopes(scopes)
309
310         kpitarget = element.find(
311             "wsag:ServiceLevelObjective/wsag:KPITarget", nss)
312         slo = Agreement.GuaranteeTerm.ServiceLevelObjective()
313         result.servicelevelobjective = slo
314         slo.kpiname = kpitarget.find("wsag:KPIName", nss).text
315         slo.customservicelevel = kpitarget.find(
316             "wsag:CustomServiceLevel", nss).text
317
318         return name, result
319
320     def _parse_guarantees(self, elements):
321         result = {}
322         for element in elements:
323             key, value = self._parse_guarantee(element)
324             result[key] = value
325         return result
326
327     def _find_text(self, src, path):
328         """Returns the inner text of the element located in path from the src
329         element; None if no elements were found.
330
331         :type src: Element
332         :type path: src
333         :rtype: str
334
335         Usage:
336             text = _find_text(root, "wsag:Context/ExpirationTime")
337         """
338         dst = src.find(path, self._namespaces)
339         if dst is None:
340             return ""
341         return dst.text
342
343
344 def _get_attribute(element, attrname):
345     """
346     Get attribute from an element.
347
348     Wrapper over Element.attrib, as this doesn't fallback to the element
349     namespace if the attribute is qnamed and the requested attribute name
350     is not.
351
352     Ex:
353         <ns:elem attr1="value1" ns:attr2="value2"/>
354
355         _get_attribute(elem, "attr1") -> value1
356         _get_attribute(elem, "attr2") -> value2
357         _get_attribute(elem, "{uri}:attr1") -> Error
358         _get_attribute(elem, "{uri}:attr2") -> value2
359     """
360     isns = (attrname[0] == '{')
361
362     #
363     # Handle qnamed request:
364     #   attrname = {uri}name
365     #
366     if isns:
367         return element.attrib[attrname]
368
369     #
370     # Handle non-qnamed request and non-qnamed actual_attr
371     #   attrname = name
372     #   actual_attr = name
373     #
374     if attrname in element.attrib:
375         return element.attrib[attrname]
376
377     #
378     # Handle non-qnamed request but qnamed actualAttr
379     #   attrname = name
380     #   actual_attr = {uri}name
381     #
382     tag_uri = element.tag[0: element.tag.find('}') + 1]
383     return element.attrib[tag_uri + attrname]