suite to test statistic manager and operational data
[integration.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']:
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                         ]
199
200 class XmlComparator:
201
202     def is_flow_configured(self, requested_flow, configured_flows):
203
204         orig_tree = md.parseString(requested_flow)
205         xml_resp_stream = configured_flows.encode( 'utf-8', 'ignore' )
206         xml_resp_tree = md.parseString( xml_resp_stream  )
207         nodeListOperFlows = xml_resp_tree.getElementsByTagNameNS("*", 'flow' )
208         origDict = XMLtoDictParserTools.parseTreeToDict( orig_tree._get_documentElement() )
209
210         reportDict = {}
211         index = 0
212         for node in nodeListOperFlows :
213             nodeDict = XMLtoDictParserTools.parseTreeToDict( node)
214             XMLtoDictParserTools.addDictValue( reportDict, index, nodeDict )
215             index += 1
216             #print nodeDict
217             #print origDict
218             if nodeDict == origDict :
219                 return True, ''
220             if nodeDict['flow']['priority'] == origDict['flow']['priority']:
221                 return False, 'Flow found with diferences {0}'.format(XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict))
222         return False, ''
223
224     def is_flow_operational2(self, requested_flow, oper_resp):
225         def _rem_unimplemented_tags(tagpath, recurs, tdict):
226             #print "_rem_unimplemented_tags", tagpath, tdict
227             if len(tagpath) > 1 and tagpath[0] in tdict:
228                  _rem_unimplemented_tags(tagpath[1:], recurs,  tdict[tagpath[0]])
229
230             #when not to delete anything
231             if len(tagpath) == 1 and tagpath[0] not in tdict:
232                 return
233             if len(tagpath) == 0:
234                 return
235
236             #when to delete
237             if len(tagpath) == 1 and tagpath[0] in tdict:
238                 del tdict[tagpath[0]]
239             if len(tagpath) > 1 and recurs==True and tagpath[0] in tdict and tdict[tagpath[0]] == {}:
240                 del tdict[tagpath[0]]
241             if tdict.keys() == ['order']:
242                 del tdict['order']
243             #print "leaving", tdict
244
245         def _add_tags(tagpath, newtag, value, tdict):
246             '''if whole tagpath exists and the tag is not present, it is added with given value'''
247             #print "_add_tags", tagpath, newtag, value, tdict
248             if len(tagpath) > 0 and tagpath[0] in tdict:
249                 _add_tags(tagpath[1:], newtag, value, tdict[tagpath[0]])
250             elif len(tagpath) == 0 and  newtag not in tdict:
251                 tdict[newtag] = value
252
253         def _to_be_modified_tags(tagpath, tag, related_tag, tdict):
254             '''if whole tagpath exists and the tag is not present, it is added with given value'''
255             #print "_to_be_modified_tags", tagpath, tag, related_tag, tdict
256             if len(tagpath) > 0 and tagpath[0] in tdict:
257                 _to_be_modified_tags(tagpath[1:], tag, related_tag, tdict[tagpath[0]])
258             elif len(tagpath) == 0 and  tag in tdict and related_tag in tdict:
259                 tdict[tag] = str(long(tdict[tag]) & long(tdict[related_tag]))
260
261
262         orig_tree = md.parseString(requested_flow)
263         xml_resp_stream = oper_resp.encode( 'utf-8', 'ignore' )
264         xml_resp_tree = md.parseString( xml_resp_stream )
265         nodeListOperFlows = xml_resp_tree.getElementsByTagNameNS( "*", 'flow' )
266         origDict = XMLtoDictParserTools.parseTreeToDict( orig_tree._get_documentElement(),
267                                                   ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
268
269         #origDict['flow-statistics'] = origDict.pop( 'flow' )
270         reportDict = {}
271         index = 0
272         for node in nodeListOperFlows :
273             nodeDict = XMLtoDictParserTools.parseTreeToDict( node,
274                                                             ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
275             XMLtoDictParserTools.addDictValue( reportDict, index, nodeDict )
276             index += 1
277             #print nodeDict
278             #print origDict
279             #print reportDict
280             if nodeDict == origDict :
281                 return True, ''
282             if nodeDict['flow']['priority'] == origDict['flow']['priority']:
283                 for p in IGNORED_PATHS_FOR_OC:
284                     td = copy.copy(origDict)
285                     _rem_unimplemented_tags(p[0], p[1],  td)
286                     for (p,t,v) in TAGS_TO_ADD_FOR_OC:
287                         _add_tags( p, t, v, td)
288                     for (p,t, rt) in TAGS_TO_MODIFY_FOR_OC:
289                         _to_be_modified_tags( p, t, rt, td)
290
291                     #print "comparing1", nodeDict
292                     #print "comparing2", td
293                     if nodeDict == td:
294                         return True, ''
295                 if nodeDict == origDict:
296                     return True, ''
297                 return False, 'Flow found with diferences {0}'.format(XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict))
298         return False, ''
299