'''
Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
This program and the accompanying materials are made available under the
terms of the Eclipse Public License v1.0 which accompanies this distribution,
and is available at http://www.eclipse.org/legal/epl-v10.html
Created on May 21, 2014
@author: Vaclav Demcak
'''
from xml.dom.minidom import Element
import ipaddr
import xml.dom.minidom as md
import copy
KEY_NOT_FOUND = '' # KeyNotFound for dictDiff
class XMLtoDictParserTools():
@staticmethod
def parseTreeToDict(node, returnedDict=None, ignoreList=[]):
"""
Return Dictionary representation of the xml Tree DOM Element.
Repeated tags are put to the array sorted by key (id or order)
otherwise is the value represented by tag key name.
@param node: DOM Element
@param returnedDict : dictionary (default value None)
@param ignereList : list of ignored tags for the xml Tree DOM Element
(default value is empty list)
@return: dict representation for the input DOM Element
"""
returnedDict = {} if returnedDict is None else returnedDict
if (node.nodeType == Element.ELEMENT_NODE):
nodeKey = (node.localName).encode('utf-8', 'ignore')
if nodeKey not in ignoreList:
if node.childNodes is not None:
childDict = {}
for child in node.childNodes:
if child.nodeType == Element.TEXT_NODE:
nodeValue = (child.nodeValue).encode('utf-8', 'ignore')
if (len(nodeValue.strip(' \t\n\r'))) > 0:
XMLtoDictParserTools.addDictValue(returnedDict, nodeKey, nodeValue)
nodeKey = None
break
elif child.nodeType == Element.ELEMENT_NODE:
childDict = XMLtoDictParserTools.parseTreeToDict(child, childDict, ignoreList)
XMLtoDictParserTools.addDictValue(returnedDict, nodeKey, childDict)
return returnedDict
@staticmethod
def addDictValue(m_dict, key, value):
def _allign_address(value):
"""unifies output"""
n = ipaddr.IPNetwork(value)
return '{0}/{1}'.format(n.network.exploded, n.prefixlen)
def _convert_numbers(value):
if value.startswith("0x"):
return str(long(value, 16))
return str(long(value))
if key is not None:
if (isinstance(value, str)):
# we need to predict possible differences
# for same value in upper or lower case
value = value.lower()
if key not in m_dict:
# lets add mask for ips withot mask
if key in ['ipv4-destination', 'ipv4-source', 'ipv6-destination', 'ipv6-source', 'ipv6-nd-target']:
nvalue = _allign_address(value)
m_dict[key] = nvalue
elif key in ['tunnel-mask', 'type', 'metadata-mask', 'out_port', 'out_group']:
nvalue = _convert_numbers(value)
m_dict[key] = nvalue
else:
m_dict[key] = value
else:
exist_value = m_dict.get(key)
if (type(exist_value) is dict):
list_values = [exist_value, value]
key_for_sort = XMLtoDictParserTools.searchKey(exist_value)
if key_for_sort is not None:
list_values = sorted(list_values, key=lambda k: k[key_for_sort])
m_dict[key] = list_values
elif (isinstance(exist_value, list)):
exist_value.append(value)
list_values = exist_value
key_for_sort = XMLtoDictParserTools.searchKey(value)
if key_for_sort is not None:
list_values = sorted(list_values, key=lambda k: k[key_for_sort])
m_dict[key] = list_values
else:
m_dict[key] += value
@staticmethod
def searchKey(dictionary):
"""
Return an order key for the array ordering. OF_13
allows only two possible kind of the order keys
'order' or '*-id'
@param dictionary: dictionary with data
@return: the array order key
"""
subKeyStr = ['-id', 'order']
for substr in subKeyStr:
for key in dictionary:
if key == substr:
return key
elif key.endswith(substr):
return key
return None
@staticmethod
def getDifferenceDict(original_dict, responded_dict):
"""
Return a dict of keys that differ with another config object. If a value is
not found in one fo the configs, it will be represented by KEY_NOT_FOUND.
@param original_dict: Fist dictionary to diff.
@param responded_dict: Second dictionary to diff.
@return diff: Dict of Key => (original_dict.val, responded_dict.val)
Dict of Key => (original_key, KEY_NOT_FOUND)
Dict of Key => (KEY_NOT_FOUNE, original_key)
"""
diff = {}
# Check all keys in original_dict dict
for key in original_dict.keys():
if key not in responded_dict:
# missing key in responded dict
diff[key] = (key, KEY_NOT_FOUND)
# check values of the dictionaries
elif (original_dict[key] != responded_dict[key]):
# values are not the same #
orig_dict_val = original_dict[key]
resp_dict_val = responded_dict[key]
# check value is instance of dictionary
if isinstance(orig_dict_val, dict) and isinstance(resp_dict_val, dict):
sub_dif = XMLtoDictParserTools.getDifferenceDict(orig_dict_val, resp_dict_val)
if sub_dif:
diff[key] = sub_dif
# check value is instance of list
# TODO - > change a basic comparator to compare by id or order
elif isinstance(orig_dict_val, list) and isinstance(resp_dict_val, list):
sub_list_diff = {}
# the list lengths
orig_i, resp_i = len(orig_dict_val), len(resp_dict_val)
# define a max iteration length (less from both)
min_index = orig_i if orig_i < resp_i else resp_i
for index in range(0, min_index, 1):
if (orig_dict_val[index] != resp_dict_val[index]):
sub_list_diff[index] = (orig_dict_val[index], resp_dict_val[index])
if (orig_i > min_index):
# original is longer as responded dict
for index in range(min_index, orig_i, 1):
sub_list_diff[index] = (orig_dict_val[index], None)
elif (resp_i > min_index):
# responded dict is longer as original
for index in range(min_index, resp_i, 1):
sub_list_diff[index] = (None, resp_dict_val[index])
if sub_list_diff:
diff[key] = sub_list_diff
else:
diff[key] = (original_dict[key], responded_dict[key])
# Check all keys in responded_dict dict to find missing
for key in responded_dict.keys():
if key not in original_dict:
diff[key] = (KEY_NOT_FOUND, key)
return diff
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', 'clear-actions'] # noqa
IGNORED_PATHS_FOR_OC = [(['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'controller-action'], True), # noqa
(['flow', 'instructions', 'instruction', 'clear-actions', 'action'], False),
(['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'push-vlan-action', 'vlan-id'], False), # noqa
(['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'drop-action'], True),
(['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'flood-action'], True),
]
TAGS_TO_ADD_FOR_OC = [(['flow', 'instructions', 'instruction', 'apply-actions', 'action', 'output-action'], 'max-length', '0'), # noqa
]
TAGS_TO_MODIFY_FOR_OC = [(['flow', 'match', 'metadata'], 'metadata', 'metadata-mask'),
(['flow', 'match', 'tunnel'], 'tunnel-id', 'tunnel-mask'),
]
class XmlComparator:
def is_flow_configured(self, requested_flow, configured_flows):
orig_tree = md.parseString(requested_flow)
xml_resp_stream = configured_flows.encode('utf-8', 'ignore')
xml_resp_tree = md.parseString(xml_resp_stream)
nodeListOperFlows = xml_resp_tree.getElementsByTagNameNS("*", 'flow')
origDict = XMLtoDictParserTools.parseTreeToDict(orig_tree._get_documentElement())
reportDict = {}
index = 0
for node in nodeListOperFlows:
nodeDict = XMLtoDictParserTools.parseTreeToDict(node)
XMLtoDictParserTools.addDictValue(reportDict, index, nodeDict)
index += 1
# print nodeDict
# print origDict
if nodeDict == origDict:
return True, ''
if nodeDict['flow']['priority'] == origDict['flow']['priority']:
return False, 'Flow found with diferences {0}'.format(
XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict))
return False, ''
def is_flow_operational2(self, requested_flow, oper_resp):
def _rem_unimplemented_tags(tagpath, recurs, tdict):
# print "_rem_unimplemented_tags", tagpath, tdict
if len(tagpath) > 1 and tagpath[0] in tdict:
_rem_unimplemented_tags(tagpath[1:], recurs, tdict[tagpath[0]])
# when not to delete anything
if len(tagpath) == 1 and tagpath[0] not in tdict:
return
if len(tagpath) == 0:
return
# when to delete
if len(tagpath) == 1 and tagpath[0] in tdict:
del tdict[tagpath[0]]
if len(tagpath) > 1 and recurs is True and tagpath[0] in tdict and tdict[tagpath[0]] == {}:
del tdict[tagpath[0]]
if tdict.keys() == ['order']:
del tdict['order']
# print "leaving", tdict
def _add_tags(tagpath, newtag, value, tdict):
'''if whole tagpath exists and the tag is not present, it is added with given value'''
# print "_add_tags", tagpath, newtag, value, tdict
if len(tagpath) > 0 and tagpath[0] in tdict:
_add_tags(tagpath[1:], newtag, value, tdict[tagpath[0]])
elif len(tagpath) == 0 and newtag not in tdict:
tdict[newtag] = value
def _to_be_modified_tags(tagpath, tag, related_tag, tdict):
'''if whole tagpath exists and the tag is not present, it is added with given value'''
# print "_to_be_modified_tags", tagpath, tag, related_tag, tdict
if len(tagpath) > 0 and tagpath[0] in tdict:
_to_be_modified_tags(tagpath[1:], tag, related_tag, tdict[tagpath[0]])
elif len(tagpath) == 0 and tag in tdict and related_tag in tdict:
tdict[tag] = str(long(tdict[tag]) & long(tdict[related_tag]))
orig_tree = md.parseString(requested_flow)
xml_resp_stream = oper_resp.encode('utf-8', 'ignore')
xml_resp_tree = md.parseString(xml_resp_stream)
nodeListOperFlows = xml_resp_tree.getElementsByTagNameNS("*", 'flow')
origDict = XMLtoDictParserTools.parseTreeToDict(
orig_tree._get_documentElement(),
ignoreList=IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON)
# origDict['flow-statistics'] = origDict.pop( 'flow' )
reportDict = {}
index = 0
for node in nodeListOperFlows:
nodeDict = XMLtoDictParserTools.parseTreeToDict(
node,
ignoreList=IGNORED_TAGS_FOR_OPERATIONAL_COMPARISON)
XMLtoDictParserTools.addDictValue(reportDict, index, nodeDict)
index += 1
# print nodeDict
# print origDict
# print reportDict
if nodeDict == origDict:
return True, ''
if nodeDict['flow']['priority'] == origDict['flow']['priority']:
for p in IGNORED_PATHS_FOR_OC:
td = copy.copy(origDict)
_rem_unimplemented_tags(p[0], p[1], td)
for (p, t, v) in TAGS_TO_ADD_FOR_OC:
_add_tags(p, t, v, td)
for (p, t, rt) in TAGS_TO_MODIFY_FOR_OC:
_to_be_modified_tags(p, t, rt, td)
# print "comparing1", nodeDict
# print "comparing2", td
if nodeDict == td:
return True, ''
if nodeDict == origDict:
return True, ''
return False, 'Flow found with diferences {0}'.format(
XMLtoDictParserTools.getDifferenceDict(nodeDict, origDict))
return False, ''
def get_data_for_flow_put_update(self, xml):
# action only for yet
xml_dom_input = md.parseString(xml)
actionList = xml_dom_input.getElementsByTagName('action')
if actionList is not None and len(actionList) > 0:
action = actionList[0]
for child in action.childNodes:
if child.nodeType == Element.ELEMENT_NODE:
nodeKey = (child.localName).encode('utf-8', 'ignore')
if nodeKey != 'order':
if nodeKey != 'drop-action':
new_act = child.ownerDocument.createElement('drop-action')
else:
new_act = child.ownerDocument.createElement('output-action')
onc = child.ownerDocument.createElement('output-node-connector')
onc_content = child.ownerDocument.createTextNode('TABLE')
onc.appendChild(onc_content)
new_act.appendChild(onc)
ml = child.ownerDocument.createElement('max-length')
ml_content = child.ownerDocument.createTextNode('60')
ml.appendChild(ml_content)
new_act.appendChild(ml)
child.parentNode.replaceChild(new_act, child)
return xml_dom_input.toxml(encoding='utf-8')