Update certificates for OpenFlow TLS connection
[integration/test.git] / csit / libraries / XmlComparator.py
1 """
2 Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3
4 This program and the accompanying materials are made available under the
5 terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 and is available at http://www.eclipse.org/legal/epl-v10.html
7
8 Created on May 21, 2014
9
10 @author: <a href="mailto:vdemcak@cisco.com">Vaclav Demcak</a>
11 """
12 from xml.dom.minidom import Element
13 import ipaddr
14 import xml.dom.minidom as md
15 import copy
16
17 KEY_NOT_FOUND = "<KEY_NOT_FOUND>"  # KeyNotFound for dictDiff
18
19
20 class XMLtoDictParserTools:
21     @staticmethod
22     def parseTreeToDict(node, returnedDict=None, ignoreList=[]):
23         """
24         Return Dictionary representation of the xml Tree DOM Element.
25         Repeated tags are put to the array sorted by key (id or order)
26         otherwise is the value represented by tag key name.
27         @param node: DOM Element
28         @param returnedDict : dictionary (default value None)
29         @param ignereList : list of ignored tags for the xml Tree DOM Element
30                             (default value is empty list)
31         @return: dict representation for the input DOM Element
32         """
33         returnedDict = {} if returnedDict is None else returnedDict
34         if node.nodeType == Element.ELEMENT_NODE:
35             nodeKey = node.localName
36             if nodeKey not in ignoreList:
37                 if node.childNodes is not None:
38                     childDict = {}
39                     for child in node.childNodes:
40                         if child.nodeType == Element.TEXT_NODE:
41                             nodeValue = child.nodeValue
42                             if (len(nodeValue.strip(" \t\n\r"))) > 0:
43                                 XMLtoDictParserTools.addDictValue(
44                                     returnedDict, nodeKey, nodeValue
45                                 )
46                                 nodeKey = None
47                                 break
48                         elif child.nodeType == Element.ELEMENT_NODE:
49                             childDict = XMLtoDictParserTools.parseTreeToDict(
50                                 child, childDict, ignoreList
51                             )
52
53                     XMLtoDictParserTools.addDictValue(returnedDict, nodeKey, childDict)
54
55         return returnedDict
56
57     @staticmethod
58     def addDictValue(m_dict, key, value):
59         def _allign_address(value):
60             """unifies output"""
61             n = ipaddr.IPNetwork(value)
62             return "{0}/{1}".format(n.network.exploded, n.prefixlen)
63
64         def _convert_numbers(value):
65             if value.startswith("0x"):
66                 return str(int(value, 16))
67             return str(int(value))
68
69         if key is not None:
70             if isinstance(value, str):
71                 # we need to predict possible differences
72                 # for same value in upper or lower case
73                 value = value.lower()
74             if key not in m_dict:
75                 # lets add mask for ips withot mask
76                 if key in [
77                     "ipv4-destination",
78                     "ipv4-source",
79                     "ipv6-destination",
80                     "ipv6-source",
81                     "ipv6-nd-target",
82                 ]:
83                     nvalue = _allign_address(value)
84                     m_dict[key] = nvalue
85                 elif key in [
86                     "tunnel-mask",
87                     "type",
88                     "metadata-mask",
89                     "out_port",
90                     "out_group",
91                 ]:
92                     nvalue = _convert_numbers(value)
93                     m_dict[key] = nvalue
94                 else:
95                     m_dict[key] = value
96             else:
97                 exist_value = m_dict.get(key)
98                 if type(exist_value) is dict:
99                     list_values = [exist_value, value]
100                     key_for_sort = XMLtoDictParserTools.searchKey(exist_value)
101                     if key_for_sort is not None:
102                         list_values = sorted(list_values, key=lambda k: k[key_for_sort])
103                     m_dict[key] = list_values
104                 elif isinstance(exist_value, list):
105                     exist_value.append(value)
106                     list_values = exist_value
107                     key_for_sort = XMLtoDictParserTools.searchKey(value)
108                     if key_for_sort is not None:
109                         list_values = sorted(list_values, key=lambda k: k[key_for_sort])
110                     m_dict[key] = list_values
111                 else:
112                     m_dict[key] += value
113
114     @staticmethod
115     def searchKey(dictionary):
116         """
117         Return an order key for the array ordering. OF_13
118         allows only two possible kind of the order keys
119         'order' or '*-id'
120         @param dictionary: dictionary with data
121         @return: the array order key
122         """
123         subKeyStr = ["-id", "order"]
124         for substr in subKeyStr:
125             for key in dictionary:
126                 if key == substr:
127                     return key
128                 elif key.endswith(substr):
129                     return key
130         return None
131
132     @staticmethod
133     def getDifferenceDict(original_dict, responded_dict):
134         """
135         Return a dict of keys that differ with another config object.  If a value is
136         not found in one fo the configs, it will be represented by KEY_NOT_FOUND.
137         @param original_dict:   Fist dictionary to diff.
138         @param responded_dict:  Second dictionary to diff.
139         @return diff:   Dict of Key => (original_dict.val, responded_dict.val)
140                         Dict of Key => (original_key, KEY_NOT_FOUND)
141                         Dict of Key => (KEY_NOT_FOUNE, original_key)
142         """
143         diff = {}
144         # Check all keys in original_dict dict
145         for key in list(original_dict.keys()):
146             if key not in responded_dict:
147                 # missing key in responded dict
148                 diff[key] = (key, KEY_NOT_FOUND)
149             # check values of the dictionaries
150             elif original_dict[key] != responded_dict[key]:
151                 # values are not the same #
152
153                 orig_dict_val = original_dict[key]
154                 resp_dict_val = responded_dict[key]
155
156                 # check value is instance of dictionary
157                 if isinstance(orig_dict_val, dict) and isinstance(resp_dict_val, dict):
158                     sub_dif = XMLtoDictParserTools.getDifferenceDict(
159                         orig_dict_val, resp_dict_val
160                     )
161                     if sub_dif:
162                         diff[key] = sub_dif
163
164                 # check value is instance of list
165                 # TODO - > change a basic comparator to compare by id or order
166                 elif isinstance(orig_dict_val, list) and isinstance(
167                     resp_dict_val, list
168                 ):
169                     sub_list_diff = {}
170                     # the list lengths
171                     orig_i, resp_i = len(orig_dict_val), len(resp_dict_val)
172                     # define a max iteration length (less from both)
173                     min_index = orig_i if orig_i < resp_i else resp_i
174                     for index in range(0, min_index, 1):
175                         if orig_dict_val[index] != resp_dict_val[index]:
176                             sub_list_diff[index] = (
177                                 orig_dict_val[index],
178                                 resp_dict_val[index],
179                             )
180                     if orig_i > min_index:
181                         # original is longer as responded dict
182                         for index in range(min_index, orig_i, 1):
183                             sub_list_diff[index] = (orig_dict_val[index], None)
184                     elif resp_i > min_index:
185                         # responded dict is longer as original
186                         for index in range(min_index, resp_i, 1):
187                             sub_list_diff[index] = (None, resp_dict_val[index])
188                     if sub_list_diff:
189                         diff[key] = sub_list_diff
190
191                 else:
192                     diff[key] = (original_dict[key], responded_dict[key])
193
194         # Check all keys in responded_dict dict to find missing
195         for key in list(responded_dict.keys()):
196             if key not in original_dict:
197                 diff[key] = (KEY_NOT_FOUND, key)
198         return diff
199
200
201 IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON = [
202     "id",
203     "flow-name",
204     "barrier",
205     "cookie_mask",
206     "installHw",
207     "flags",
208     "strict",
209     "byte-count",
210     "duration",
211     "packet-count",
212     "in-port",
213     "vlan-id-present",
214     "out_group",
215     "out_port",
216     "hard-timeout",
217     "idle-timeout",
218     "flow-statistics",
219     "cookie",
220     "clear-actions",
221     "ipv4-source-address-no-mask",
222     "ipv4-source-arbitrary-bitmask",
223     "ipv4-destination-address-no-mask",
224     "ipv4-destination-arbitrary-bitmask",
225     "ipv6-source-address-no-mask",
226     "ipv6-source-arbitrary-bitmask",
227     "ipv6-destination-address-no-mask",
228     "ipv6-destination-arbitrary-bitmask",
229 ]  # noqa
230
231 IGNORED_PATHS_FOR_OC = [
232     (
233         [
234             "flow",
235             "instructions",
236             "instruction",
237             "apply-actions",
238             "action",
239             "controller-action",
240         ],
241         True,
242     ),  # noqa
243     (["flow", "instructions", "instruction", "clear-actions", "action"], False),
244     (
245         [
246             "flow",
247             "instructions",
248             "instruction",
249             "apply-actions",
250             "action",
251             "push-vlan-action",
252             "vlan-id",
253         ],
254         False,
255     ),  # noqa
256     (
257         [
258             "flow",
259             "instructions",
260             "instruction",
261             "apply-actions",
262             "action",
263             "drop-action",
264         ],
265         True,
266     ),
267     (
268         [
269             "flow",
270             "instructions",
271             "instruction",
272             "apply-actions",
273             "action",
274             "flood-action",
275         ],
276         True,
277     ),
278 ]
279
280 TAGS_TO_ADD_FOR_OC = [
281     (
282         [
283             "flow",
284             "instructions",
285             "instruction",
286             "apply-actions",
287             "action",
288             "output-action",
289         ],
290         "max-length",
291         "0",
292     )  # noqa
293 ]
294
295
296 TAGS_TO_MODIFY_FOR_OC = [
297     (["flow", "match", "metadata"], "metadata", "metadata-mask"),
298     (["flow", "match", "tunnel"], "tunnel-id", "tunnel-mask"),
299 ]
300
301
302 class XmlComparator:
303     def is_flow_configured(self, requested_flow, configured_flows):
304
305         orig_tree = md.parseString(requested_flow)
306         xml_resp_stream = configured_flows
307         xml_resp_tree = md.parseString(xml_resp_stream)
308         nodeListOperFlows = xml_resp_tree.getElementsByTagNameNS("*", "flow")
309         origDict = XMLtoDictParserTools.parseTreeToDict(
310             orig_tree._get_documentElement()
311         )
312
313         reportDict = {}
314         index = 0
315         for node in nodeListOperFlows:
316             nodeDict = XMLtoDictParserTools.parseTreeToDict(node)
317             XMLtoDictParserTools.addDictValue(reportDict, index, nodeDict)
318             index += 1
319             if nodeDict == origDict:
320                 return True, ""
321             if nodeDict["flow"]["priority"] == origDict["flow"]["priority"]:
322                 return (
323                     False,
324                     "Flow found with diferences {0}".format(
325                         XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict)
326                     ),
327                 )
328         return False, ""
329
330     def is_flow_operational2(self, requested_flow, oper_resp, check_id=False):
331         def _rem_unimplemented_tags(tagpath, recurs, tdict):
332             if len(tagpath) > 1 and tagpath[0] in tdict:
333                 _rem_unimplemented_tags(tagpath[1:], recurs, tdict[tagpath[0]])
334
335             # when not to delete anything
336             if len(tagpath) == 1 and tagpath[0] not in tdict:
337                 return
338             if len(tagpath) == 0:
339                 return
340
341             # when to delete
342             if len(tagpath) == 1 and tagpath[0] in tdict:
343                 del tdict[tagpath[0]]
344             if (
345                 len(tagpath) > 1
346                 and recurs is True
347                 and tagpath[0] in tdict
348                 and tdict[tagpath[0]] == {}
349             ):
350                 del tdict[tagpath[0]]
351             if list(tdict.keys()) == ["order"]:
352                 del tdict["order"]
353
354         def _add_tags(tagpath, newtag, value, tdict):
355             """if whole tagpath exists and the tag is not present, it is added with given value"""
356             if len(tagpath) > 0 and tagpath[0] in tdict:
357                 _add_tags(tagpath[1:], newtag, value, tdict[tagpath[0]])
358             elif len(tagpath) == 0 and newtag not in tdict:
359                 tdict[newtag] = value
360
361         def _to_be_modified_tags(tagpath, tag, related_tag, tdict):
362             """if whole tagpath exists and the tag is not present, it is added with given value"""
363             if len(tagpath) > 0 and tagpath[0] in tdict:
364                 _to_be_modified_tags(tagpath[1:], tag, related_tag, tdict[tagpath[0]])
365             elif len(tagpath) == 0 and tag in tdict and related_tag in tdict:
366                 tdict[tag] = str(int(tdict[tag]) & int(tdict[related_tag]))
367
368         IGNORED_TAGS_LIST = list(IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON)
369         if check_id:
370             IGNORED_TAGS_LIST.remove("id")
371         orig_tree = md.parseString(requested_flow)
372         xml_resp_stream = oper_resp
373         xml_resp_tree = md.parseString(xml_resp_stream)
374         nodeListOperFlows = xml_resp_tree.getElementsByTagNameNS("*", "flow")
375         origDict = XMLtoDictParserTools.parseTreeToDict(
376             orig_tree._get_documentElement(), ignoreList=IGNORED_TAGS_LIST
377         )
378
379         # origDict['flow-statistics'] = origDict.pop( 'flow' )
380         reportDict = {}
381         index = 0
382         for node in nodeListOperFlows:
383             nodeDict = XMLtoDictParserTools.parseTreeToDict(
384                 node, ignoreList=IGNORED_TAGS_LIST
385             )
386             XMLtoDictParserTools.addDictValue(reportDict, index, nodeDict)
387             index += 1
388             if nodeDict == origDict:
389                 return True, ""
390             if nodeDict["flow"]["priority"] == origDict["flow"]["priority"]:
391                 for p in IGNORED_PATHS_FOR_OC:
392                     td = copy.copy(origDict)
393                     _rem_unimplemented_tags(p[0], p[1], td)
394                     for (p, t, v) in TAGS_TO_ADD_FOR_OC:
395                         _add_tags(p, t, v, td)
396                     for (p, t, rt) in TAGS_TO_MODIFY_FOR_OC:
397                         _to_be_modified_tags(p, t, rt, td)
398
399                     if nodeDict == td:
400                         return True, ""
401                 if nodeDict == origDict:
402                     return True, ""
403                 return (
404                     False,
405                     "Flow found with diferences {0}".format(
406                         XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict)
407                     ),
408                 )
409         return False, ""
410
411     def get_data_for_flow_put_update(self, xml):
412         # action only for yet
413         xml_dom_input = md.parseString(xml)
414         actionList = xml_dom_input.getElementsByTagName("action")
415         if actionList is not None and len(actionList) > 0:
416             action = actionList[0]
417             for child in action.childNodes:
418                 if child.nodeType == Element.ELEMENT_NODE:
419                     nodeKey = child.localName
420                     if nodeKey != "order":
421                         if nodeKey != "drop-action":
422                             new_act = child.ownerDocument.createElement("drop-action")
423                         else:
424                             new_act = child.ownerDocument.createElement("output-action")
425                             onc = child.ownerDocument.createElement(
426                                 "output-node-connector"
427                             )
428                             onc_content = child.ownerDocument.createTextNode("TABLE")
429                             onc.appendChild(onc_content)
430                             new_act.appendChild(onc)
431                             ml = child.ownerDocument.createElement("max-length")
432                             ml_content = child.ownerDocument.createTextNode("60")
433                             ml.appendChild(ml_content)
434                             new_act.appendChild(ml)
435                         child.parentNode.replaceChild(new_act, child)
436         return xml_dom_input.toxml(encoding="utf-8")
437
438     def get_flow_content(self, tid=1, fid=1, priority=1):
439         """Returns an xml flow content identified by given details.
440
441         Args:
442             :param tid: table id
443             :param fid: flow id
444             :param priority: flow priority
445         """
446
447         flow_template = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
448 <flow xmlns="urn:opendaylight:flow:inventory">
449     <strict>false</strict>
450     <instructions>
451         <instruction>
452             <order>0</order>
453             <apply-actions>
454                 <action>
455                     <order>0</order>
456                     <drop-action/>
457                 </action>
458             </apply-actions>
459         </instruction>
460     </instructions>
461     <table_id>%s</table_id>
462     <id>%s</id>
463     <cookie_mask>4294967295</cookie_mask>
464     <installHw>false</installHw>
465     <match>
466         <ethernet-match>
467             <ethernet-type>
468                 <type>2048</type>
469             </ethernet-type>
470         </ethernet-match>
471         <ipv4-source>10.0.0.1/32</ipv4-source>
472     </match>
473     <cookie>%s</cookie>
474     <flow-name>%s</flow-name>
475     <priority>%s</priority>
476     <barrier>false</barrier>
477 </flow>"""
478
479         flow_data = flow_template % (
480             tid,
481             fid,
482             fid,
483             "TestFlow-{0}".format(fid),
484             priority,
485         )
486         return flow_data