Skip to content

Commit

Permalink
refactor: multi-level snapshot comparison and config parser refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
alperenkose committed Dec 5, 2023
1 parent ceaa31c commit 4f0baee
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 305 deletions.
46 changes: 46 additions & 0 deletions examples/report/lic-5.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"license": {
"Logging Service": {
"authcode": null,
"custom": {
"_Log_Storage_TB": "7",
"logtype": "disk",
"alogging": "enabled"
},
"description": "Device Logging Service",
"expired": "no",
"expires": "August 04, 2024",
"feature": "Logging Service",
"issued": "June 27, 2022",
"serial": "013201027229"
},
"Y feature": {
"authcode": null,
"description": "Device Logging Service",
"expired": "no",
"expires": "August 04, 2024",
"feature": "Logging Service",
"issued": "June 29, 2022",
"serial": "013201027248"
},
"PAN-DB URL Filtering": {
"Logging Service": "somehting",
"authcode": null,
"description": "Palo Alto Networks URL Filtering License",
"expired": "no",
"expires": "June 30, 2028",
"feature": "PAN-DB URL Filtering",
"issued": "April 27, 2023",
"serial": "013201027229"
},
"C feature": {
"authcode": null,
"description": "Palo Alto Networks URL Filtering License",
"expired": "no",
"expires": "June 30, 2028",
"feature": "PAN-DB URL Filtering",
"issued": "April 27, 2023",
"serial": "013201027229"
}
}
}
45 changes: 45 additions & 0 deletions examples/report/lic-6.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"license": {
"Logging Service": {
"authcode": null,
"custom": {
"_Log_Storage_TB": "9",
"logtype": "tape",
"blogging": "enabled"
},
"description": "Device Logging Service",
"expired": "no",
"expires": "August 04, 2024",
"feature": "Logging Service",
"issued": "June 29, 2022",
"serial": "013201027229"
},
"X feature": {
"authcode": null,
"description": "Device Logging Service",
"expired": "no",
"expires": "August 04, 2024",
"feature": "Logging Service",
"issued": "June 29, 2022",
"serial": "013201027248"
},
"PAN-DB URL Filtering": {
"authcode": null,
"description": "Palo Alto Networks URL Filtering License",
"expired": "no",
"expires": "June 30, 2028",
"feature": "PAN-DB URL Filtering",
"issued": "April 29, 2023",
"serial": "013201027248"
},
"C feature": {
"authcode": null,
"description": "Palo Alto Networks URL Filtering License",
"expired": "no",
"expires": "June 30, 2028",
"feature": "PAN-DB URL Filtering",
"issued": "April 27, 2023",
"serial": "013201027229"
}
}
}
48 changes: 14 additions & 34 deletions examples/report/snapshot_load_compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,49 +25,19 @@ def load_snap(fname: str) -> dict:
# {"nics": {"count_change_threshold": 10}},
# {"license": {"properties": ["!serial"]}},

# {"license": {
# "properties": ["!serial", "!issued", "!authcode", "!expires", "!custom", "non-existing"] # exclude higher level
# }},
# {"license": {
# "properties": ["!serial", "!issued", "!authcode", "!expires", "non-existing", "!_Log_Storage_TB"] # works in multi-levels
# }},
# {"license": {
# "properties": ["all"]
# }},
# {"license": {
# "properties": ["!serial", "!issued", "!authcode", "!expires", "non-existing"] # invalid config is ignored and all is appended if all other are valid exclusions..
# }},
# {"license": {
# "properties": ["serial", "non-existing"] # compare only requested
# }},
# {"license": {
# "properties": ["custom"] # if key exists in some sub-dicts, it will compare all the keys for the other dicts since this will be treated as non-existing key and ignored! NOTE: now it only ignores top level for added/missing
# }},
# {"license": {
# "properties": ["!issued", "all"] # compare all except
# }},
# {"license": {
# "properties": ["!issued", "serial"] # skip one and compare specific ones
# }},
# NOTE lic-* files and below tests are added for testing during review - will be removed afterwards

# {"license": {
# "properties": ["Logging Service", "!custom"] # combination with parent
# }},
# "!license",
# "license",
# {"license": {
# "properties": ["!Logging Service","!X feature"]
# }},

