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
17 KEY_NOT_FOUND = '<KEY_NOT_FOUND>' # KeyNotFound for dictDiff
20 class XMLtoDictParserTools():
23 def parseTreeToDict(node, returnedDict=None, ignoreList=[]):
25 Return Dictionary representation of the xml Tree DOM Element.
26 Repeated tags are put to the array sorted by key (id or order)
27 otherwise is the value represented by tag key name.
28 @param node: DOM Element
29 @param returnedDict : dictionary (default value None)
30 @param ignereList : list of ignored tags for the xml Tree DOM Element
31 (default value is empty list)
32 @return: dict representation for the input DOM Element
34 returnedDict = {} if returnedDict is None else returnedDict
35 if (node.nodeType == Element.ELEMENT_NODE):
36 nodeKey = (node.localName).encode('utf-8', 'ignore')
37 if nodeKey not in ignoreList:
38 if node.childNodes is not None:
40 for child in node.childNodes:
41 if child.nodeType == Element.TEXT_NODE:
42 nodeValue = (child.nodeValue).encode('utf-8', 'ignore')
43 if (len(nodeValue.strip(' \t\n\r'))) > 0:
44 XMLtoDictParserTools.addDictValue(returnedDict, nodeKey, nodeValue)
47 elif child.nodeType == Element.ELEMENT_NODE:
48 childDict = XMLtoDictParserTools.parseTreeToDict(child, childDict, ignoreList)
50 XMLtoDictParserTools.addDictValue(returnedDict, nodeKey, childDict)
55 def addDictValue(m_dict, key, value):
57 def _allign_address(value):
59 n = ipaddr.IPNetwork(value)
60 return '{0}/{1}'.format(n.network.exploded, n.prefixlen)
62 def _convert_numbers(value):
63 if value.startswith("0x"):
64 return str(long(value, 16))
65 return str(long(value))
68 if (isinstance(value, str)):
69 # we need to predict possible differences
70 # for same value in upper or lower case
73 # lets add mask for ips withot mask
74 if key in ['ipv4-destination', 'ipv4-source', 'ipv6-destination', 'ipv6-source', 'ipv6-nd-target']:
75 nvalue = _allign_address(value)
77 elif key in ['tunnel-mask', 'type', 'metadata-mask', 'out_port', 'out_group']:
78 nvalue = _convert_numbers(value)
83 exist_value = m_dict.get(key)
84 if (type(exist_value) is dict):
85 list_values = [exist_value, value]
86 key_for_sort = XMLtoDictParserTools.searchKey(exist_value)
87 if key_for_sort is not None:
88 list_values = sorted(list_values, key=lambda k: k[key_for_sort])
89 m_dict[key] = list_values
90 elif (isinstance(exist_value, list)):
91 exist_value.append(value)
92 list_values = exist_value
93 key_for_sort = XMLtoDictParserTools.searchKey(value)
94 if key_for_sort is not None:
95 list_values = sorted(list_values, key=lambda k: k[key_for_sort])
96 m_dict[key] = list_values
101 def searchKey(dictionary):
103 Return an order key for the array ordering. OF_13
104 allows only two possible kind of the order keys
106 @param dictionary: dictionary with data
107 @return: the array order key
109 subKeyStr = ['-id', 'order']
110 for substr in subKeyStr:
111 for key in dictionary:
114 elif key.endswith(substr):
119 def getDifferenceDict(original_dict, responded_dict):
121 Return a dict of keys that differ with another config object. If a value is
122 not found in one fo the configs, it will be represented by KEY_NOT_FOUND.
123 @param original_dict: Fist dictionary to diff.
124 @param responded_dict: Second dictionary to diff.
125 @return diff: Dict of Key => (original_dict.val, responded_dict.val)
126 Dict of Key => (original_key, KEY_NOT_FOUND)
127 Dict of Key => (KEY_NOT_FOUNE, original_key)
130 # Check all keys in original_dict dict
131 for key in original_dict.keys():
132 if key not in responded_dict:
133 # missing key in responded dict
134 diff[key] = (key, KEY_NOT_FOUND)
135 # check values of the dictionaries
136 elif (original_dict[key] != responded_dict[key]):
137 # values are not the same #
139 orig_dict_val = original_dict[key]
140 resp_dict_val = responded_dict[key]
142 # check value is instance of dictionary
143 if isinstance(orig_dict_val, dict) and isinstance(resp_dict_val, dict):
144 sub_dif = XMLtoDictParserTools.getDifferenceDict(orig_dict_val, resp_dict_val)
148 # check value is instance of list
149 # TODO - > change a basic comparator to compare by id or order
150 elif isinstance(orig_dict_val, list) and isinstance(resp_dict_val, list):
153 orig_i, resp_i = len(orig_dict_val), len(resp_dict_val)
154 # define a max iteration length (less from both)
155 min_index = orig_i if orig_i < resp_i else resp_i
156 for index in range(0, min_index, 1):
157 if (orig_dict_val[index] != resp_dict_val[index]):
158 sub_list_diff[index] = (orig_dict_val[index], resp_dict_val[index])
159 if (orig_i > min_index):
160 # original is longer as responded dict
161 for index in range(min_index, orig_i, 1):
162 sub_list_diff[index] = (orig_dict_val[index], None)
163 elif (resp_i > min_index):
164 # responded dict is longer as original
165 for index in range(min_index, resp_i, 1):
166 sub_list_diff[index] = (None, resp_dict_val[index])
168 diff[key] = sub_list_diff
171 diff[key] = (original_dict[key], responded_dict[key])
173 # Check all keys in responded_dict dict to find missing
174 for key in responded_dict.keys():
175 if key not in original_dict:
176 diff[key] = (KEY_NOT_FOUND, key)
180 IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON = ['id', 'flow-name', 'barrier', 'cookie_mask', 'installHw', 'flags',
181 'strict', 'byte-count', 'duration', 'packet-count', 'in-port',
182 'vlan-id-present', 'out_group', 'out_port', 'hard-timeout', 'idle-timeout',
183 'flow-statistics', 'cookie', 'clear-actions',
184 'ipv4-source-address-no-mask', 'ipv4-source-arbitrary-bitmask',
185 'ipv4-destination-address-no-mask', 'ipv4-destination-arbitrary-bitmask',
186 'ipv6-source-address-no-mask', 'ipv6-source-arbitrary-bitmask',
187 'ipv6-destination-address-no-mask', 'ipv6-destination-arbitrary-bitmask'] # noqa
189 IGNORED_PATHS_FOR_OC = [(['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'controller-action'], True), # noqa
190 (['flow', 'instructions', 'instruction', 'clear-actions', 'action'], False),
191 (['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'push-vlan-action', 'vlan-id'], False), # noqa
192 (['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'drop-action'], True),
193 (['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'flood-action'], True),
196 TAGS_TO_ADD_FOR_OC = [(['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'output-action'], 'max-length', '0'), # noqa
200 TAGS_TO_MODIFY_FOR_OC = [(['flow', 'match', 'metadata'], 'metadata', 'metadata-mask'),
201 (['flow', 'match', 'tunnel'], 'tunnel-id', 'tunnel-mask'),
207 def is_flow_configured(self, requested_flow, configured_flows):
209 orig_tree = md.parseString(requested_flow)
210 xml_resp_stream = configured_flows.encode('utf-8', 'ignore')
211 xml_resp_tree = md.parseString(xml_resp_stream)
212 nodeListOperFlows = xml_resp_tree.getElementsByTagNameNS("*", 'flow')
213 origDict = XMLtoDictParserTools.parseTreeToDict(orig_tree._get_documentElement())
217 for node in nodeListOperFlows:
218 nodeDict = XMLtoDictParserTools.parseTreeToDict(node)
219 XMLtoDictParserTools.addDictValue(reportDict, index, nodeDict)
223 if nodeDict == origDict:
225 if nodeDict['flow']['priority'] == origDict['flow']['priority']:
226 return False, 'Flow found with diferences {0}'.format(
227 XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict))
230 def is_flow_operational2(self, requested_flow, oper_resp, check_id=False):
231 def _rem_unimplemented_tags(tagpath, recurs, tdict):
232 # print "_rem_unimplemented_tags", tagpath, tdict
233 if len(tagpath) > 1 and tagpath[0] in tdict:
234 _rem_unimplemented_tags(tagpath[1:], recurs, tdict[tagpath[0]])
236 # when not to delete anything
237 if len(tagpath) == 1 and tagpath[0] not in tdict:
239 if len(tagpath) == 0:
243 if len(tagpath) == 1 and tagpath[0] in tdict:
244 del tdict[tagpath[0]]
245 if len(tagpath) > 1 and recurs is True and tagpath[0] in tdict and tdict[tagpath[0]] == {}:
246 del tdict[tagpath[0]]
247 if tdict.keys() == ['order']:
249 # print "leaving", tdict
251 def _add_tags(tagpath, newtag, value, tdict):
252 '''if whole tagpath exists and the tag is not present, it is added with given value'''
253 # print "_add_tags", tagpath, newtag, value, tdict
254 if len(tagpath) > 0 and tagpath[0] in tdict:
255 _add_tags(tagpath[1:], newtag, value, tdict[tagpath[0]])
256 elif len(tagpath) == 0 and newtag not in tdict:
257 tdict[newtag] = value
259 def _to_be_modified_tags(tagpath, tag, related_tag, tdict):
260 '''if whole tagpath exists and the tag is not present, it is added with given value'''
261 # print "_to_be_modified_tags", tagpath, tag, related_tag, tdict
262 if len(tagpath) > 0 and tagpath[0] in tdict:
263 _to_be_modified_tags(tagpath[1:], tag, related_tag, tdict[tagpath[0]])
264 elif len(tagpath) == 0 and tag in tdict and related_tag in tdict:
265 tdict[tag] = str(long(tdict[tag]) & long(tdict[related_tag]))
267 IGNORED_TAGS_LIST = list(IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON)
269 IGNORED_TAGS_LIST.remove('id')
270 orig_tree = md.parseString(requested_flow)
271 xml_resp_stream = oper_resp.encode('utf-8', 'ignore')
272 xml_resp_tree = md.parseString(xml_resp_stream)
273 nodeListOperFlows = xml_resp_tree.getElementsByTagNameNS("*", 'flow')
274 origDict = XMLtoDictParserTools.parseTreeToDict(
275 orig_tree._get_documentElement(),
276 ignoreList=IGNORED_TAGS_LIST)
278 # origDict['flow-statistics'] = origDict.pop( 'flow' )
281 for node in nodeListOperFlows:
282 nodeDict = XMLtoDictParserTools.parseTreeToDict(
284 ignoreList=IGNORED_TAGS_LIST)
285 XMLtoDictParserTools.addDictValue(reportDict, index, nodeDict)
290 if nodeDict == origDict:
292 if nodeDict['flow']['priority'] == origDict['flow']['priority']:
293 for p in IGNORED_PATHS_FOR_OC:
294 td = copy.copy(origDict)
295 _rem_unimplemented_tags(p[0], p[1], td)
296 for (p, t, v) in TAGS_TO_ADD_FOR_OC:
297 _add_tags(p, t, v, td)
298 for (p, t, rt) in TAGS_TO_MODIFY_FOR_OC:
299 _to_be_modified_tags(p, t, rt, td)
301 # print "comparing1", nodeDict
302 # print "comparing2", td
305 if nodeDict == origDict:
307 return False, 'Flow found with diferences {0}'.format(
308 XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict))
311 def get_data_for_flow_put_update(self, xml):
312 # action only for yet
313 xml_dom_input = md.parseString(xml)
314 actionList = xml_dom_input.getElementsByTagName('action')
315 if actionList is not None and len(actionList) > 0:
316 action = actionList[0]
317 for child in action.childNodes:
318 if child.nodeType == Element.ELEMENT_NODE:
319 nodeKey = (child.localName).encode('utf-8', 'ignore')
320 if nodeKey != 'order':
321 if nodeKey != 'drop-action':
322 new_act = child.ownerDocument.createElement('drop-action')
324 new_act = child.ownerDocument.createElement('output-action')
325 onc = child.ownerDocument.createElement('output-node-connector')
326 onc_content = child.ownerDocument.createTextNode('TABLE')
327 onc.appendChild(onc_content)
328 new_act.appendChild(onc)
329 ml = child.ownerDocument.createElement('max-length')
330 ml_content = child.ownerDocument.createTextNode('60')
331 ml.appendChild(ml_content)
332 new_act.appendChild(ml)
333 child.parentNode.replaceChild(new_act, child)
334 return xml_dom_input.toxml(encoding='utf-8')
336 def get_flow_content(self, tid=1, fid=1, priority=1):
337 """Returns an xml flow content identified by given details.
342 :param priority: flow priority
345 flow_template = '''<?xml version="1.0" encoding="UTF-8" standalone="no"?>
346 <flow xmlns="urn:opendaylight:flow:inventory">
347 <strict>false</strict>
359 <table_id>%s</table_id>
361 <cookie_mask>4294967295</cookie_mask>
362 <installHw>false</installHw>
369 <ipv4-source>10.0.0.1/32</ipv4-source>
372 <flow-name>%s</flow-name>
373 <priority>%s</priority>
374 <barrier>false</barrier>
377 flow_data = flow_template % (tid, fid, fid, 'TestFlow-{0}'.format(fid), priority)