bool ComparisonLogic::performObjectSetTransition()

in syncd/ComparisonLogic.cpp [1165:1757]


bool ComparisonLogic::performObjectSetTransition(
        _In_ AsicView &currentView,
        _In_ AsicView &temporaryView,
        _In_ const std::shared_ptr<SaiObj> currentBestMatch,
        _In_ std::shared_ptr<SaiObj> temporaryObj,
        _In_ bool performTransition)
{
    SWSS_LOG_ENTER();

    /*
     * All parents (if any) are in final state here, we could now search for
     * best match in current view that some of the objects in temp final state
     * could been created so they should exist in current view but without
     * actual RID since all creation and RID mapping is done after all
     * matching, so it may cause problems for finding RID for compare.
     */

    /*
     * When we have best match we need to determine whether current object can
     * be updated to "this temporary" object or whether current needs to be
     * destroyed and recreated according to temporary.
     */

    std::set<sai_attr_id_t> processedAttributes;

    /*
     * Matched objects can have different attributes so we need to mark in
     * processed attributes which one were processed so if current object has
     * more attributes then we need to bring them back to default values if
     * possible.
     */

    /*
     * Depending on performTransition flag this method used in first pass will
     * determine whether current object can be updated to temporary one.  If
     * first pass was successful second pass when flag is set to true, actual
     * SET operations will be generated and current view will be modified.  No
     * actual ASIC operations will be performed, all ASIC changes will be done
     * after all object will be moved to final state.
     */

    /*
     * XXX If objects are matched (same vid/rid on object id) then this
     * function must return true, just skip all create only attributes.
     */

    for (auto &at: temporaryObj->getAllAttributes())
    {
        auto &temporaryAttr = at.second;

        SWSS_LOG_INFO("first pass (temp): attr %s", temporaryAttr->getStrAttrId().c_str());

        const auto meta = temporaryAttr->getAttrMetadata();

        const sai_attribute_t &attr = *temporaryAttr->getSaiAttr();

        processedAttributes.insert(attr.id); // mark attr id as processed

        if (currentBestMatch->hasAttr(attr.id))
        {
            /*
             * Same attribute exists on current and temp view, check if it's
             * the same.  Previously we used hasEqualAttribute method to find
             * best match but now we are looking for different attribute
             * values.
             */

            auto currentAttr = currentBestMatch->getSaiAttr(attr.id);

            SWSS_LOG_INFO("compare attr value curr %s vs temp %s",
                     currentBestMatch->getSaiAttr(attr.id)->getStrAttrValue().c_str(),
                     temporaryObj->getSaiAttr(attr.id)->getStrAttrValue().c_str());

            if (BestCandidateFinder::hasEqualAttribute(currentView, temporaryView, currentBestMatch, temporaryObj, attr.id))
            {
                /*
                 * Attributes are equal so go for next attribute
                 */

                continue;
            }

            /*
             * Now we know that attribute values are different.
             */

            /*
             * Here we don't need to check if attribute is mandatory on create
             * or conditional since attribute is present on both objects. If
             * attribute is CREATE_AND_SET that means we can update attribute
             * value on current best match object.
             */

            if (SAI_HAS_FLAG_CREATE_AND_SET(meta->flags))
            {
                SWSS_LOG_DEBUG("Attr %s can be updated from %s to %s",
                        meta->attridname,
                        currentAttr->getStrAttrValue().c_str(),
                        temporaryAttr->getStrAttrValue().c_str());

                /*
                 * Generate action and update current view in second pass
                 * and continue for next attribute.
                 */

                if (performTransition)
                {
                    setAttributeOnCurrentObject(currentView, temporaryView, currentBestMatch, temporaryAttr);
                }

                continue;
            }

            /*
             * In this place we know attribute is CREATE_ONLY and it's value is
             * different on both objects. Object must be destroyed and new
             * object must be created. In this case does not matter whether
             * attribute is mandatory on create or conditional since attribute
             * is present on both objects.
             */

            if (currentBestMatch->getObjectStatus() == SAI_OBJECT_STATUS_MATCHED)
            {
                /*
                 * This should not happen, since this mean, that attribute is
                 * create only, object is matched, and attribute value is
                 * different! DB is broken?
                 */

                SWSS_LOG_THROW("Attr %s CAN'T be updated from %s to %s on VID %s when MATCHED and CREATE_ONLY, FATAL",
                        meta->attridname,
                        currentAttr->getStrAttrValue().c_str(),
                        temporaryAttr->getStrAttrValue().c_str(),
                        temporaryObj->m_str_object_id.c_str());
            }

            SWSS_LOG_WARN("Attr %s CAN'T be updated from %s to %s since it's CREATE_ONLY",
                    meta->attridname,
                    currentAttr->getStrAttrValue().c_str(),
                    temporaryAttr->getStrAttrValue().c_str());

            /*
             * We return false since object can't be updated.  Object creation
             * is in different place when current best match is not found.
             */

            return false;
        }

        /*
         * In this case attribute exists only on temporary object.  Because of
         * different flags, and conditions this maybe not easy task to
         * determine what should happen.
         *
         * Depends on attribute order processing, we may process mandatory on
         * create conditional attribute first, before finding out that
         * condition attribute is different, but if condition would be the same
         * then this conditional attribute would be also present on current
         * best match.
         *
         * There are also default values here that come into play.  We can
         * expand this logic in the future.
         */

        bool conditional = meta->isconditional;

        /*
         * If attribute is CREATE_AND_SET and not conditional then it's
         * safe to make SET operation.
         *
         * XXX previously we had (meta->flags == SAI_ATTR_FLAGS_CREATE_AND_SET)
         * If it's not conditional current SAI_HAS_FLAG should not matter. But it
         * can also be mandatory on create but this does not matter since if
         * it's mandatory on create then current object already exists co we can
         * still perform update on this attribute because it was passed during
         * creation.
         */

        if (SAI_HAS_FLAG_CREATE_AND_SET(meta->flags) && !conditional)
        {
            SWSS_LOG_INFO("Missing current attr %s can be set to %s",
                    meta->attridname,
                    temporaryAttr->getStrAttrValue().c_str());

            /*
             * There is another case here, if this attribute exists only in
             * temporary view and it has default value, and SET value is the
             * same as default, then there is no need for ASIC operation.
             *
             * NOTE: This can lead to not put attributes with default VALUE to
             * redis database and could be confusing when debugging.
             */

            const auto defaultValueAttr = BestCandidateFinder::getSaiAttrFromDefaultValue(currentView, m_switch, *meta);

            if (defaultValueAttr != nullptr)
            {
                std::string defStr = sai_serialize_attr_value(*meta, *defaultValueAttr->getSaiAttr());

                if (defStr == temporaryAttr->getStrAttrValue())
                {
                    SWSS_LOG_NOTICE("explicit %s:%s is the same as default, no need for ASIC SET action",
                            meta->attridname, defStr.c_str());

                    continue;
                }
            }

            /*
             * Generate action and update current view in second pass
             * and continue for next attribute.
             */

            if (performTransition)
            {
                setAttributeOnCurrentObject(currentView, temporaryView, currentBestMatch, temporaryAttr);
            }

            continue;
        }

        if (currentBestMatch->getObjectStatus() == SAI_OBJECT_STATUS_MATCHED)
        {
            if (SAI_HAS_FLAG_CREATE_ONLY(meta->flags))
            {
                /*
                 * Attribute is create only attribute on matched object. This
                 * can happen when we are have create only attributes in asic
                 * view, those attributes were put by snoop logic. Since we
                 * skipping only read-only attributes then we snoop create-only
                 * also, but on "existing" objects this will cause problem and
                 * during apply logic we need to skip this attribute since we
                 * won't be able to SET it anyway on matched object, and value
                 * is the same as current object.
                 */

                SWSS_LOG_INFO("Skipping create only attr on matched object: %s:%s",
                        meta->attridname,
                        temporaryAttr->getStrAttrValue().c_str());

                continue;
            }
        }

        /*
         * This is the most interesting case, we currently leave it here and we
         * will support it later. Some other cases here also can be considered
         * as success, for example if default value is the same as current
         * value.
         */

        SWSS_LOG_WARN("Missing current attr %s (conditional: %d) CAN'T be set to %s, flags: 0x%x, FIXME",
                meta->attridname,
                conditional,
                temporaryAttr->getStrAttrValue().c_str(),
                meta->flags);

        /*
         * We can't continue with update in that case, so return false.
         */

        return false;
    }

    const bool beginTempSizeZero = temporaryObj->getAllAttributes().size() == 0;

    /*
     * Current best match can have more attributes than temporary object.
     * let see if we can bring them to default value if possible.
     */

    for (auto &ac: currentBestMatch->getAllAttributes())
    {
        auto &currentAttr = ac.second;

        const auto &meta = currentAttr->getAttrMetadata();

        const sai_attribute_t &attr = *currentAttr->getSaiAttr();

        if (processedAttributes.find(attr.id) != processedAttributes.end())
        {
            /*
             * This attribute was processed in previous temporary attributes processing so skip it here.
             */

            continue;
        }

        SWSS_LOG_INFO("first pass (curr): attr %s", currentAttr->getStrAttrId().c_str());

        /*
         * Check if we are bringing one of the default created objects to
         * default value, since for that we will need dependency TREE.  Most of
         * the time user should just query configuration and just set new
         * values based on get results, so this will not be needed.
         *
         * And even if we will have dependency tree, those values may not be
         * synced because of remove etc, so we will need to check if default
         * values actually exists.
         */

        if (currentBestMatch->isOidObject())
        {
            sai_object_id_t vid = currentBestMatch->getVid();

            /*
             * Current best match may be created, check if vid/rid exist.
             */

            if (currentView.hasVid(vid))
            {
                sai_object_id_t rid = currentView.m_vidToRid.at(vid);

                if (m_switch->isDiscoveredRid(rid))
                {
                    SWSS_LOG_INFO("performing default on existing object VID %s: %s: %s, we need default dependency TREE, FIXME",
                            sai_serialize_object_id(vid).c_str(),
                            meta->attridname,
                            currentAttr->getStrAttrValue().c_str());
                }
            }
        }

        /*
         * We should not have MANDATORY_ON_CREATE attributes here since all
         * mandatory on create (even conditional) should be present in in
         * previous loop and they are matching, so we should get here
         * CREATE_ONLY or CREATE_AND_SET attributes only. So we should not get
         * conditional attributes here also, but lets take extra care about that
         * just as sanity check.
         */

        bool conditional = meta->isconditional;

        if (conditional || SAI_HAS_FLAG_MANDATORY_ON_CREATE(meta->flags))
        {
            if (currentBestMatch->getObjectStatus() == SAI_OBJECT_STATUS_MATCHED &&
                    SAI_HAS_FLAG_CREATE_AND_SET(meta->flags))
            {
                if (meta->objecttype == SAI_OBJECT_TYPE_PORT &&
                        meta->attrid == SAI_PORT_ATTR_SPEED)
                {
                    /*
                     * NOTE: for SPEED we could query each port at start and
                     * save it's speed to recover here, or even we could query
                     * each attribute on existing object during discovery
                     * process.
                     */

                    SWSS_LOG_WARN("No previous value specified on %s (VID), can't bring to default, leaving attr unchanged: %s:%s",
                            sai_serialize_object_id(currentBestMatch->getVid()).c_str(),
                            meta->attridname,
                            currentAttr->getStrAttrValue().c_str());

                    continue;
                }

                if (meta->objecttype == SAI_OBJECT_TYPE_SCHEDULER_GROUP &&
                        meta->attrid == SAI_SCHEDULER_GROUP_ATTR_SCHEDULER_PROFILE_ID)
                {
                    /*
                     * This attribute can hold reference to user created
                     * objects which maybe required to be destroyed, that's why
                     * we need to bring real value. What if real value were
                     * removed?
                     */

                    sai_object_id_t def = SAI_NULL_OBJECT_ID;

                    sai_object_id_t vid = currentBestMatch->getVid();

                    if (currentView.hasVid(vid))
                    {
                        // scheduler_group RID
                        sai_object_id_t rid = currentView.m_vidToRid.at(vid);

                        rid = m_switch->getDefaultValueForOidAttr(rid, SAI_SCHEDULER_GROUP_ATTR_SCHEDULER_PROFILE_ID);

                        if (rid != SAI_NULL_OBJECT_ID && currentView.hasRid(rid))
                        {
                            /*
                             * We found default value
                             */

                            SWSS_LOG_DEBUG("found default rid %s, vid %s for %s",
                                    sai_serialize_object_id(rid).c_str(),
                                    sai_serialize_object_id(vid).c_str(),
                                    meta->attridname);

                            def = currentView.m_ridToVid.at(rid);
                        }
                    }

                    sai_attribute_t defattr;

                    defattr.id = meta->attrid;
                    defattr.value.oid = def;

                    std::string str_attr_value = sai_serialize_attr_value(*meta, defattr, false);

                    auto defaultValueAttr = std::make_shared<SaiAttr>(meta->attridname, str_attr_value);

                    if (performTransition)
                    {
                        setAttributeOnCurrentObject(currentView, temporaryView, currentBestMatch, defaultValueAttr);
                    }

                    continue;
                }

                // current best match is MATCHED

                //auto vid = currentBestMatch->getVid();

                // TODO don't transfer oid attributes, we don't know how to handle this yet
                // If OA did GET on any attributes, snoop in syncd should catch that and write
                // to database so we would have some attributes here.

                if (beginTempSizeZero && !meta->isoidattribute)
                {
                    SWSS_LOG_WARN("current attr is MoC|CaS and object is MATCHED: %s transferring %s:%s to temp object (was empty)",
                            currentBestMatch->m_str_object_id.c_str(),
                            meta->attridname,
                            currentAttr->getStrAttrValue().c_str());

                    std::shared_ptr<SaiAttr> transferedAttr = std::make_shared<SaiAttr>(
                            currentAttr->getStrAttrId(),
                            currentAttr->getStrAttrValue());

                    temporaryObj->setAttr(transferedAttr);

                    continue;
                }

                // SAI_QUEUE_ATTR_PARENT_SCHEDULER_NODE
                // SAI_SCHEDULER_GROUP_ATTR_SCHEDULER_PROFILE_ID*
                // SAI_SCHEDULER_GROUP_ATTR_PARENT_NODE
                // SAI_BRIDGE_PORT_ATTR_BRIDGE_ID
                //
                // TODO matched by ID (MATCHED state) should always be updatable
                // except those 4 above (at least for those above since they can have
                // default value present after switch creation

                // TODO SAI_SCHEDULER_GROUP_ATTR_SCHEDULER_PROFILE_ID is mandatory on create but also SET
                // if attribute is set we and object is in MATCHED state then that means we are able to
                // bring this attribute to default state not for all attributes!
                // *SAI_SCHEDULER_GROUP_ATTR_SCHEDULER_PROFILE_ID - is not any more mandatory on create, so default should be NULL

                SWSS_LOG_ERROR("current attribute is mandatory on create, crate and set, and object MATCHED, FIXME %s %s:%s",
                        currentBestMatch->m_str_object_id.c_str(),
                        meta->attridname,
                        currentAttr->getStrAttrValue().c_str());

                return false;
            }

            if (currentBestMatch->getObjectStatus() == SAI_OBJECT_STATUS_MATCHED)
            {
                if (SAI_HAS_FLAG_CREATE_ONLY(meta->flags))
                {
                    /*
                     * Attribute is create only attribute on matched object. This
                     * can happen when we are have create only attributes in asic
                     * view, those attributes were put by snoop logic. Since we
                     * skipping only read-only attributes then we snoop create-only
                     * also, but on "existing" objects this will cause problem and
                     * during apply logic we need to skip this attribute since we
                     * won't be able to SET it anyway on matched object, and value
                     * is the same as current object.
                     */

                    SWSS_LOG_INFO("Skipping create only attr on matched object: %s:%s",
                            meta->attridname,
                            currentAttr->getStrAttrValue().c_str());

                    // don't produce too much noise for queues
                    if (currentAttr->getStrAttrId() != "SAI_QUEUE_ATTR_TYPE")
                    {
                        SWSS_LOG_WARN("current attr is CREATE_ONLY and object is MATCHED: %s transferring %s:%s to temp object",
                                currentBestMatch->m_str_object_id.c_str(),
                                meta->attridname,
                                currentAttr->getStrAttrValue().c_str());
                    }

                    std::shared_ptr<SaiAttr> transferedAttr = std::make_shared<SaiAttr>(
                            currentAttr->getStrAttrId(),
                            currentAttr->getStrAttrValue());

                    temporaryObj->setAttr(transferedAttr);

                    continue;
                }
            }

            SWSS_LOG_ERROR("Present current attr %s:%s is conditional or MANDATORY_ON_CREATE, we don't expect this here, FIXME",
                    meta->attridname,
                    currentAttr->getStrAttrValue().c_str());

            /*
             * We don't expect conditional or mandatory on create attributes,
             * since in previous loop all attributes were matching they also
             * had to match.  If we hit this case we need to take a closer look
             * since it will mean we have a bug somewhere.
             */

            return false;
        }

        /*
         * If attribute is CREATE_AND_SET or CREATE_ONLY, they may have a
         * default value, for create and set we maybe able to set it and for
         * create only we just need to make sure its expected value, if not
         * then it can't be updated and we need to return false.
         *
         * TODO Currently we will support limited default value types.
         *
         * Later this comparison of default value we need to extract to
         * separate functions. Maybe create SaiAttr from default value or
         * nullptr if it's not supported yet.
         */

        if (meta->flags == SAI_ATTR_FLAGS_CREATE_AND_SET || meta->flags == SAI_ATTR_FLAGS_CREATE_ONLY)
        {
            // TODO default value for existing objects needs dependency tree

            const auto defaultValueAttr = BestCandidateFinder::getSaiAttrFromDefaultValue(currentView, m_switch, *meta);

            if (defaultValueAttr == nullptr)
            {
                SWSS_LOG_WARN("Can't get default value for present current attr %s:%s, FIXME",
                        meta->attridname,
                        currentAttr->getStrAttrValue().c_str());

                /*
                 * If we can't get default value then we can't do set, because
                 * we don't know with what, so we need to destroy current
                 * object and recreate new one from temporary.
                 */

                return false;
            }

            if (currentAttr->getStrAttrValue() == defaultValueAttr->getStrAttrValue())
            {
                SWSS_LOG_INFO("Present current attr %s value %s is the same as default value, no action needed",
                    meta->attridname,
                    currentAttr->getStrAttrValue().c_str());

                continue;
            }

            if (meta->flags == SAI_ATTR_FLAGS_CREATE_ONLY)
            {
                SWSS_LOG_WARN("Present current attr %s:%s has default that CAN'T be set to %s since it's CREATE_ONLY",
                        meta->attridname,
                        currentAttr->getStrAttrValue().c_str(),
                        defaultValueAttr->getStrAttrValue().c_str());

                return false;
            }

            SWSS_LOG_INFO("Present current attr %s:%s has default that can be set to %s",
                    meta->attridname,
                    currentAttr->getStrAttrValue().c_str(),
                    defaultValueAttr->getStrAttrValue().c_str());

            /*
             * Generate action and update current view in second pass
             * and continue for next attribute.
             */

            if (performTransition)
            {
                setAttributeOnCurrentObject(currentView, temporaryView, currentBestMatch, defaultValueAttr);
            }

            continue;
        }

        SWSS_LOG_THROW("we should not get here, we have a bug, current present attribute %s:%s has some wrong flags 0x%x",
                    meta->attridname,
                    currentAttr->getStrAttrValue().c_str(),
                    meta->flags);
    }

    /*
     * All attributes were processed, and ether no changes are required or all
     * changes can be performed or some missing attributes has exact value as
     * default value.
     */

    return true;
}