Merge "BUG 2067 : Utility to automate the deployment of a cluster"
[integration/test.git] / test / 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 re
16 import copy
17
18
19
20 KEY_NOT_FOUND = '<KEY_NOT_FOUND>'  # KeyNotFound for dictDiff
21
22 class XMLtoDictParserTools():
23
24
25     @staticmethod
26     def parseTreeToDict( node, returnedDict = None, ignoreList = [] ):
27         """
28         Return Dictionary representation of the xml Tree DOM Element.
29         Repeated tags are put to the array sorted by key (id or order)
30         otherwise is the value represented by tag key name.
31         @param node: DOM Element 
32         @param returnedDict : dictionary (default value None)
33         @param ignereList : list of ignored tags for the xml Tree DOM Element
34                             (default value is empty list)
35         @return: dict representation for the input DOM Element
36         """
37         returnedDict = {} if returnedDict is None else returnedDict
38         if ( node.nodeType == Element.ELEMENT_NODE ) :
39             nodeKey = ( node.localName ).encode( 'utf-8', 'ignore' )
40             if nodeKey not in ignoreList :
41                 if node.childNodes is not None :
42                     childDict = {}
43                     for child in node.childNodes :
44                         if child.nodeType == Element.TEXT_NODE :
45                             nodeValue = ( child.nodeValue ).encode( 'utf-8', 'ignore' )
46                             if ( len( nodeValue.strip( ' \t\n\r' ) ) ) > 0 :
47                                 XMLtoDictParserTools.addDictValue( returnedDict, nodeKey, nodeValue )
48                                 nodeKey = None
49                                 break
50                         elif child.nodeType == Element.ELEMENT_NODE :
51                             childDict = XMLtoDictParserTools.parseTreeToDict( child, childDict, ignoreList )
52
53                     XMLtoDictParserTools.addDictValue( returnedDict, nodeKey, childDict )
54
55         return returnedDict
56
57     @staticmethod
58     def addDictValue( m_dict, key, value ):
59
60         def _allign_address(value):
61             """unifies output"""
62             n = ipaddr.IPNetwork(value)
63             return '{0}/{1}'.format(n.network.exploded, n.prefixlen)
64
65         def _convert_numbers(value):
66             if value.startswith("0x"):
67                 return str(long(value,16))
68             return str(long(value))
69
70         if key is not None :
71             if ( isinstance( value, str ) ) :
72                 # we need to predict possible differences
73                 # for same value in upper or lower case
74                 value = value.lower()
75             if key not in m_dict :
76                 #lets add mask for ips withot mask
77                 if key in ['ipv4-destination', 'ipv4-source', 'ipv6-destination', 'ipv6-source', 'ipv6-nd-target']:
78                     nvalue = _allign_address(value)
79                     m_dict[key] = nvalue
80                 elif key in ['tunnel-mask','type','metadata-mask']:
81                     nvalue = _convert_numbers(value)
82                     m_dict[key] = nvalue
83                 else:
84                     m_dict[key] = value
85             else :
86                 exist_value = m_dict.get( key )
87                 if ( type( exist_value ) is dict ) :
88                     list_values = [exist_value, value]
89                     key_for_sort = XMLtoDictParserTools.searchKey( exist_value )
90                     if key_for_sort is not None :
91                         list_values = sorted( list_values, key = lambda k: k[key_for_sort] )
92                     m_dict[key] = list_values
93                 elif ( isinstance( exist_value, list ) ) :
94                     exist_value.append( value )
95                     list_values = exist_value
96                     key_for_sort = XMLtoDictParserTools.searchKey( value )
97                     if key_for_sort is not None :
98                         list_values = sorted( list_values, key = lambda k: k[key_for_sort] )
99                     m_dict[key] = list_values
100                 else :
101                     m_dict[key] += value
102
103
104     @staticmethod
105     def searchKey( dictionary ):
106         """
107         Return an order key for the array ordering. OF_13
108         allows only two possible kind of the order keys
109         'order' or '*-id'
110         @param dictionary: dictionary with data
111         @return: the array order key 
112         """
113         subKeyStr = ['-id', 'order']
114         for substr in subKeyStr :
115             for key in dictionary:
116                 if key == substr :
117                     return key
118                 elif key.endswith( substr ):
119                     return key
120         return None
121
122
123     @staticmethod
124     def getDifferenceDict( original_dict, responded_dict ):
125         """ 
126         Return a dict of keys that differ with another config object.  If a value is
127         not found in one fo the configs, it will be represented by KEY_NOT_FOUND.
128         @param original_dict:   Fist dictionary to diff.
129         @param responded_dict:  Second dictionary to diff.
130         @return diff:   Dict of Key => (original_dict.val, responded_dict.val)
131                         Dict of Key => (original_key, KEY_NOT_FOUND)
132                         Dict of Key => (KEY_NOT_FOUNE, original_key)
133         """
134         diff = {}
135         # Check all keys in original_dict dict
136         for key in original_dict.keys():
137             if ( not responded_dict.has_key( key ) ):
138                 # missing key in responded dict
139                 diff[key] = ( key, KEY_NOT_FOUND )
140             # check values of the dictionaries
141             elif ( original_dict[key] != responded_dict[key] ):
142                 # values are not the same #
143
144                 orig_dict_val = original_dict[key]
145                 resp_dict_val = responded_dict[key]
146
147                 # check value is instance of dictionary
148                 if isinstance( orig_dict_val, dict ) and isinstance( resp_dict_val, dict ):
149                     sub_dif = XMLtoDictParserTools.getDifferenceDict( orig_dict_val, resp_dict_val )
150                     if sub_dif :
151                         diff[key] = sub_dif
152
153                 # check value is instance of list
154                 # TODO - > change a basic comparator to compare by id or order
155                 elif isinstance( orig_dict_val, list ) and isinstance( resp_dict_val, list ) :
156                     sub_list_diff = {}
157                     # the list lengths
158                     orig_i, resp_i = len( orig_dict_val ), len( resp_dict_val )
159                     # define a max iteration length (less from both)
160                     min_index = orig_i if orig_i < resp_i else resp_i
161                     for index in range ( 0, min_index, 1 ) :
162                         if ( orig_dict_val[index] != resp_dict_val[index] ) :
163                             sub_list_diff[index] = ( orig_dict_val[index], resp_dict_val[index] )
164                     if ( orig_i > min_index ) :
165                         # original is longer as responded dict
166                         for index in range ( min_index, orig_i, 1 ):
167                             sub_list_diff[index] = ( orig_dict_val[index], None )
168                     elif ( resp_i > min_index ) :
169                         # responded dict is longer as original
170                         for index in range ( min_index, resp_i, 1 ) :
171                             sub_list_diff[index] = ( None, resp_dict_val[index] )
172                     if sub_list_diff :
173                         diff[key] = sub_list_diff
174
175                 else :
176                     diff[key] = ( original_dict[key], responded_dict[key] )
177
178         # Check all keys in responded_dict dict to find missing
179         for key in responded_dict.keys():
180             if ( not original_dict.has_key( key ) ):
181                 diff[key] = ( KEY_NOT_FOUND, key )
182         return diff
183
184 IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON = ['id', 'flow-name', 'barrier', 'cookie_mask', 'installHw', 'flags', 'strict', 'byte-count', 'duration', 'packet-count', 'in-port', 'vlan-id-present', 'out_group', 'out_port', 'hard-timeout', 'idle-timeout', 'flow-statistics', 'cookie']
185
186 IGNORED_PATHS_FOR_OC = [(['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'controller-action' ], True),
187                         (['flow', 'instructions', 'instruction', 'clear-actions', 'action'], False),
188                         (['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'push-vlan-action', 'vlan-id'], False),
189                         (['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'drop-action'], True),
190                         (['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'flood-action'], True),
191                        ]
192
193 TAGS_TO_ADD_FOR_OC = [(['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'output-action'], 'max-length' , '0'),
194                      ]
195
196
197 TAGS_TO_MODIFY_FOR_OC = [(['flow', 'match','metadata'], 'metadata', 'metadata-mask'),
198                          (['flow', 'match','tunnel'], 'tunnel-id', 'tunnel-mask'),
199                         ]
200
201 class XmlComparator:
202
203     def is_flow_configured(self, requested_flow, configured_flows):
204
205         orig_tree = md.parseString(requested_flow)
206         xml_resp_stream = configured_flows.encode( 'utf-8', 'ignore' )
207         xml_resp_tree = md.parseString( xml_resp_stream  )
208         nodeListOperFlows = xml_resp_tree.getElementsByTagNameNS("*", 'flow' )
209         origDict = XMLtoDictParserTools.parseTreeToDict( orig_tree._get_documentElement() )
210
211         reportDict = {}
212         index = 0
213         for node in nodeListOperFlows :
214             nodeDict = XMLtoDictParserTools.parseTreeToDict( node)
215             XMLtoDictParserTools.addDictValue( reportDict, index, nodeDict )
216             index += 1
217             #print nodeDict
218             #print origDict
219             if nodeDict == origDict :
220                 return True, ''
221             if nodeDict['flow']['priority'] == origDict['flow']['priority']:
222                 return False, 'Flow found with diferences {0}'.format(XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict))
223         return False, ''
224
225     def is_flow_operational2(self, requested_flow, oper_resp):
226         def _rem_unimplemented_tags(tagpath, recurs, tdict):
227             #print "_rem_unimplemented_tags", tagpath, tdict
228             if len(tagpath) > 1 and tagpath[0] in tdict:
229                  _rem_unimplemented_tags(tagpath[1:], recurs,  tdict[tagpath[0]])
230
231             #when not to delete anything
232             if len(tagpath) == 1 and tagpath[0] not in tdict:
233                 return
234             if len(tagpath) == 0:
235                 return
236
237             #when to delete
238             if len(tagpath) == 1 and tagpath[0] in tdict:
239                 del tdict[tagpath[0]]
240             if len(tagpath) > 1 and recurs==True and tagpath[0] in tdict and tdict[tagpath[0]] == {}:
241                 del tdict[tagpath[0]]
242             if tdict.keys() == ['order']:
243                 del tdict['order']
244             #print "leaving", tdict
245
246         def _add_tags(tagpath, newtag, value, tdict):
247             '''if whole tagpath exists and the tag is not present, it is added with given value'''
248             #print "_add_tags", tagpath, newtag, value, tdict
249             if len(tagpath) > 0 and tagpath[0] in tdict:
250                 _add_tags(tagpath[1:], newtag, value, tdict[tagpath[0]])
251             elif len(tagpath) == 0 and  newtag not in tdict:
252                 tdict[newtag] = value
253
254         def _to_be_modified_tags(tagpath, tag, related_tag, tdict):
255             '''if whole tagpath exists and the tag is not present, it is added with given value'''
256             #print "_to_be_modified_tags", tagpath, tag, related_tag, tdict
257             if len(tagpath) > 0 and tagpath[0] in tdict:
258                 _to_be_modified_tags(tagpath[1:], tag, related_tag, tdict[tagpath[0]])
259             elif len(tagpath) == 0 and  tag in tdict and related_tag in tdict:
260                 tdict[tag] = str(long(tdict[tag]) & long(tdict[related_tag]))
261
262
263         orig_tree = md.parseString(requested_flow)
264         xml_resp_stream = oper_resp.encode( 'utf-8', 'ignore' )
265         xml_resp_tree = md.parseString( xml_resp_stream )
266         nodeListOperFlows = xml_resp_tree.getElementsByTagNameNS( "*", 'flow' )
267         origDict = XMLtoDictParserTools.parseTreeToDict( orig_tree._get_documentElement(),
268                                                   ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
269
270         #origDict['flow-statistics'] = origDict.pop( 'flow' )
271         reportDict = {}
272         index = 0
273         for node in nodeListOperFlows :
274             nodeDict = XMLtoDictParserTools.parseTreeToDict( node,
275                                                             ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
276             XMLtoDictParserTools.addDictValue( reportDict, index, nodeDict )
277             index += 1
278             #print nodeDict
279             #print origDict
280             #print reportDict
281             if nodeDict == origDict :
282                 return True, ''
283             if nodeDict['flow']['priority'] == origDict['flow']['priority']:
284                 for p in IGNORED_PATHS_FOR_OC:
285                     td = copy.copy(origDict)
286                     _rem_unimplemented_tags(p[0], p[1],  td)
287                     for (p,t,v) in TAGS_TO_ADD_FOR_OC:
288                         _add_tags( p, t, v, td)
289                     for (p,t, rt) in TAGS_TO_MODIFY_FOR_OC:
290                         _to_be_modified_tags( p, t, rt, td)
291
292                     #print "comparing1", nodeDict
293                     #print "comparing2", td
294                     if nodeDict == td:
295                         return True, ''
296                 if nodeDict == origDict:
297                     return True, ''
298                 return False, 'Flow found with diferences {0}'.format(XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict))
299         return False, ''
300
301     def get_data_for_flow_put_update(self, xml):
302         # action only for yet
303         xml_dom_input = md.parseString( xml )
304         actionList = xml_dom_input.getElementsByTagName( 'action' )
305         if actionList is not None and len( actionList ) > 0 :
306             action = actionList[0]
307             for child in action.childNodes :
308                 if child.nodeType == Element.ELEMENT_NODE:
309                     nodeKey = ( child.localName ).encode( 'utf-8', 'ignore' )
310                     if nodeKey != 'order' :
311                         if nodeKey != 'drop-action' :
312                             new_act = child.ownerDocument.createElement( 'drop-action' )
313                         else :
314                             new_act = child.ownerDocument.createElement( 'dec-mpls-ttl' )
315                         child.parentNode.replaceChild( new_act, child )
316         return xml_dom_input.toxml( encoding = 'utf-8' )
317