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