Fixed sla creation calls and cleaned debug messages
[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
207         if xmlroot.tag in self.agreement_tags:
208             result = Agreement()
209             agreementId = str(QName(self._namespaces["wsag"], "AgreementId"))
210             result.agreement_id = xmlroot.attrib[agreementId]
211         elif xmlroot.tag in self.template_tags:
212             result = Template()
213             templateId = str(QName(self._namespaces["wsag"], "TemplateId"))
214             result.template_id = xmlroot.attrib[templateId]
215         else:
216             raise ValueError("Not valid root element name: " + xmlroot.tag)
217
218         context = xmlroot.find("wsag:Context", self._namespaces)
219         result.context = self._parse_context(context)
220
221         terms = xmlroot.find("wsag:Terms/wsag:All", self._namespaces)
222
223         properties = terms.findall("wsag:ServiceProperties", self._namespaces)
224         result.variables = self._parse_properties(properties)
225
226         guarantees = terms.findall("wsag:GuaranteeTerm", self._namespaces)
227         result.guaranteeterms = self._parse_guarantees(guarantees)
228
229         return result
230
231     def _parse_context(self, element):
232         nss = self._namespaces
233         result = Agreement.Context()
234
235         result.template_id = self._find_text(element, "wsag:TemplateId")
236         result.expirationtime = self._find_text(element, "wsag:ExpirationTime")
237
238         service_elem = element.find("sla:Service", nss)
239         result.service = \
240             service_elem.text if service_elem is not None else "<servicename>"
241
242         initiator = self._find_text(element, "wsag:AgreementInitiator")
243         responder = self._find_text(element, "wsag:AgreementResponder")
244         serviceprovider_elem = self._find_text(element, "wsag:ServiceProvider")
245
246         #
247         # Deloop the initiator-responder indirection.
248         #
249         if serviceprovider_elem == "AgreementResponder":
250             consumer = initiator
251             provider = responder
252         elif serviceprovider_elem == "AgreementInitiator":
253             consumer = responder
254             provider = initiator
255         else:
256             raise ValueError(
257                 "Invalid value for wsag:ServiceProvider : " +
258                 serviceprovider_elem)
259
260         result.initiator = initiator
261         result.responder = responder
262         result.provider = provider
263         result.consumer = consumer
264
265         return result
266
267     def _parse_property(self, element, servicename):
268         nss = self._namespaces
269
270         key = _get_attribute(element, "Name")
271         value = Agreement.Property()
272         value.servicename = servicename
273         value.name = key
274         value.metric = _get_attribute(element, "Metric")
275         value.location = element.find("wsag:Location", nss).text
276
277         return key, value
278
279     def _parse_properties(self, elements):
280         result = {}
281         nss = self._namespaces
282         for element in elements:
283             servicename = _get_attribute(element, "ServiceName")
284             for var in element.findall("wsag:VariableSet/wsag:Variable", nss):
285                 key, value = self._parse_property(var, servicename)
286                 result[key] = value
287
288         return result
289
290     def _parse_guarantee_scope(self, element):
291         result = Agreement.GuaranteeTerm.GuaranteeScope()
292         result.servicename = _get_attribute(element, "ServiceName")
293         result.scope = element.text
294         return result
295
296     def _parse_guarantee_scopes(self, elements):
297         result = []
298         for scope in elements:
299             result.append(self._parse_guarantee_scope(scope))
300         return result
301
302     def _parse_guarantee(self, element):
303         nss = self._namespaces
304
305         result = Agreement.GuaranteeTerm()
306         name = _get_attribute(element, "Name")
307         result.name = name
308         scopes = element.findall("wsag:ServiceScope", nss)
309         result.scopes = self._parse_guarantee_scopes(scopes)
310
311         kpitarget = element.find(
312             "wsag:ServiceLevelObjective/wsag:KPITarget", nss)
313         slo = Agreement.GuaranteeTerm.ServiceLevelObjective()
314         result.servicelevelobjective = slo
315         slo.kpiname = kpitarget.find("wsag:KPIName", nss).text
316         slo.customservicelevel = kpitarget.find(
317             "wsag:CustomServiceLevel", nss).text
318
319         return name, result
320
321     def _parse_guarantees(self, elements):
322         result = {}
323         for element in elements:
324             key, value = self._parse_guarantee(element)
325             result[key] = value
326         return result
327
328     def _find_text(self, src, path):
329         """Returns the inner text of the element located in path from the src
330         element; None if no elements were found.
331
332         :type src: Element
333         :type path: src
334         :rtype: str
335
336         Usage:
337             text = _find_text(root, "wsag:Context/ExpirationTime")
338         """
339         dst = src.find(path, self._namespaces)
340         if dst is None:
341             return ""
342         return dst.text
343
344
345 def _get_attribute(element, attrname):
346     """
347     Get attribute from an element.
348
349     Wrapper over Element.attrib, as this doesn't fallback to the element
350     namespace if the attribute is qnamed and the requested attribute name
351     is not.
352
353     Ex:
354         <ns:elem attr1="value1" ns:attr2="value2"/>
355
356         _get_attribute(elem, "attr1") -> value1
357         _get_attribute(elem, "attr2") -> value2
358         _get_attribute(elem, "{uri}:attr1") -> Error
359         _get_attribute(elem, "{uri}:attr2") -> value2
360     """
361     isns = (attrname[0] == '{')
362
363     #
364     # Handle qnamed request:
365     #   attrname = {uri}name
366     #
367     if isns:
368         return element.attrib[attrname]
369
370     #
371     # Handle non-qnamed request and non-qnamed actual_attr
372     #   attrname = name
373     #   actual_attr = name
374     #
375     if attrname in element.attrib:
376         return element.attrib[attrname]
377
378     #
379     # Handle non-qnamed request but qnamed actualAttr
380     #   attrname = name
381     #   actual_attr = {uri}name
382     #
383     tag_uri = element.tag[0: element.tag.find('}') + 1]
384     return element.attrib[tag_uri + attrname]