######## Top level keys - not intented but works
# {"license": {
# "properties": ["Logging Service"] # even works for parent level
# }},
# {"license": {
# "properties": ["!Logging Service"] # even works for parent level
# "properties": ["!Logging Service"] # also support if property exists in different levels in different dicts
# }},
# {"license": {
# "properties": ["issued", "PAN-DB URL Filtering"] # even works for parent level - but without multi level
# "properties": ["issued", "PAN-DB URL Filtering"] # multi-level "AND" operation (combination with parent) for properties is not supported on purpose - "PAN-DB URL Filtering" diff will be made for all its attributes since its the parent
# }},
# {"license": {
# "properties": ["X feature"]
Expand All @@ -81,7 +51,7 @@ def load_snap(fname: str) -> dict:

######## 1st and 2nd level keys
# {"license": {
# "properties": ["!_Log_Storage_TB"] # works..
# "properties": ["!_Log_Storage_TB"] # works
# }},
# {"license": {
# "properties": ["_Log_Storage_TB"] # works
Expand All @@ -90,6 +60,9 @@ def load_snap(fname: str) -> dict:
# "properties": ["_Log_Storage_TB","issued"] # works
# }},
# {"license": {
# "properties": ["issued", "!logtype"] # works
# }},
# {"license": {
# "properties": ["serial", "!logtype"] # works
# }},
# {"license": {
Expand All @@ -108,15 +81,22 @@ def load_snap(fname: str) -> dict:
# "properties": ["something"] # since no such key is there it passes - works
# }},
# {"license": {
# "properties": ["serial", "non-existing"] # works - compare only requested
# }},
# {"license": {
# "properties": ["all"] # works
# }},
# {"license": {
# "properties": ["!issued", "all"] # works - compare all except
# }},
# {"license": {
# "properties": ["!logtype"] # works
# }},
{"license": {
"properties": ["!Logging Service", "issued"] # works
}},


