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:
22 def parseTreeToDict(node, returnedDict=None, ignoreList=[]):
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
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:
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
48 elif child.nodeType == Element.ELEMENT_NODE:
49 childDict = XMLtoDictParserTools.parseTreeToDict(
50 child, childDict, ignoreList
53 XMLtoDictParserTools.addDictValue(returnedDict, nodeKey, childDict)
58 def addDictValue(m_dict, key, value):
59 def _allign_address(value):
61 n = ipaddr.IPNetwork(value)
62 return "{0}/{1}".format(n.network.exploded, n.prefixlen)
64 def _convert_numbers(value):
65 if value.startswith("0x"):
66 return str(int(value, 16))
67 return str(int(value))
70 if isinstance(value, str):
71 # we need to predict possible differences
72 # for same value in upper or lower case
75 # lets add mask for ips withot mask
83 nvalue = _allign_address(value)
92 nvalue = _convert_numbers(value)
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
115 def searchKey(dictionary):
117 Return an order key for the array ordering. OF_13
118 allows only two possible kind of the order keys
120 @param dictionary: dictionary with data
121 @return: the array order key
123 subKeyStr = ["-id", "order"]
124 for substr in subKeyStr:
125 for key in dictionary:
128 elif key.endswith(substr):
133 def getDifferenceDict(original_dict, responded_dict):
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)
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 #
153 orig_dict_val = original_dict[key]
154 resp_dict_val = responded_dict[key]
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
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(
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],
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])
189 diff[key] = sub_list_diff
192 diff[key] = (original_dict[key], responded_dict[key])
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)
201 IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON = [
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",
231 IGNORED_PATHS_FOR_OC = [
243 (["flow", "instructions", "instruction", "clear-actions", "action"], False),
280 TAGS_TO_ADD_FOR_OC = [
296 TAGS_TO_MODIFY_FOR_OC = [
297 (["flow", "match", "metadata"], "metadata", "metadata-mask"),
298 (["flow", "match", "tunnel"], "tunnel-id", "tunnel-mask"),
303 def is_flow_configured(self, requested_flow, configured_flows):
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()
315 for node in nodeListOperFlows:
316 nodeDict = XMLtoDictParserTools.parseTreeToDict(node)
317 XMLtoDictParserTools.addDictValue(reportDict, index, nodeDict)
319 if nodeDict == origDict:
321 if nodeDict["flow"]["priority"] == origDict["flow"]["priority"]:
324 "Flow found with diferences {0}".format(
325 XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict)
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]])
335 # when not to delete anything
336 if len(tagpath) == 1 and tagpath[0] not in tdict:
338 if len(tagpath) == 0:
342 if len(tagpath) == 1 and tagpath[0] in tdict:
343 del tdict[tagpath[0]]
347 and tagpath[0] in tdict
348 and tdict[tagpath[0]] == {}
350 del tdict[tagpath[0]]
351 if list(tdict.keys()) == ["order"]:
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
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]))
368 IGNORED_TAGS_LIST = list(IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON)
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
379 # origDict['flow-statistics'] = origDict.pop( 'flow' )
382 for node in nodeListOperFlows:
383 nodeDict = XMLtoDictParserTools.parseTreeToDict(
384 node, ignoreList=IGNORED_TAGS_LIST
386 XMLtoDictParserTools.addDictValue(reportDict, index, nodeDict)
388 if nodeDict == origDict:
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)
401 if nodeDict == origDict:
405 "Flow found with diferences {0}".format(
406 XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict)
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")
424 new_act = child.ownerDocument.createElement("output-action")
425 onc = child.ownerDocument.createElement(
426 "output-node-connector"
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")
438 def get_flow_content(self, tid=1, fid=1, priority=1):
439 """Returns an xml flow content identified by given details.
444 :param priority: flow priority
447 flow_template = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
448 <flow xmlns="urn:opendaylight:flow:inventory">
449 <strict>false</strict>
461 <table_id>%s</table_id>
463 <cookie_mask>4294967295</cookie_mask>
464 <installHw>false</installHw>
471 <ipv4-source>10.0.0.1/32</ipv4-source>
474 <flow-name>%s</flow-name>
475 <priority>%s</priority>
476 <barrier>false</barrier>
479 flow_data = flow_template % (
483 "TestFlow-{0}".format(fid),