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 ¤tObj: 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);
}