# {"routes": {"properties": ["!flags"], "count_change_threshold": 10}},
# "!content_version",
# {
Expand Down
178 changes: 39 additions & 139 deletions panos_upgrade_assurance/snapshot_compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,49 +227,6 @@ def calculate_change_percentage(
result["passed"] = False
return result



# @staticmethod
# def get_all_keys(nested_dict): # NOTE keep it in snapshotcompare
# keys = []
# for key, value in nested_dict.items():
# keys.append(key)
# if isinstance(value, dict):
# keys.extend(SnapshotCompare.get_all_keys(value))
# return keys

@staticmethod
def is_key_comparison_allowed(key, properties):
"""
key str
properties List
"""
if properties is None:
return True

if key in properties:
return True
elif ("!"+key) in properties:
return False
elif "all" in properties:
return True
elif all((element.startswith("!") for element in properties)): # all '!' or empty list
return True

return False


@staticmethod
def is_key_excluded_in_properties(key, properties):
if properties is None:
return False

if ("!"+key) in properties:
return True

return False


@staticmethod
def calculate_diff_on_dicts(
left_side_to_compare: Dict[str, Union[str, dict]],
Expand Down Expand Up @@ -439,112 +396,55 @@ def calculate_diff_on_dicts(

missing = left_side_to_compare.keys() - right_side_to_compare.keys()
for key in missing:
if SnapshotCompare.is_key_comparison_allowed(key, properties):
if ConfigParser.is_element_included(key, properties):
result["missing"]["missing_keys"].append(key)
result["missing"]["passed"] = False

# if missing:
# result["missing"]["passed"] = False
# for key in missing:
# result["missing"]["missing_keys"].append(key)

added = right_side_to_compare.keys() - left_side_to_compare.keys()
for key in added:
if SnapshotCompare.is_key_comparison_allowed(key, properties):
if ConfigParser.is_element_included(key, properties):
result["added"]["added_keys"].append(key)
result["added"]["passed"] = False

# if added:
# result["added"]["passed"] = False
# for key in added:
# result["added"]["added_keys"].append(key)

common_keys = left_side_to_compare.keys() & right_side_to_compare.keys()
if common_keys:
print("common keys:",common_keys)
# keys_to_check = (
# ConfigParser(valid_elements=set(common_keys),
# requested_config=properties,
# ignore_invalid_config=True).prepare_config()
# if properties
# else common_keys
# )
keys_to_check = common_keys # TODO

item_changed = False
for key in keys_to_check:
print(f"Checking {key} in {left_side_to_compare}")
# if key == "PAN-DB URL Filtering":
# import pdb; pdb.set_trace()


if right_side_to_compare[key] != left_side_to_compare[key]:
if isinstance(left_side_to_compare[key], str):

print(f"is key {key} comparison allowed with {properties}")

if SnapshotCompare.is_key_comparison_allowed(key, properties):
result["changed"]["changed_raw"][key] = dict(
left_snap=left_side_to_compare[key],
right_snap=right_side_to_compare[key],
)
item_changed = True

elif isinstance(left_side_to_compare[key], dict):

nested_keys_within_common_key = chain_unique_set(get_all_dict_keys(left_side_to_compare[key]),
get_all_dict_keys(right_side_to_compare[key]))
print("nested keys within common key:",nested_keys_within_common_key)

# if is key excluded in properties
# continue
#
# if key in properties
# ##maybe remove key from properties
# ##if any nested key in properties; call with properties
# ##else call without properties
# recursive call without properties - DO NOT ALLOW MULTI LEVEL FILTERING..
#
# if any nested key in properties
# ##filter properties in nested level
# recursive call
#

if SnapshotCompare.is_key_excluded_in_properties(key, properties): # NOTE should check for exclude explicitly
continue # skip to the next key


if properties and key in properties:
# call without properties - DO NOT ALLOW MULTI LEVEL FILTERING..
nested_results = SnapshotCompare.calculate_diff_on_dicts(
left_side_to_compare=left_side_to_compare[key],
right_side_to_compare=right_side_to_compare[key],
)
else:
nested_results = SnapshotCompare.calculate_diff_on_dicts(
left_side_to_compare=left_side_to_compare[key],
right_side_to_compare=right_side_to_compare[key],
properties=properties,
)

# if any(
# SnapshotCompare.is_key_comparison_allowed(nested_key, properties)
# for nested_key in nested_keys_within_common_key
# ):
# nested_results = SnapshotCompare.calculate_diff_on_dicts(
# left_side_to_compare=left_side_to_compare[key],
# right_side_to_compare=right_side_to_compare[key],
# properties=properties,
# )

if nested_results: # TODO remove this
SnapshotCompare.calculate_passed(nested_results)
if not nested_results["passed"]:
result["changed"]["changed_raw"][key] = nested_results
item_changed = True

item_changed = False
for key in common_keys:
if right_side_to_compare[key] != left_side_to_compare[key]:
if isinstance(left_side_to_compare[key], str):
if ConfigParser.is_element_included(key, properties):
result["changed"]["changed_raw"][key] = dict(
left_snap=left_side_to_compare[key],
right_snap=right_side_to_compare[key],
)
item_changed = True

elif isinstance(left_side_to_compare[key], dict):
nested_keys_within_common_key = chain_unique_set(get_all_dict_keys(left_side_to_compare[key]),
get_all_dict_keys(right_side_to_compare[key]))
if ConfigParser.is_element_explicit_excluded(key, properties):
continue # skip to the next key

if properties and key in properties:
# call without properties - do not allow multi level (combined with parent) filtering..
nested_results = SnapshotCompare.calculate_diff_on_dicts(
left_side_to_compare=left_side_to_compare[key],
right_side_to_compare=right_side_to_compare[key],
)
else:
raise exceptions.WrongDataTypeException(f"Unknown value format for key {key}.")
result["changed"]["passed"] = not item_changed
nested_results = SnapshotCompare.calculate_diff_on_dicts(
left_side_to_compare=left_side_to_compare[key],
right_side_to_compare=right_side_to_compare[key],
properties=properties,
)

SnapshotCompare.calculate_passed(nested_results)
if not nested_results["passed"]:
result["changed"]["changed_raw"][key] = nested_results
item_changed = True
else:
raise exceptions.WrongDataTypeException(f"Unknown value format for key {key}.")
result["changed"]["passed"] = not item_changed

return result

Expand Down
Loading

0 comments on commit 4f0baee

Please sign in to comment.