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