# Copyright 2015 Cisco Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This module contains the api used to provide compare and sync functionality
within ucsm or across ucsm.
"""
from __future__ import print_function
import re
import logging
from .. import ucsgenutils
from .. import ucscoreutils
from ..ucscoremeta import MoPropertyMeta, MoMeta
from ..ucsexception import UcsWarning, UcsValidationException
from ..ucsmo import GenericMo
log = logging.getLogger('ucs')
skip_mos = ["LsVersionBeh", "LsmaintAck", "LsIssues", "VnicIpV4PooledAddr",
"VnicDynamicCon", "LsVConAssign", "ComputePooledSlot",
"ComputePooledRackUnit", "VnicIPv4PooledIscsiAddr",
"IppoolPooled", "IqnpoolPooled", "MacpoolPooled",
"UuidpoolPooled", "VnicConnDef", "VnicFcGroupDef",
"LsServerExtension"]
class _CompareStatus(object):
"""
Internal class used as enum.
"""
TYPES_DIFFERENT = 0
PROPS_DIFFERENT = 1
EQUAL = 2
class _PropDiff(object):
"""
Internal class for property delta
"""
def __init__(self, prop, old_value, new_value):
self.prop = prop
self.old_value = old_value
self.new_value = new_value
class _MoDiff(object):
"""
This class represents difference object.
"""
REMOVE = "<="
ADD_MODIFY = "=>"
EQUAL = "=="
def __init__(self, input_object, side_indicator, diff_property=None,
ref_values=None, diff_values=None):
self.input_object = input_object
self.dn = input_object.dn
self.side_indicator = side_indicator
self.diff_property = diff_property
if ref_values:
self.ref_prop_values = ref_values
else:
self.ref_prop_values = {}
if diff_values:
self.diff_prop_values = diff_values
else:
self.diff_prop_values = {}
def get_prop_diff(self, prop):
if prop in self.ref_prop_values and prop in self.diff_prop_values:
prop_name = ""
for key in self.ref_prop_values:
if prop.lower() == key.lower():
prop_name = key
break
return _PropDiff(prop_name, self.ref_prop_values[prop],
self.diff_prop_values[prop])
return None
def _update_mo_dn_and_naming_props(mo, ref_dn):
"""
Internal method to modify the naming properties of mo using dn.
"""
# modify diffmo dn with refmo dn
mo.__dict__['dn'] = ref_dn
# modify diffmo rn with refmo rn
ref_rn = re.sub(r'^.*/', '', ref_dn)
mo.__dict__['rn'] = ref_rn
# modify diff mo naming properties using ref_rn
naming_prop_dict = ucscoreutils.get_naming_props(rn_str=ref_rn,
rn_pattern=mo.mo_meta.rn)
for prop in naming_prop_dict:
mo.__dict__[prop] = naming_prop_dict[prop]
def _translate_managed_object(mo, xlate_org, xlate_map):
"""
Internal method used to translate a managed object which helps in comparing
two mo with different dn.
"""
if not xlate_org and not xlate_map:
return mo
mo = mo.clone()
ref_dn = None
if xlate_org is not None:
match_obj = re.match(
r'^(org-[\-\.:_a-zA-Z0-9]{1,16}/)*org-[\-\.:_a-zA-Z0-9]{1,16}',
mo.dn)
if match_obj:
if mo.get_class_id() == "OrgOrg":
ref_dn = re.sub("%s" % (match_obj.group(0)),
"%s" % xlate_org,
mo.dn)
else:
ref_dn = re.sub("^%s/" % (match_obj.group(0)),
"%s/" % xlate_org,
mo.dn)
_update_mo_dn_and_naming_props(mo, ref_dn)
if xlate_map is not None:
diff_dn = mo.dn
if diff_dn in xlate_map:
ref_dn = xlate_map[diff_dn]
_update_mo_dn_and_naming_props(mo, ref_dn)
else:
diff_dn = re.sub(r'[/]*[^/]+$', '', diff_dn)
while diff_dn:
if diff_dn not in xlate_map:
diff_dn = re.sub(r'[/]*[^/]+$', '', diff_dn)
continue
ref_dn = re.sub("^%s/" % diff_dn,
"%s/" % xlate_map[diff_dn],
mo.dn)
_update_mo_dn_and_naming_props(mo, ref_dn)
break
return mo
def _list_has_values(obj):
"""
Internal function to check if obj is non empty list.
"""
return isinstance(obj, list) and len(obj) > 0
def _should_skip_mo(mo):
"""
Internal function to check if mo to be skipped from comparison.
"""
if mo is None:
return True
if isinstance(mo, GenericMo):
if ucsgenutils.word_u(mo.get_class_id()) in skip_mos:
return True
elif mo.get_class_id() in skip_mos or \
mo.mo_meta.inp_out == MoMeta.ACCESS_TYPE_OUTPUTONLY or \
(mo.mo_meta.inp_out == MoMeta.ACCESS_TYPE_IO
and len(mo.mo_meta.access) == 1
and mo.mo_meta.access[0] == "read-only"):
return True
return False
def _get_skip_props(mo, include_operational=False, version_filter=True):
"""
Internal function to skip mo property if not to be considered for sync.
"""
skip_props = []
for prop in mo.prop_meta:
mo_property_meta = mo.prop_meta[prop]
if mo_property_meta is None:
continue
# not include operational property
if not include_operational:
if mo_property_meta.access in (MoPropertyMeta.INTERNAL,
MoPropertyMeta.READ_ONLY):
skip_props.append(prop)
# checks if property is part of current or earlier ucsm schema
if version_filter:
version = mo.get_handle().version
if version is None or version < mo_property_meta.version or \
mo_property_meta.access == MoPropertyMeta.INTERNAL:
skip_props.append(prop)
return skip_props
def _compare_known_mo(from_mo, to_mo, diff, include_operational=False,
version_filter=True):
"""
Internal function to compare if both the ref and diff obj is known mo.
"""
from_mo_skip_props = None
if not include_operational or version_filter:
from_mo_skip_props = _get_skip_props(from_mo, include_operational,
version_filter)
# comparing known properties of ref mo
for prop in from_mo.prop_meta:
if from_mo_skip_props and prop in from_mo_skip_props:
continue
# if not exist in diff mo
if not hasattr(to_mo, prop):
log.debug("Property '%s' of '%s' does not exist in diff obj." % (
prop, from_mo.dn))
continue
if getattr(from_mo, prop) != getattr(to_mo, prop):
diff.append(prop)
# comparing unknown properties of ref mo
from_xtra_props = from_mo._ManagedObject__xtra_props
to_xtra_props = to_mo._ManagedObject__xtra_props
if not to_xtra_props:
return
for prop in from_xtra_props:
if prop not in to_xtra_props:
continue
if from_xtra_props[prop].value != to_xtra_props[prop].value:
if version_filter:
UcsWarning("Ignoring xtra property '%s' of '%s'" % (
prop, from_mo.dn))
else:
diff.append(prop)
def _compare_unknown_mo(from_mo, to_mo, diff, version_filter=True):
"""
Internal function to compare if any or both of the ref and diff obj is
unknown mo.
"""
# both unknown mo
if version_filter:
return
for prop in from_mo.properties:
if prop not in to_mo.properties:
continue
if from_mo.properties[prop] != to_mo.properties[prop]:
diff.append(prop)
def _compare(from_mo, to_mo, diff, include_operational=False,
version_filter=True):
"""
Internal method to support compare reference and difference object.
"""
# compare mo of different types
if from_mo.get_class_id() != to_mo.get_class_id():
return _CompareStatus.TYPES_DIFFERENT
# compare for unknown class_id
if isinstance(from_mo, GenericMo) or isinstance(to_mo, GenericMo):
_compare_unknown_mo(from_mo, to_mo, diff, version_filter)
# compare for known class_id
else:
_compare_known_mo(from_mo, to_mo, diff, include_operational,
version_filter)
if len(diff) > 0:
return _CompareStatus.PROPS_DIFFERENT
return _CompareStatus.EQUAL
def _compare_common_mo(ref_dict, diff_dict, include_operational=False,
version_filter=True, include_equal=False,
exclude_different=False):
diff_output = []
common_dns = set(ref_dict) & set(diff_dict)
for dn in common_dns:
ref_mo = ref_dict[dn]
diff_mo = diff_dict[dn]
diff_props = []
# compare both mo for property and type
diff_status = _compare(ref_mo, diff_mo, diff_props,
include_operational, version_filter)
if diff_status == _CompareStatus.EQUAL and include_equal:
mo_diff = _MoDiff(ref_mo, _MoDiff.EQUAL)
diff_output.append(mo_diff)
continue
if exclude_different:
continue
if diff_status == _CompareStatus.TYPES_DIFFERENT:
mo_diff = _MoDiff(ref_mo, _MoDiff.REMOVE)
diff_output.append(mo_diff)
mo_diff = _MoDiff(diff_mo, _MoDiff.ADD_MODIFY)
diff_output.append(mo_diff)
elif diff_status == _CompareStatus.PROPS_DIFFERENT:
ref_values = {}
diff_values = {}
for prop in diff_props:
ref_values[prop] = getattr(ref_mo, prop)
diff_values[prop] = getattr(diff_mo, prop)
mo_diff = _MoDiff(diff_mo, _MoDiff.ADD_MODIFY,
diff_props, ref_values, diff_values)
diff_output.append(mo_diff)
return diff_output
[docs]def compare_ucs_mo(ref_obj, diff_obj,
exclude_different=False,
include_equal=False,
version_filter=True,
include_operational=False,
xlate_org=None, xlate_map=None):
"""
Compares the state of two managed objects with same dn.
Args:
ref_obj (list): list of Managed Objects of reference UCSM
diff_obj (list): list of Managed Objects of difference UCSM
exclude_different (bool): default:False. When True, compares MOs that
exist on both reference and difference UCSM
include_equal (bool): default:False. When True, also displays MOs which
are equal.
version_filter (bool): default:True. When False, ignores properties
which is introduced in later UCSM version than
reference UCSM version.
include_operational (bool): default:False. When True, compares all the
properties of mo.
xlate_org (str): org-dn of reference mo, compares objects of same type
and same rn under different org.
xlate_map (dict) : {"difference_dn": "reference_dn"}, compares objects
of same type with different dn.
Returns:
List of MoDiff objects:
MoDiff Object : dn - dn of Managed Object
input_object - Managed Object
side_indicator -
"=>" Add the diff obj to ref ucsm
or
Modify the ref object by diff object on ref ucsm
"<=" Removes the ref obj from ref ucsm
"==" object is equal on both ref and diff ucsm
diff_property - property list with different value
Example:
#1
ref_mos = [ref_handle.query_dn(dn="org-root/ls-sp")]
diff_mos = [diff_handle.query_dn(dn="org-root/ls-sp")]
compare_ucs_mo(ref_mos, diff_mos)
#2
ref_mos = [ref_handle.query_dn(dn="org-root/org-ref/ls-sp")]
diff_mos = [diff_handle.query_dn(dn="org-root/org-diff/ls-sp")]
compare_ucs_mo(ref_mos, diff_mos, xlate_org = "org-root/org-ref")
#3
ref_mos = [ref_handle.query_dn(dn="org-root/ls-ref")]
diff_mos = [diff_handle.query_dn(dn="org-root/ls-diff")]
compare_ucs_mo(ref_mos, diff_mos,
xlate_map = {"org-root/ls-diff": "org-root/ls-ref"})
"""
reference_dict = {}
difference_dict = {}
diff_output = []
if ref_obj is not None and _list_has_values(ref_obj):
for mo in ref_obj:
if _should_skip_mo(mo):
continue
reference_dict[mo.dn] = mo
if diff_obj is not None and _list_has_values(diff_obj):
for mo in diff_obj:
if _should_skip_mo(mo):
continue
translated_mo = _translate_managed_object(mo, xlate_org, xlate_map)
difference_dict[translated_mo.dn] = translated_mo
if not exclude_different:
only_ref_dns = set(reference_dict) - set(difference_dict)
only_diff_dns = set(difference_dict) - set(reference_dict)
for dn in only_ref_dns:
diff_output.append(_MoDiff(reference_dict[dn],
_MoDiff.REMOVE))
for dn in only_diff_dns:
diff_output.append(_MoDiff(difference_dict[dn],
_MoDiff.ADD_MODIFY))
diff_with_props = _compare_common_mo(reference_dict, difference_dict,
include_operational,
version_filter, include_equal,
exclude_different)
if diff_with_props:
diff_output.extend(diff_with_props)
return diff_output
[docs]def sync_ucs_mo(ref_handle, difference,
delete_not_present=False,
version_filter=True):
"""
syncs the difference object on reference ucsm.
In other words, make the state of reference ucsm for the respective
ref object same as difference object on reference ucsm.
Args:
ref_handle (UcsHandle): connect handle of reference ucsm
difference (list of MoDiff objects): output of compare-ucs-mo api
delete_not_present (bool): by dafault False. If set to true, will
delete ref mo which does not exist on
difference ucsm
version_filter (bool): by default True: If set to False, ignore
properties which is introduced in later ucsm
version than reference ucsm
Returns:
None
Example:
#1
ref_mos = [ref_handle.query_dn(dn="org-root/ls-sp")]
diff_mos = [diff_handle.query_dn(dn="org-root/ls-sp")]
difference = compare_ucs_mo(ref_handle, ref_mos,diff_handle, diff_mos)
sync_ucs_mo(ref_handle, difference=difference, delete_not_present=True)
"""
if difference is None or not _list_has_values(difference):
raise UcsValidationException(
"difference object parameter is not provided.")
to_commit = False
for mo_diff in difference:
mo = mo_diff.input_object
class_id = mo.get_class_id()
if isinstance(mo, GenericMo):
UcsWarning("Ignoring '%s'.Unknown ClasId '%s'" % (mo.dn, class_id))
continue
# Remove
if mo_diff.side_indicator == _MoDiff.REMOVE and delete_not_present:
log.debug("removing mo '%s'." % mo.dn)
ref_handle.remove_mo(mo)
to_commit = True
if version_filter:
mo_meta = mo.mo_meta
if ref_handle.version < mo_meta.version:
UcsWarning("Ignoring unsupported class_id '%s' for dn '%s'" %
(class_id, mo.dn))
continue
# Add or Modify
if mo_diff.side_indicator == _MoDiff.ADD_MODIFY:
add_exists = False
if not isinstance(mo, GenericMo) and 'Add' in mo.mo_meta.verbs:
add_exists = True
# Add
if add_exists and (mo_diff.diff_property is None
or len(mo_diff.diff_property) == 0):
log.debug("adding mo '%s'" % mo.dn)
mo.mark_dirty()
ref_handle.add_mo(mo)
to_commit = True
# Modify the Managed Object
else:
if mo_diff.diff_property is None \
or len(mo_diff.diff_property) == 0:
continue
set_flag = False
for prop in mo_diff.diff_property:
if mo.prop_meta[prop].access == MoPropertyMeta.READ_WRITE:
setattr(mo, prop, getattr(mo, prop))
set_flag = True
if not set_flag:
log.debug("No Configurable Properties Changed for mo '%s'"
% mo.dn)
else:
log.debug("set mo '%s'" % mo.dn)
ref_handle.set_mo(mo)
to_commit = True
if to_commit:
ref_handle.commit()
[docs]def write_mo_diff(diff_obj):
"""
Writes the difference managedObject(output of CompareManagedObject)
on the terminal.
"""
tab_size = 8
if not _list_has_values(diff_obj):
return
if isinstance(diff_obj[0], _MoDiff):
print("dn".ljust(tab_size * 10), "input_object".ljust(tab_size * 4),
"side_indicator".ljust(tab_size * 3), "diff_property")
print("--".ljust(tab_size * 10), "-----------".ljust(tab_size * 4),
"-------------".ljust(tab_size * 3), "------------")
diff_obj_mod = []
for mo_diff in diff_obj:
if not isinstance(mo_diff, _MoDiff):
continue
diff_obj_mod.append(mo_diff)
for mo_diff in sorted(diff_obj_mod, key=lambda mo: mo.dn):
print(str(mo_diff.dn).ljust(tab_size * 10),
str(mo_diff.input_object.get_class_id()).ljust(tab_size * 4),
str(mo_diff.side_indicator).ljust(tab_size * 3),
str(sorted(mo_diff.diff_property))
if mo_diff.diff_property else str(mo_diff.diff_property))