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