2 Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 Created on May 21, 2014
10 @author: <a href="mailto:vdemcak@cisco.com">Vaclav Demcak</a>
12 from xml.dom.minidom import Element
14 import xml.dom.minidom as md
20 KEY_NOT_FOUND = '<KEY_NOT_FOUND>' # KeyNotFound for dictDiff
22 class XMLtoDictParserTools():
26 def parseTreeToDict( node, returnedDict = None, ignoreList = [] ):
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
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 :
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 )
50 elif child.nodeType == Element.ELEMENT_NODE :
51 childDict = XMLtoDictParserTools.parseTreeToDict( child, childDict, ignoreList )
53 XMLtoDictParserTools.addDictValue( returnedDict, nodeKey, childDict )
58 def addDictValue( m_dict, key, value ):
60 def _allign_address(value):
62 n = ipaddr.IPNetwork(value)
63 return '{0}/{1}'.format(n.network.exploded, n.prefixlen)
65 def _convert_numbers(value):
66 if value.startswith("0x"):
67 return str(long(value,16))
68 return str(long(value))
71 if ( isinstance( value, str ) ) :
72 # we need to predict possible differences
73 # for same value in upper or lower case
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)
80 elif key in ['tunnel-mask','type','metadata-mask','out_port','out_group']:
81 nvalue = _convert_numbers(value)
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
105 def searchKey( dictionary ):
107 Return an order key for the array ordering. OF_13
108 allows only two possible kind of the order keys
110 @param dictionary: dictionary with data
111 @return: the array order key
113 subKeyStr = ['-id', 'order']
114 for substr in subKeyStr :
115 for key in dictionary:
118 elif key.endswith( substr ):
124 def getDifferenceDict( original_dict, responded_dict ):
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)
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 #
144 orig_dict_val = original_dict[key]
145 resp_dict_val = responded_dict[key]
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 )
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 ) :
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] )
173 diff[key] = sub_list_diff
176 diff[key] = ( original_dict[key], responded_dict[key] )
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 )
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']
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),
193 TAGS_TO_ADD_FOR_OC = [(['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'output-action'], 'max-length' , '0'),
197 TAGS_TO_MODIFY_FOR_OC = [(['flow', 'match','metadata'], 'metadata', 'metadata-mask'),
198 (['flow', 'match','tunnel'], 'tunnel-id', 'tunnel-mask'),
203 def is_flow_configured(self, requested_flow, configured_flows):
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() )
213 for node in nodeListOperFlows :
214 nodeDict = XMLtoDictParserTools.parseTreeToDict( node)
215 XMLtoDictParserTools.addDictValue( reportDict, index, nodeDict )
219 if nodeDict == origDict :
221 if nodeDict['flow']['priority'] == origDict['flow']['priority']:
222 return False, 'Flow found with diferences {0}'.format(XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict))
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]])
231 #when not to delete anything
232 if len(tagpath) == 1 and tagpath[0] not in tdict:
234 if len(tagpath) == 0:
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']:
244 #print "leaving", tdict
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
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]))
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 )
270 #origDict['flow-statistics'] = origDict.pop( 'flow' )
273 for node in nodeListOperFlows :
274 nodeDict = XMLtoDictParserTools.parseTreeToDict( node,
275 ignoreList = IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON )
276 XMLtoDictParserTools.addDictValue( reportDict, index, nodeDict )
281 if nodeDict == origDict :
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)
292 #print "comparing1", nodeDict
293 #print "comparing2", td
296 if nodeDict == origDict:
298 return False, 'Flow found with diferences {0}'.format(XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict))
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' )
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' )