std::shared_ptr BestCandidateFinder::findCurrentBestMatchForGenericObject()

in syncd/BestCandidateFinder.cpp [1609:2000]


std::shared_ptr<SaiObj> BestCandidateFinder::findCurrentBestMatchForGenericObject(
        _In_ const std::shared_ptr<const SaiObj> &temporaryObj)
{
    SWSS_LOG_ENTER();

    /*
     * This method will try to find current best match object for a given
     * temporary object. This method should be used only on object id objects,
     * since non object id structures also contains object id which in this
     * case are not take into account. Besides for objects like FDB, ROUTE or
     * NEIGHBOR we can do quick hash lookup instead of looping for all objects
     * where there can be a lot of them.
     *
     * Special case here we can add later on is VLAN, since we can have a lot
     * of VLANS so instead looking via all of them we just need to make sure
     * that we will create reverse map via VLAN_ID KEY and then we can make
     * hash lookup to see if such vlan is present.
     */

    /*
     * Since our system design is to restart orchagent without restarting syncd
     * and recreating objects and reassign new VIDs created inside orchagent,
     * in our most cases values of objects will not change.  This will cause to
     * make our comparison logic here fairly simple:
     *
     * Find all objects that have the same equal attributes on current object
     * and choose the one with the most attributes that match current and
     * temporary object.
     *
     * This seems simple, but there are a lot of cases that needs to be taken
     * into account:
     *
     * - what if we have several objects with the same number of equal
     *   attributes then we can choose at random or implement some heuristic
     *   logic to try figure out which of those objects will be the best, even
     *   then if we choose wrong object, then there can be a lot of removes and
     *   recreating objects on the ASIC
     *
     * - what if in temporary object CREATE_ONLY attributes don't match but
     *   many of CREATE_AND_SET are the same, in that case we can choose object
     *   with most matching attributes, but object will still needs to be
     *   destroyed because we need to set new CREATE_ONLY attributes
     *
     * - there are also cases with default values of attributes where attribute
     *   is present only in one object but on other one it have default value
     *   and this default value is the same as the one in attribute
     *
     * - another case is for objects that needs to be removed since there are
     *   no corresponding objects in temporary view, but they can't be removed,
     *   objects like PORT or default QUEUEs or INGRESS_PRIORITY_GROUPs, then
     *   we need to bring their current set values to default ones, which also
     *   can be challenging since we need to know previous default value and it
     *   could be assigned by switch internally, like default MAC address or
     *   default TRAP group etc
     *
     * - there is also interesting case with KEYs attributes which when
     *   doing remove/create they needs to be removed first since
     *   we can't have 2 identical cases
     *
     * There are a lot of aspects to consider here, in here we will cake only
     * couple of them in consideration, and other will be taken care inside
     * processObjectForViewTransition method which will handle all other cases
     * not mentioned here.
     */

    /*
     * First check if object is oid object, if yes, check if it status is
     * matched.
     */

    if (!temporaryObj->isOidObject())
    {
        SWSS_LOG_THROW("non object id %s is used in generic method, please implement special case, FIXME",
                temporaryObj->m_str_object_type.c_str());
    }

    /*
     * Get not processed objects of temporary object type, and all attributes
     * that are set on that object. This function should be used only on oid
     * object ids, since for non object id finding best match is based on
     * struct entry of object id.
     */

    sai_object_type_t object_type = temporaryObj->getObjectType();

    const auto notProcessedObjects = m_currentView.getNotProcessedObjectsByObjectType(object_type);

    const auto attrs = temporaryObj->getAllAttributes();

    /*
     * Complexity here is O((n^2)*m) since we iterate via all not processed
     * objects, then we iterate through all present attributes.  N is squared
     * since for given object type we iterate via entire list for each object,
     * this can be optimized a little bit inside AsicView class.
     */

    SWSS_LOG_INFO("not processed objects for %s: %zu, attrs: %zu",
            temporaryObj->m_str_object_type.c_str(),
            notProcessedObjects.size(),
            attrs.size());

    std::vector<sai_object_compare_info_t> candidateObjects;

    for (const auto &currentObj: notProcessedObjects)
    {
        SWSS_LOG_INFO("* examing current obj: %s", currentObj->m_str_object_id.c_str());

        sai_object_compare_info_t soci = { 0, currentObj };

        bool has_different_create_only_attr = false;

        /*
         * NOTE: we only iterate by attributes that are present in temporary
         * view. It may happen that current view has some additional attributes
         * set that are create only and value can't be updated then, so in that
         * case such object must be disqualified from being candidate.
         */

        for (const auto &attr: attrs)
        {
            sai_attr_id_t attrId = attr.first;

            /*
             * Function hasEqualAttribute check if attribute exists on both objects.
             */

            if (hasEqualAttribute(m_currentView, m_temporaryView, currentObj, temporaryObj, attrId))
            {
                soci.equal_attributes++;

                SWSS_LOG_INFO("ob equal %s %s, %s: %s",
                        temporaryObj->m_str_object_id.c_str(),
                        currentObj->m_str_object_id.c_str(),
                        attr.second->getStrAttrId().c_str(),
                        attr.second->getStrAttrValue().c_str());
            }
            else
            {
                SWSS_LOG_INFO("ob not equal %s %s, %s: %s",
                        temporaryObj->m_str_object_id.c_str(),
                        currentObj->m_str_object_id.c_str(),
                        attr.second->getStrAttrId().c_str(),
                        attr.second->getStrAttrValue().c_str());

                /*
                 * Function hasEqualAttribute returns true only when both
                 * attributes are existing and both are equal, so here it
                 * returned false, so it may mean 2 things:
                 *
                 * - attribute doesn't exist in current view, or
                 * - attributes are different
                 *
                 * If we check if attribute also exists in current view and has
                 * CREATE_ONLY flag then attributes are different and we
                 * disqualify this object since new temporary object needs to
                 * pass new different attribute with CREATE_ONLY flag.
                 *
                 * Case when attribute doesn't exist is much more complicated
                 * since it maybe conditional and have default value, we will
                 * do that check when we select best match.
                 */

                /*
                 * Get attribute metadata to see if contains CREATE_ONLY flag.
                 */

                const sai_attr_metadata_t* meta = attr.second->getAttrMetadata();

                if (SAI_HAS_FLAG_CREATE_ONLY(meta->flags) && currentObj->hasAttr(attrId))
                {
                    has_different_create_only_attr = true;

                    SWSS_LOG_INFO("obj has not equal create only attributes %s",
                            temporaryObj->m_str_object_id.c_str());

                    /*
                     * In this case there is no need to compare other
                     * attributes since we won't be able to update them anyway.
                     */

                    break;
                }

                if (SAI_HAS_FLAG_CREATE_ONLY(meta->flags) && !currentObj->hasAttr(attrId))
                {
                    /*
                     * This attribute exists only on temporary view and it's
                     * create only.  If it has default value, check if it's the
                     * same as current.
                     */

                    auto curDefault = getSaiAttrFromDefaultValue(m_currentView, m_switch, *meta);

                    if (curDefault != nullptr)
                    {
                        if (curDefault->getStrAttrValue() != attr.second->getStrAttrValue())
                        {
                            has_different_create_only_attr = true;

                            SWSS_LOG_INFO("obj has not equal create only attributes %s (default): %s",
                                    temporaryObj->m_str_object_id.c_str(),
                                    meta->attridname);
                            break;
                        }
                        else
                        {
                            SWSS_LOG_INFO("obj has equal create only value %s (default): %s",
                                    temporaryObj->m_str_object_id.c_str(),
                                    meta->attridname);
                        }
                    }
                }
            }
        }

        /*
         * Before we add this object as candidate, see if there are some create
         * only attributes which are not present in temporary object but
         * present in current, and if there is default value that is the same.
         */

        const auto curAttrs = currentObj->getAllAttributes();

        for (auto curAttr: curAttrs)
        {
            if (attrs.find(curAttr.first) != attrs.end())
            {
                // attr exists in both objects.
                continue;
            }

            const sai_attr_metadata_t* meta = curAttr.second->getAttrMetadata();

            if (SAI_HAS_FLAG_CREATE_ONLY(meta->flags) && !temporaryObj->hasAttr(curAttr.first))
            {
                /*
                 * This attribute exists only on current view and it's
                 * create only.  If it has default value, check if it's the
                 * same as current.
                 */

                auto tmpDefault = getSaiAttrFromDefaultValue(m_temporaryView, m_switch, *meta);

                if (tmpDefault != nullptr)
                {
                    if (tmpDefault->getStrAttrValue() != curAttr.second->getStrAttrValue())
                    {
                        has_different_create_only_attr = true;

                        SWSS_LOG_INFO("obj has not equal create only attributes %s (default): %s",
                                currentObj->m_str_object_id.c_str(),
                                meta->attridname);
                        break;
                    }
                    else
                    {
                        SWSS_LOG_INFO("obj has equal create only value %s (default): %s",
                                temporaryObj->m_str_object_id.c_str(),
                                meta->attridname);
                    }
                }
            }
        }

        if (has_different_create_only_attr)
        {
            /*
             * Those objects differs with attribute which is marked as
             * CREATE_ONLY so we will not be able to update current if
             * necessary using SET operations.
             */

            continue;
        }

        SWSS_LOG_INFO("* current obj: %s has equal %lu attributes",
                currentObj->m_str_object_id.c_str(),
                soci.equal_attributes);

        candidateObjects.push_back(soci);
    }

    SWSS_LOG_INFO("number candidate objects for %s is %zu",
            temporaryObj->m_str_object_id.c_str(),
            candidateObjects.size());

    if (candidateObjects.size() == 0)
    {
        /*
         * We didn't found any object.
         */

        return nullptr;
    }

    if (candidateObjects.size() == 1)
    {
        /*
         * We found only one object so it must be it.
         */

        return candidateObjects.begin()->obj;
    }

    auto labelCandidate = findCurrentBestMatchForGenericObjectUsingLabel(
            temporaryObj,
            candidateObjects);

    if (labelCandidate != nullptr)
        return labelCandidate;

    /*
     * If we have more than 1 object matched actually more preferred
     * object would be the object with most CREATE_ONLY attributes matching
     * since that will reduce risk of removing and recreating that object in
     * current view.
     */

    /*
     * But at this point also let's try find best candidate using graph paths,
     * since if some attributes are mismatched (like for example more ACLs are
     * created) this can lead to choose wrong LAG and have implications on
     * router interface and so on.  So matching by graph path here could be
     * more precise.
     */

    auto graphCandidate = findCurrentBestMatchForGenericObjectUsingGraph(
            temporaryObj,
            candidateObjects);

    if (graphCandidate != nullptr)
        return graphCandidate;

    /*
     * Sort candidate objects by equal attributes in descending order, we know
     * here that we have at least 2 candidates.
     *
     * NOTE: maybe at this point we should be using heuristics?
     */

    std::sort(candidateObjects.begin(), candidateObjects.end(), compareByEqualAttributes);

    if (candidateObjects.at(0).equal_attributes > candidateObjects.at(1).equal_attributes)
    {
        /*
         * We have only 1 object with the greatest number of equal attributes
         * lets choose that object as our best match.
         */

        SWSS_LOG_INFO("eq attributes: %ld vs %ld",
            candidateObjects.at(0).equal_attributes,
            candidateObjects.at(1).equal_attributes);

        return candidateObjects.begin()->obj;
    }

    /*
     * In here there are at least 2 current objects that have the same
     * number of equal attributes. In here we can do two things
     *
     * - select object at random, or
     * - use heuristic/smart lookup for inside graph
     *
     * Smart lookup would be for example searching whether current object is
     * pointing to the same PORT as temporary object (since ports are matched
     * at the beginning). For different types of objects we need different type
     * of logic and we can start adding that when needed and when missing we
     * will just choose at random possible causing some remove/recreate but
     * this logic is not perfect at this point.
     */

    /*
     * Lets also remove candidates with less equal attributes
     */

    size_t previousCandidates = candidateObjects.size();

    size_t equalAttributes = candidateObjects.at(0).equal_attributes;

    auto endIt = std::remove_if(candidateObjects.begin(), candidateObjects.end(),
            [equalAttributes](const sai_object_compare_info_t &candidate)
            { return candidate.equal_attributes != equalAttributes; });

    candidateObjects.erase(endIt, candidateObjects.end());

    SWSS_LOG_INFO("multiple candidates found (%zu of %zu) for %s, will use heuristic",
            candidateObjects.size(),
            previousCandidates,
            temporaryObj->m_str_object_id.c_str());

    return findCurrentBestMatchForGenericObjectUsingHeuristic(temporaryObj, candidateObjects);
}