Source code for ucsmsdk.utils.comparesyncmo

# 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))