CiscoUcs/imcsdk

View on GitHub
imcsdk/imcmo.py

Summary

Maintainability
F
6 days
Test Coverage
# 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 prop
#  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 ManagedObject and GenericManagedObject Class.
"""

import logging
import os

from . import imcgenutils
from . import imccoreutils
from . import imccoremeta
import six

try:
    import xml.etree.cElementTree as ET
    from xml.etree.cElementTree import Element, SubElement
except ImportError:
    import cElementTree as ET
    from cElementTree import Element, SubElement

from .imccoremeta import WriteXmlOption
from .imcexception import ImcValidationException, ImcWarning
from .imccore import ImcBase

log = logging.getLogger('imc')


class _GenericProp():
    """
    Internal class to handle the unknown property.
    """
    def __init__(self, name, value, is_dirty):
        self.name = name
        self.value = value
        self.is_dirty = is_dirty


class ManagedObject(ImcBase):
    """
    This class structures/represents all the managed objects.
    """

    DUMMY_DIRTY = "0x1"
    __internal_prop = frozenset(
        ["_dirty_mask", "_class_id", "_child", "_handle", ''])

    def __init__(
            self,
            class_id,
            parent_mo_or_dn=None,
            from_xml_response=False,
            **kwargs):
        self.__parent_mo = None
        self.__status = None
        self.__parent_dn = None
        self.__xtra_props = {}
        self.__xtra_props_dirty_mask = 0x1

        if parent_mo_or_dn:
            if isinstance(parent_mo_or_dn, ManagedObject):
                self.__parent_mo = parent_mo_or_dn
                self.__parent_dn = self.__parent_mo.dn
            elif isinstance(parent_mo_or_dn, str) or \
                    isinstance(parent_mo_or_dn, six.text_type):
                self.__parent_dn = str(parent_mo_or_dn)
            else:
                raise ValueError('parent mo or dn must be specified')

        platform = None
        if kwargs and '_handle' in kwargs:
            platform = kwargs['_handle'].platform
        mo_meta = imccoreutils.get_mo_meta(self, platform=platform)
        xml_attribute = mo_meta.xml_attribute

        ImcBase.__init__(self, imcgenutils.word_u(xml_attribute))
        # self.mark_dirty()

        if self.__parent_mo:
            self.__parent_mo.child_add(self)

        if kwargs:
            for prop_name, prop_value in list(kwargs.items()):
                if prop_name not in self.__internal_prop and \
                        not imccoreutils.prop_exists(self, prop_name):
                    log.debug("Unknown property %s" % prop_name)
                self.__set_prop(prop_name, prop_value)

        if not from_xml_response:
            self._rn_set()
            self._dn_set()

    @property
    def parent_mo(self):
        """Getter method of ManagedObject Class"""

        return self.__parent_mo

    def _rn_set(self):
        """
        Internal method to set rn
        """

        if "prop_meta" in dir(self) and imccoreutils.prop_exists(self, "rn"):
            self.rn = self.make_rn()
        else:
            self.rn = ""

    def _dn_set(self):
        """
        Internal method to set dn
        """

        if "prop_meta" in dir(self) and imccoreutils.prop_exists(self, "dn"):
            if self.__parent_dn:
                self.dn = self.__parent_dn + '/' + self.rn
            else:
                self.dn = self.rn
        else:
            self.dn = ""

    def __setattr__(self, name, value):
        """
        overridden setattr method
        """

        if "prop_meta" in dir(self) and \
                imccoreutils.prop_exists(self, name):
            if name in dir(self):
                self.__set_prop(name, value)
            else:
                if value:
                    match, msg = imccoreutils.validate_property_value(self, name, value)
                    if not match:
                        raise ValueError("Invalid Value Exception - "
                                         "[%s]: Prop <%s>, Value<%s>. "
                                         % (self.__class__.__name__,
                                            name,
                                            value))
                object.__setattr__(self, name, value)
                prop = imccoreutils.get_prop_meta(self, name)
                if prop.mask:
                    self._dirty_mask |= prop.mask
        elif name.startswith("_"):
            object.__setattr__(self, name, value)
        else:
            # These are properties which the current version of imc sdk
            # does not know of.
            # The code will come here lot often, when using older version of
            # imcsdk with newer releases on the IMC.
            # This needs to be handled so that the same sdk can work across
            # multiple imc releases
            self.__xtra_props[name] = _GenericProp(name, value, True)
            self._dirty_mask |= self.__xtra_props_dirty_mask
            object.__setattr__(self, name, value)

    def __set_prop(self, name, value, mark_dirty=True, forced=False):
        """
        Internal method to set the properties after validation

        Args:
            name (str): property name
            value (str): property value
            mark_dirty (bool): if True, property will be part of xml request
            forced (bool): if True, set the value without validation

        Returns:
            None
        """

        if value is None:
            return
        if not forced and name not in ManagedObject.__internal_prop:
            prop = imccoreutils.get_prop_meta(self, name)
            if not imccoreutils.is_writable_prop(self, name):
                if getattr(self, name) is not None or \
                                prop.access != \
                                imccoremeta.MoPropertyMeta.CREATE_ONLY:
                    raise ValueError("%s is not a read-write property." % name)
            is_valid, msg = imccoreutils.validate_property_value(self, name, value)
            if not is_valid:
                raise ValueError("Invalid Value Exception - "
                                 "[%s]: Prop <%s>, Value<%s>. "
                                 % (self.__class__.__name__,
                                    name,
                                    value))
                # return False
            mask = prop.mask
            if mask and mark_dirty:
                self._dirty_mask |= mask
        object.__setattr__(self, name, value)

    def check_prop_match(self, **kwargs):
        for prop_name in kwargs:
            if kwargs[prop_name] is None:
                continue
            if not imccoreutils.prop_exists(self, prop_name):
                raise ValueError("Invalid Property Name Exception - "
                                 "Class [%s]: Prop <%s> "
                                 % (self.__class__.__name__, prop_name))

            if str(kwargs[prop_name]) != getattr(self, prop_name):
                return False
        return True

    def validate_inputs(self, **kwargs):
        validation_errors = []
        for prop in kwargs:
            if not imccoreutils.prop_exists(self, prop):
                continue
            if not kwargs[prop]:
                continue
            print(("prop ", prop))
            is_valid, msg = imccoreutils.validate_property_value(self, prop, kwargs[prop])
            if not is_valid:
                validation_errors.append({"prop": prop, "error": "Invalid value error", "msg": msg})
        return validation_errors

    def set_prop_multiple(self, **kwargs):
        for prop_name in kwargs:
            if imccoreutils.prop_exists(self, prop_name):
                self.__set_prop(prop_name, kwargs[prop_name])
            else:
                ImcWarning("Invalid Property Name for "
                           "Class [%s]: Prop <%s>, setting it forcefully"
                           % (self.__class__.__name__, prop_name))
                self.__set_prop(prop_name, kwargs[prop_name], forced=True)

    def __str__(self):
        """
        Method to return string representation of a managed object.
        """

        ts = 8
        out_str = "\n"
        out_str += "Managed Object\t\t\t:\t" + str(self._class_id) + "\n"
        out_str += "-" * len("Managed Object") + "\n"
        for prop, prop_value in sorted(self.__dict__.items()):
            if prop in ManagedObject.__internal_prop or prop.startswith(
                    "_ManagedObject__"):
                continue
            if prop in self.__xtra_props:
                prop = "[X]" + str(prop)
            out_str += str(prop).ljust(ts * 4) + ':' + str(
                prop_value) + "\n"

        out_str += "\n"
        return out_str

    def mark_dirty(self):
        """
        This method marks the managed object dirty.
        """

        if self.__class__.__name__ == "ManagedObject" and not self.is_dirty():
            self._dirty_mask = ManagedObject.DUMMY_DIRTY
        elif "mo_meta" in dir(self):
            mo_meta = imccoreutils.get_mo_meta(self)
            self._dirty_mask = mo_meta.mask

    def is_dirty(self):
        """
        This method checks if managed object is dirty.
        """

        return self._dirty_mask != 0 or self.child_is_dirty()

    def make_rn(self):
        """
        This method returns the Rn for a managed object.
        """

        import re

        platform = self._handle.platform if self._handle else None
        mo_meta = imccoreutils.get_mo_meta(self, platform=platform)
        rn_pattern = mo_meta.rn
        for prop in re.findall(r"""\[([^\]]*)\]""", rn_pattern):
            if imccoreutils.prop_exists(self, prop):
                if getattr(self, prop):
                    rn_pattern = re.sub(r"""\[%s\]""" % prop,
                                        '%s' % getattr(self, prop), rn_pattern)
                else:
                    log.debug('Property "%s" was None in make_rn' % prop)
                    raise ImcValidationException(
                        'Property "%s" was None in make_rn' % prop)
            else:
                log.debug(
                    'Property "%s" was not found in make_rn arguments' % prop)
                raise ImcValidationException(
                    'Property "%s" was not found in make_rn arguments' % prop)

        return rn_pattern

    def to_xml(self, xml_doc=None, option=None, elem_name=None, cookie=None):
        """
        Method writes the xml representation of the managed object.
        """

        unknown_properties = []
        if option == WriteXmlOption.DIRTY and not self.is_dirty():
            log.debug("Object is not dirty")
            return

        platform = None
        handle = None
        if cookie:
            handle = imccoreutils.get_handle_from_cookie(cookie)
            if handle:
                platform = handle.platform

        mo_meta = imccoreutils.get_mo_meta(self)
        xml_attribute = mo_meta.xml_attribute
        xml_obj = self.elem_create(class_tag=xml_attribute,
                                   xml_doc=xml_doc,
                                   override_tag=elem_name)

        for key in self.__dict__:
            if key != 'rn' and imccoreutils.prop_exists(
                                self,
                                key,
                                platform=platform):
                prop = imccoreutils.get_prop_meta(
                                self,
                                key,
                                platform=platform)
                if (option != WriteXmlOption.DIRTY or (
                            prop.mask and
                            self._dirty_mask & prop.mask != 0)):
                    value = getattr(self, key)
                    if value is not None:
                        #Skip version validation for Bios Tokens. For other MOs continue to do version validation.
                        if handle and not (mo_meta.name.startswith("BiosVf") and prop.name.startswith("vp")):
                            if prop.version <= handle.version:
                                xml_obj.set(prop.xml_attribute, value)
                            else:
                                log.debug('## Not sending property: %s' % prop.name)
                                log.debug('## handle.version:%s prop.version:%s', handle.version, prop.version)
                        else:
                            xml_obj.set(prop.xml_attribute, value)
                        # xml_obj.set(prop.xml_attribute, value)
                        # The above has been commented out to allow higher layers set newer properties on older servers
                        # The version check will take care of not sending the newer properties on older servers.
            else:
                if key not in self.__xtra_props:
                    # This is an internal property
                    # This should not be a part of the xml
                    if key not in ManagedObject.__internal_prop and \
                            key not in ['rn', 'dn'] and \
                            not key.startswith("_"):
                        unknown_properties.append(key)
                    continue

                # This is an unknown property
                # This should be a part of the xml
                # The server might understand this property, even though
                # the sdk does not
                if option != WriteXmlOption.DIRTY or \
                        self.__xtra_props[key].is_dirty:
                    value = self.__xtra_props[key].value
                    if value is not None:
                        xml_obj.set(key, value)

        if 'dn' not in xml_obj.attrib:
            xml_obj.set('dn', self.dn)

        self.child_to_xml(xml_obj, option, cookie=cookie)
        # if unknown_properties:
        #     log.info("List of properties not applicable for the current platform:")
        #     log.info(unknown_properties)

        return xml_obj

    def from_xml(self, elem, handle=None):
        """
        Method updates the object from the xml representation of the managed
        object.
        """

        self._handle = handle
        if elem.attrib:
            if self.__class__.__name__ != "ManagedObject":
                for attr_name, attr_value in list(elem.attrib.items()):
                    if imccoreutils.property_exists_in_prop_map(self,
                                                                attr_name):
                        attr_name = \
                            imccoreutils.get_property_from_prop_map(self,
                                                                    attr_name)
                    else:
                        self.__xtra_props[attr_name] = _GenericProp(
                            attr_name,
                            attr_value,
                            False)
                    object.__setattr__(self, attr_name, attr_value)
            else:
                for attr_name, attr_value in list(elem.attrib.items()):
                    object.__setattr__(self, attr_name, attr_value)

        if hasattr(self, 'rn') and not hasattr(self, 'dn'):
            self._dn_set()
        elif not hasattr(self, 'rn') and hasattr(self, 'dn'):
            self.__set_prop("rn", os.path.basename(self.dn), forced=True)
        self.mark_clean()

        child_elems = list(elem)
        if child_elems:
            for child_elem in child_elems:
                if not ET.iselement(child_elem):
                    continue

                if self.__class__.__name__ != "ManagedObject":
                    mo_meta = imccoreutils.get_mo_meta(self)
                    field_names = mo_meta.field_names
                    if field_names and child_elem.tag in field_names:
                        pass

                class_id = imcgenutils.word_u(child_elem.tag)
                child_obj = imccoreutils.get_imc_obj(class_id, child_elem,
                                                     self)
                self.child_add(child_obj)
                child_obj.from_xml(child_elem, handle)

    def sync_mo(self, mo):
        """
        Method to return string representation of a managed object.
        """

        for prop, prop_value in sorted(self.__dict__.items()):
            if prop in ManagedObject.__internal_prop or prop.startswith(
                    "_ManagedObject__"):
                continue
            mo.__dict__[prop] = prop_value if prop != "status" else None
        mo.mark_clean()
        return None

    def show_tree(self, level=0):
        """
        Method to return string representation of a managed object.
        """

        indent = "  "
        level_indent = "%s%s)" % (indent * level, level)
        print(("%s %s[%s]" % (level_indent, self._class_id, self.dn)))
        for ch_ in self.children:
            level += 1
            ch_.show_tree(level)
            level -= 1
        return None

    def show_hierarchy(self, level=0, depth=None,
                       show_level=[], platform=None):
        """
        Method to return string representation of a managed object.
        """

        from .imccoreutils import print_mo_hierarchy

        print_mo_hierarchy(self._class_id, level, depth,
                           show_level, platform)

    def get_version(self, platform):
        """
        Method to get the IMC version from which this MO is supported
        """

        from .imccoreutils import get_mo_meta
        mo_meta = get_mo_meta(self, platform)
        return mo_meta.version


def generic_mo_from_xml(xml_str):
    """
    create GenericMo object from xml string
    """
    root_elem = ET.fromstring(xml_str)
    class_id = root_elem.tag
    gmo = GenericMo(class_id)
    gmo.from_xml(root_elem)
    return gmo


def generic_mo_from_xml_elem(elem):
    """
    create GenericMo object from xml element
    """

    from . import imcxmlcodec as xc
    xml_str = xc.to_xml_str(elem)
    gmo = generic_mo_from_xml(xml_str)
    return gmo


class GenericMo(ImcBase):
    """
    This class implements a Generic Managed Object.

    Args:
        class_id (str): class id of managed object
        parent_mo_or_dn (ManagedObject or str): parent managed object or dn
    """

    # Every variable that should not be a part of the final xml
    # should start with a underscore in this class
    def __init__(self, class_id, parent_mo_or_dn=None, **kwargs):

        self.__properties = {}

        if isinstance(parent_mo_or_dn, GenericMo):
            self.__parent_mo = parent_mo_or_dn
            self.__parent_dn = parent_mo_or_dn.dn
        elif isinstance(parent_mo_or_dn, str):
            self.__parent_dn = parent_mo_or_dn
            self.__parent_mo = None
        elif parent_mo_or_dn is None:
            self.__parent_dn = ""
            self.__parent_mo = None
        else:
            raise ValueError("parent_mo_or_dn should be an instance of str or "
                             "GenericMo")

        ImcBase.__init__(self, class_id)

        if kwargs:
            for key, value in list(kwargs.items()):
                self.__dict__[key] = str(value)
                self.__properties[key] = str(value)

        if 'rn' in dir(self) and 'dn' in dir(self):
            pass
        elif 'rn' in dir(self) and 'dn' not in dir(self):
            if self.__parent_dn and self.__parent_dn != "":
                self.dn = self.__parent_dn + '/' + self.rn
                self.__properties['dn'] = self.dn
            else:
                self.dn = self.rn
                self.__properties['dn'] = self.dn
        elif 'rn' not in dir(self) and 'dn' in dir(self):
            self.rn = os.path.basename(self.dn)
            self.__properties['rn'] = self.rn
        else:
            self.rn = ""
            self.dn = ""

        if self.__parent_mo:
            self.__parent_mo.child_add(self)

    def to_xml(self, xml_doc=None, option=None, cookie=None):
        """
        This method returns the xml element node for the current object
        with it's hierarchy.

        Args:
            xml_doc: document to which the Mo attributes are added.
                    Can be None.
            option: not required for Generic Mo class object

        Example:
            from imcmsdk.imcmo import GenericMo\n
            args = {"a": 1, "b": 2, "c":3}\n
            obj = GenericMo("testLsA", "org-root", **args)\n
            obj1 = GenericMo("testLsB", "org-root", **args)\n
            obj.add_child(obj1)\n
            elem = obj.write_xml()\n

            import imcmsdk.imcxmlcodec as xc\n
            xc.to_xml_str(elem)\n

        Output:
            '<testLsA a="1" b="2" c="3" dn="org-root/" rn="">\n
                <testLsB a="1" b="2" c="3" dn="org-root/" rn="" />\n
            </testLsA>'
        """

        if xml_doc is None:
            xml_obj = Element(imcgenutils.word_l(self._class_id))
        else:
            xml_obj = SubElement(xml_doc, imcgenutils.word_l(self._class_id))
        for key in self.__dict__:
            if not key.startswith('_'):
                xml_obj.set(key, getattr(self, key))
        self.child_to_xml(xml_obj)
        return xml_obj

    @property
    def properties(self):
        """Getter Method of GenericMO Class"""
        return self.__properties

    def from_xml(self, elem, handle=None):
        """
        This method is form objects out of xml element.
        This is called internally from imcxmlcode.from_xml_str
        method.

        Example:
            xml = '<testLsA a="1" b="2" c="3" dn="org-root/" rn="">
            <testLsB a="1" b="2" c="3" dn="org-root/" rn="" /></testLsA>'\n
            obj = xc.from_xml_str(xml)\n

            print type(obj)\n

        Outputs:
            <class 'imcsdk.imcmo.GenericMo'>
        """

        if elem is None:
            return None

        self._handle = handle
        self._class_id = elem.tag
        if elem.attrib:
            for name, value in list(elem.attrib.items()):
                self.__dict__[name] = value
                self.__properties[name] = str(value)

        if self.rn and self.dn:
            pass
        elif self.rn and not self.dn:
            if self.__parent_dn and self.__parent_dn != "":
                self.dn = self.__parent_dn + '/' + self.rn
                self.__properties['dn'] = self.dn
            else:
                self.dn = self.rn
                self.__properties['dn'] = self.dn
        elif not self.rn and self.dn:
            self.rn = os.path.basename(self.dn)
            self.__properties['rn'] = self.rn

        children = list(elem)
        if children:
            for child in children:
                if not ET.iselement(child):
                    continue
                class_id = imcgenutils.word_u(child.tag)
                pdn = None
                if 'dn' in dir(self):
                    pdn = self.dn
                child_obj = GenericMo(class_id, parent_mo_or_dn=pdn)
                self.child_add(child_obj)
                child_obj.from_xml(child, handle)

    def __get_mo_obj(self, class_id):
        """
        Internal methods to create managed object from class_id
        """

        import inspect

        mo_class = imccoreutils.load_class(class_id)
        mo_class_params = inspect.getargspec(mo_class.__init__)[0][2:]
        mo_class_param_dict = {}
        for param in mo_class_params:
            prop = imccoreutils.get_prop_meta(mo_class, param)
            mo_param = prop.xml_attribute
            if mo_param not in self.__properties:
                if 'rn' in self.__properties:
                    rn_str = self.__properties['rn']
                elif 'dn' in self.__properties:
                    rn_str = os.path.basename(self.__properties['dn'])

                mo_meta = imccoreutils.get_mo_meta(mo_class)
                rn_pattern = mo_meta.rn
                np_dict = imccoreutils.get_naming_props(rn_str, rn_pattern)
                if param not in np_dict:
                    mo_class_param_dict[param] = ""
                else:
                    mo_class_param_dict[param] = np_dict[param]
            else:
                mo_class_param_dict[param] = self.__properties[mo_param]

        p_dn = ""

        mo_meta = imccoreutils.get_mo_meta(mo_class)
        class_parents = mo_meta.parents
        if 'topRoot' in class_parents:
            mo_obj = mo_class(**mo_class_param_dict)
        else:
            mo_obj = mo_class(parent_mo_or_dn=p_dn, **mo_class_param_dict)
        return mo_obj

    def to_mo(self):
        """
        Converts GenericMo to ManagedObject
        """

        from . import imcmeta

        class_id = imcgenutils.word_u(self._class_id)
        if class_id not in imcmeta.MO_CLASS_ID:
            return None

        mo = self.__get_mo_obj(class_id)
        if not mo:  # or not isinstance(mo, ManagedObject):
            return None

        for prop in self.__properties:
            if imccoreutils.property_exists_in_prop_map(mo, prop):
                mo.__dict__[imccoreutils.get_property_from_prop_map(mo, prop)] = \
                    self.__properties[prop]
            else:
                ImcWarning("Property %s Not Exist in MO %s" % (
                    imcgenutils.word_u(prop), class_id))

        if len(self.child):
            for ch_ in self.child:
                mo_ch = ch_.to_mo()
                mo.child_add(mo_ch)

        return mo

    def __str__(self):
        ts = 8
        if isinstance(self, GenericMo):
            out_str = "\n"
            out_str += 'GenericMo'.ljust(ts * 4) + ':' + str(
                self._class_id) + "\n"
            out_str += "-" * len("GenericMo") + "\n"
            for prop, prop_val in sorted(self.__dict__.items()):
                if prop.startswith('_'):
                    continue
                out_str += str(prop).ljust(ts * 4) + ':' + str(prop_val) + "\n"

            return out_str