syncd/ComparisonLogic.cpp (1,905 lines of code) (raw):
#include "ComparisonLogic.h"
#include "VidManager.h"
#include "BestCandidateFinder.h"
#include "NotificationHandler.h"
#include "VirtualOidTranslator.h"
#include "CommandLineOptions.h"
#include "Workaround.h"
#include "swss/logger.h"
#include "meta/sai_serialize.h"
#include "meta/SaiAttributeList.h"
#include <inttypes.h>
using namespace syncd;
using namespace saimeta;
/*
* NOTE: All methods taking current and temporary view could be moved to
* transition class etc to just use class members instead of passing those
* parameters to every function.
*/
ComparisonLogic::ComparisonLogic(
_In_ std::shared_ptr<sairedis::SaiInterface> vendorSai,
_In_ std::shared_ptr<SaiSwitchInterface> sw,
_In_ std::shared_ptr<NotificationHandler> handler,
_In_ std::set<sai_object_id_t> initViewRemovedVids,
_In_ std::shared_ptr<AsicView> current,
_In_ std::shared_ptr<AsicView> temp,
_In_ std::shared_ptr<BreakConfig> breakConfig):
m_vendorSai(vendorSai),
m_switch(sw),
m_initViewRemovedVids(initViewRemovedVids),
m_current(current),
m_temp(temp),
m_handler(handler),
m_breakConfig(breakConfig)
{
SWSS_LOG_ENTER();
m_enableRefernceCountLogs = false;
// will inside filter only RID/VID to this particular switch
// TODO move outside switch ? since later could be in different ASIC_DB
m_current->m_ridToVid = m_switch->getRidToVidMap();
m_current->m_vidToRid = m_switch->getVidToRidMap();
/*
* Those calls could be calls to SAI, but when this will be separate lib
* then we would like to limit sai to minimum or reimplement getting those.
*
* TODO: This needs to be refactored and solved in other way since asic
* view can contain multiple switches.
*
* TODO: This also can be optimized using metadata.
* We need to add access to SaiSwitch in AsicView.
*/
// TODO needs to be removed and done in generic
m_current->m_defaultTrapGroupRid = m_switch->getSwitchDefaultAttrOid(SAI_SWITCH_ATTR_DEFAULT_TRAP_GROUP);
m_temp->m_defaultTrapGroupRid = m_switch->getSwitchDefaultAttrOid(SAI_SWITCH_ATTR_DEFAULT_TRAP_GROUP);
auto seed = (unsigned int)std::time(0);
SWSS_LOG_NOTICE("srand seed for switch %s: %u", sai_serialize_object_id(m_switch->getVid()).c_str(), seed);
std::srand(seed);
}
ComparisonLogic::~ComparisonLogic()
{
SWSS_LOG_ENTER();
// empty
}
void ComparisonLogic::compareViews()
{
SWSS_LOG_ENTER();
AsicView& current = *m_current;
AsicView& temp = *m_temp;
/*
* Match oids before calling populate existing objects since after
* matching oids RID and VID maps will be populated.
*/
matchOids(current, temp);
/*
* Populate existing objects to current and temp view if they don't
* exist since we are populating them when syncd starts, and when we
* switch view we don't want to loose any of those objects since during
* syncd runtime is counting on that those objects exists.
*
* TODO: If some object's will be removed like VLAN members then this
* existing objects needs to be updated in the switch!
*/
populateExistingObjects(current, temp);
checkInternalObjects(current, temp);
/*
* Call main method!
*/
if (m_enableRefernceCountLogs)
{
current.dumpRef("current START");
temp.dumpRef("temp START");
}
createPreMatchMap(current, temp);
logViewObjectCount(current, temp);
applyViewTransition(current, temp);
transferNotProcessed(current, temp);
// TODO have a method to check for not processed objects
// and maybe add them to list on processing attributes
// and move note processed objects to temporary view as well
// we need to check oid attributes as well
SWSS_LOG_NOTICE("ASIC operations to execute: %zu", current.asicGetOperationsCount());
temp.checkObjectsStatus();
SWSS_LOG_NOTICE("all temporary view objects were processed to FINAL state");
current.checkObjectsStatus();
SWSS_LOG_NOTICE("all current view objects were processed to FINAL state");
/*
* After all operations both views should look the same so number of
* rid/vid should look the same.
*/
checkMap(current, temp);
/*
* At the end number of m_soAll objects must be equal on both views. If
* some on temporary views are missing, we need to transport empty
* objects to temporary view, like queues, scheduler groups, virtual
* router, trap groups etc.
*/
if (current.m_soAll.size() != temp.m_soAll.size())
{
/*
* If this will happen that means non object id values are
* different since number of RID/VID maps is identical (previous
* check).
*
* Unlikely to be routes/neighbors/fdbs, can be traps, switch,
* vlan.
*
* TODO: For debug we will need to display differences
*/
SWSS_LOG_THROW("wrong number of all objects current: %zu vs temp %zu, FIXME",
current.m_soAll.size(),
temp.m_soAll.size());
}
}
/**
* @brief Match OIDs on temporary and current view using VIDs.
*
* For all OID objects in temporary view we match all VID's in the current
* view, some objects will have the same VID's in both views (eg. ports), in
* that case their RID's also are the same, so in that case we mark both
* objects in both views as "MATCHED" which will speed up search comparison
* logic for those type of objects.
*
* @param currentView Current view.
* @param temporaryView Temporary view.
*/
void ComparisonLogic::matchOids(
_In_ AsicView ¤tView,
_In_ AsicView &temporaryView)
{
SWSS_LOG_ENTER();
auto coldBootDiscoveredVids = m_switch->getColdBootDiscoveredVids();
for (const auto &temporaryIt: temporaryView.m_oOids)
{
sai_object_id_t temporaryVid = temporaryIt.first;
const auto ¤tIt = currentView.m_oOids.find(temporaryVid);
if (currentIt == currentView.m_oOids.end())
{
continue;
}
sai_object_id_t vid = temporaryVid;
sai_object_id_t rid = currentView.m_vidToRid.at(vid);
// save VID and RID in temporary view
temporaryView.m_ridToVid[rid] = vid;
temporaryView.m_vidToRid[vid] = rid;
// set both objects status as matched
temporaryIt.second->setObjectStatus(SAI_OBJECT_STATUS_MATCHED);
currentIt->second->setObjectStatus(SAI_OBJECT_STATUS_MATCHED);
SWSS_LOG_INFO("matched %s RID %s VID %s",
currentIt->second->m_str_object_type.c_str(),
sai_serialize_object_id(rid).c_str(),
sai_serialize_object_id(vid).c_str());
if (coldBootDiscoveredVids.find(vid) == coldBootDiscoveredVids.end())
{
auto ot = currentIt->second->getObjectType();
switch (ot)
{
case SAI_OBJECT_TYPE_INGRESS_PRIORITY_GROUP:
case SAI_OBJECT_TYPE_SCHEDULER_GROUP:
case SAI_OBJECT_TYPE_QUEUE:
case SAI_OBJECT_TYPE_PORT:
break;
default:
// Should we also add check if also not in removed?
// This is for case of only GET:
// 1. cold boot:
// - OA assigns buffer profile to Queue
// 2. warm boot:
// - OA do GET on Queue and got previous buffer profile
// - OA assigns new buffer profile on Queue
//
// Question: what should happen to old buffer profile? should it be removed?
// No, since OA still hold's the reference to that VID and may use it later.
SWSS_LOG_INFO("matched %s VID %s was not in cold boot, possible only GET?",
sai_serialize_object_id(vid).c_str(),
currentIt->second->m_str_object_type.c_str());
break;
}
}
}
SWSS_LOG_NOTICE("matched oids");
}
/**
* @brief Check internal objects.
*
* During warm boot, when switch restarted we expect that switch will have the
* same number of objects like before boot, and all vendor OIDs (RIDs) will be
* exactly the same as before reboot.
*
* If OIDs are not the same, then this is a vendor bug.
*
* Exception of this rule is when orchagent will be restarted and it will add
* some more objects or remove some objects.
*
* We take special care here about INGRESS_PRIORITY_GROUPS, SCHEDULER_GROUPS
* and QUEUES, since those are internal objects created by vendor when switch
* is instantiated.
*
* @param currentView Current view.
* @param temporaryView Temporary view.
*/
void ComparisonLogic::checkInternalObjects(
_In_ const AsicView &cv,
_In_ const AsicView &tv)
{
SWSS_LOG_ENTER();
SWSS_LOG_NOTICE("check internal objects");
std::vector<sai_object_type_t> ots =
{
SAI_OBJECT_TYPE_QUEUE,
SAI_OBJECT_TYPE_SCHEDULER_GROUP,
SAI_OBJECT_TYPE_INGRESS_PRIORITY_GROUP
};
for (auto ot: ots)
{
auto cot = cv.getObjectsByObjectType(ot);
auto tot = tv.getObjectsByObjectType(ot);
auto sot = sai_serialize_object_type(ot);
if (cot.size() != tot.size())
{
SWSS_LOG_WARN("different number of objects %s, curr: %zu, tmp %zu (not expected if warm boot)",
sot.c_str(),
cot.size(),
tot.size());
}
for (auto o: cot)
if (o->getObjectStatus() != SAI_OBJECT_STATUS_MATCHED)
SWSS_LOG_ERROR("object status is not MATCHED on curr: %s:%s",
sot.c_str(), o->m_str_object_id.c_str());
for (auto o: tot)
if (o->getObjectStatus() != SAI_OBJECT_STATUS_MATCHED)
SWSS_LOG_ERROR("object status is not MATCHED on temp: %s:%s",
sot.c_str(), o->m_str_object_id.c_str());
}
}
void ComparisonLogic::checkMatchedPorts(
_In_ const AsicView &temporaryView)
{
SWSS_LOG_ENTER();
/*
* We should check if all PORT's and SWITCH are matched since this is our
* starting point for comparison logic.
*/
auto ports = temporaryView.getObjectsByObjectType(SAI_OBJECT_TYPE_PORT);
for (const auto &p: ports)
{
if (p->getObjectStatus() == SAI_OBJECT_STATUS_MATCHED)
{
continue;
}
/*
* If that happens then:
*
* - we have a bug in our matching port logic, or
* - we need to remap ports VID/RID after syncd restart, or
* - after changing switch profile we have different number of ports
*
* In any of those cased this is FATAL and needs to be addressed,
* currently we don't expect port OIDs to change, there is check on
* syncd start for that, and our goal is that each time we will load
* the same profile for switch so different number of ports should be
* ruled out.
*/
SWSS_LOG_THROW("port %s object status is not MATCHED (%d)", p->m_str_object_id.c_str(), p->getObjectStatus());
}
SWSS_LOG_NOTICE("all ports are matched");
}
/**
* @brief Process object attributes for view transition.
*
* Since each object can contain attributes (or non id struct object can
* contain oids inside struct) they need to be processed first recursively and
* they need to be in FINAL state before processing temporary view further.
*
* This processing may result in changes in current view like removing and
* recreating or adding new objects depends on transitions.
*
* @param currentView Current view.
* @param temporaryView Temporary view.
* @param temporaryObj Temporary object.
*/
void ComparisonLogic::procesObjectAttributesForViewTransition(
_In_ AsicView ¤tView,
_In_ AsicView &temporaryView,
_In_ const std::shared_ptr<SaiObj> &temporaryObj)
{
SWSS_LOG_ENTER();
SWSS_LOG_INFO("%s %s", temporaryObj->m_str_object_type.c_str(), temporaryObj->m_str_object_id.c_str());
/*
* First we need to make sure if all attributes of this temporary object
* are in FINAL or MATCHED state, then we can process this object and find
* best match in current asic view.
*/
for (auto &at: temporaryObj->getAllAttributes())
{
auto &attribute = at.second;
SWSS_LOG_INFO("attr %s", attribute->getStrAttrId().c_str());
/*
* For each object id in attributes go recursively to match those
* objects.
*/
for (auto vid: attribute->getOidListFromAttribute())
{
if (vid == SAI_NULL_OBJECT_ID)
{
continue;
}
SWSS_LOG_INFO("- processing attr VID %s", sai_serialize_object_id(vid).c_str());
auto tempParent = temporaryView.m_oOids.at(vid);
processObjectForViewTransition(currentView, temporaryView, tempParent); // recursion
/*
* Temporary object here is never changed, even if we do recursion
* here all that could been removed are objects in current view
* tree so we don't need to worry about any temporary object
* removal.
*/
}
}
if (temporaryObj->isOidObject())
{
return;
}
/*
* For non object id types like NEIGHBOR or ROUTE they have object id
* inside object struct entry, they also need to be processed, in SAI 1.0
* this can be automated since we have metadata for those structs.
*/
for (size_t j = 0; j < temporaryObj->m_info->structmemberscount; ++j)
{
const sai_struct_member_info_t *m = temporaryObj->m_info->structmembers[j];
if (m->membervaluetype != SAI_ATTR_VALUE_TYPE_OBJECT_ID)
{
continue;
}
sai_object_id_t vid = m->getoid(&temporaryObj->m_meta_key);
SWSS_LOG_INFO("- processing %s (%s) VID %s",
temporaryObj->m_str_object_type.c_str(),
m->membername,
sai_serialize_object_id(vid).c_str());
auto structObject = temporaryView.m_oOids.at(vid);
processObjectForViewTransition(currentView, temporaryView, structObject); // recursion
}
}
void ComparisonLogic::bringNonRemovableObjectToDefaultState(
_In_ AsicView ¤tView,
_In_ const std::shared_ptr<SaiObj> ¤tObj)
{
SWSS_LOG_ENTER();
for (const auto &it: currentObj->getAllAttributes())
{
const auto &attr = it.second;
const auto &meta = attr->getAttrMetadata();
if (!SAI_HAS_FLAG_CREATE_AND_SET(meta->flags))
{
SWSS_LOG_THROW("attribute %s is not CREATE_AND_SET, bug?", meta->attridname);
}
if (meta->defaultvaluetype == SAI_DEFAULT_VALUE_TYPE_NONE)
{
SWSS_LOG_THROW("attribute %s default value type is NONE, bug?", meta->attridname);
}
auto defaultValueAttr = BestCandidateFinder::getSaiAttrFromDefaultValue(currentView, m_switch, *meta);
if (defaultValueAttr == nullptr)
{
SWSS_LOG_THROW("Can't get default value for present current attr %s:%s, FIXME",
meta->attridname,
attr->getStrAttrValue().c_str());
}
if (attr->getStrAttrValue() == defaultValueAttr->getStrAttrValue())
{
/*
* If current value is the same as default value no need to
* generate ASIC update command.
*/
continue;
}
currentView.asicSetAttribute(currentObj, defaultValueAttr);
}
currentObj->setObjectStatus(SAI_OBJECT_STATUS_FINAL);
/*
* TODO Revisit.
*
* Problem here is that default trap group is used and we build references
* for it, so it will not be "removed" when processing, since it's default
* object so it will not be processed, and also it can't be removed since
* it's default object.
*/
}
/**
* @brief Indicates whether object can be removed.
*
* This method should be used on oid objects, all non oid objects (like route,
* neighbor, etc.) can be safely removed.
*
* @param currentObj Current object to be examined.
*
* @return True if object can be removed, false otherwise.
*/
bool ComparisonLogic::isNonRemovableObject(
_In_ const AsicView ¤tView,
_In_ const AsicView &temporaryView,
_In_ const std::shared_ptr<const SaiObj> ¤tObj)
{
SWSS_LOG_ENTER();
if (currentObj->isOidObject())
{
sai_object_id_t rid = currentView.m_vidToRid.at(currentObj->getVid());
return m_switch->isNonRemovableRid(rid);
}
/*
* Object is non object id, like ROUTE_ENTRY etc, can be removed.
*/
return false;
}
void ComparisonLogic::removeExistingObjectFromCurrentView(
_In_ AsicView ¤tView,
_In_ const AsicView &temporaryView,
_In_ const std::shared_ptr<SaiObj> ¤tObj)
{
SWSS_LOG_ENTER();
if (currentObj->getObjectStatus() != SAI_OBJECT_STATUS_NOT_PROCESSED)
{
SWSS_LOG_THROW("FATAL: removing object with status: %d, logic error", currentObj->getObjectStatus());
}
/*
* This decreasing VID reference will be hard when actual reference will
* be one of default objects like CPU or default trap group when we "bring
* default value" or maybe in oid case we will not bring default values and
* just remove and recreate.
*/
if (currentObj->isOidObject())
{
int count = currentView.getVidReferenceCount(currentObj->getVid());
if (count != 0)
{
/*
* If references count is not zero, we need to remove child
* first for that we need dependency tree, not supported yet.
*
* NOTE: Object can be non removable and can have reference on it.
*/
SWSS_LOG_THROW("can't remove existing object %s:%s since reference count is %d, FIXME",
currentObj->m_str_object_type.c_str(),
currentObj->m_str_object_id.c_str(),
count);
}
}
/*
* If some object can't be removed from current view and it's missing from
* temp, then it needs to be transferred to temp as well and bring to
* default values.
*
* First we need to check if object can be removed, like port's cant be
* removed, vlan 1, queues, ingress pg etc.
*
* Remove for those objects is not supported now lets bring back
* object to default state.
*
* For oid values we can figure out if we would have list of
* defaults which can be removed and which can't and use list same
* for queues, schedulers etc.
*
* We could use switch here and non created objects to make this simpler.
*/
if (currentObj->getObjectType() == SAI_OBJECT_TYPE_SWITCH)
{
/*
* Remove switch is much simpler, we just need check if switch doesn't
* exist in temporary view, and we can just remove it. In here when
* attributes don't match it's much complicated.
*/
SWSS_LOG_THROW("switch can't be removed from current view, not supported yet");
}
if (isNonRemovableObject(currentView, temporaryView, currentObj))
{
bringNonRemovableObjectToDefaultState(currentView, currentObj);
}
else
{
/*
* Asic remove object is decreasing reference count on non object ID if
* current object is non object id, release existing links only looks
* into attributes.
*/
currentView.asicRemoveObject(currentObj);
currentObj->setObjectStatus(SAI_OBJECT_STATUS_REMOVED);
}
}
sai_object_id_t ComparisonLogic::translateTemporaryVidToCurrentVid(
_In_ const AsicView ¤tView,
_In_ const AsicView &temporaryView,
_In_ sai_object_id_t tvid)
{
SWSS_LOG_ENTER();
/*
* This method is used to translate temporary VID to current VID using RID
* which should be present in both views. If RID doesn't exist, then we
* check whether object was created if so, then we return temporary VID
* instead of creating new current VID and we don't need to track mapping
* of those vids not having actual RID. This function should be used only
* when we creating new objects in current view.
*/
auto temporaryIt = temporaryView.m_vidToRid.find(tvid);
if (temporaryIt == temporaryView.m_vidToRid.end())
{
/*
* This can also happen when this VID object was created dynamically,
* and don't have RID assigned yet, in that case we should use exact
* same VID. We need to get temporary object for that VID and see if
* flag created is set, and same VID must then exists in current view.
*/
/*
* We need to find object that has this vid
*/
auto tempIt = temporaryView.m_oOids.find(tvid);
if (tempIt == temporaryView.m_oOids.end())
{
SWSS_LOG_THROW("temporary VID %s not found in temporary view",
sai_serialize_object_id(tvid).c_str());
}
const auto &tempObj = tempIt->second;
if (tempObj->m_createdObject)
{
SWSS_LOG_DEBUG("translated temp VID %s to current, since object was created",
sai_serialize_object_id(tvid).c_str());
return tvid;
}
SWSS_LOG_THROW("VID %s was not found in temporary view, was object created? FIXME",
sai_serialize_object_id(tvid).c_str());
}
sai_object_id_t rid = temporaryIt->second;
auto currentIt = currentView.m_ridToVid.find(rid);
if (currentIt == currentView.m_ridToVid.end())
{
SWSS_LOG_THROW("RID %s was not found in current view",
sai_serialize_object_id(rid).c_str());
}
sai_object_id_t cvid = currentIt->second;
SWSS_LOG_DEBUG("translated temp VID %s using RID %s to current VID %s",
sai_serialize_object_id(tvid).c_str(),
sai_serialize_object_id(rid).c_str(),
sai_serialize_object_id(cvid).c_str());
return cvid;
}
std::shared_ptr<SaiAttr> ComparisonLogic::translateTemporaryVidsToCurrentVids(
_In_ const AsicView ¤tView,
_In_ const AsicView &temporaryView,
_In_ const std::shared_ptr<SaiObj> ¤tObj,
_In_ const std::shared_ptr<SaiAttr> &inattr)
{
SWSS_LOG_ENTER();
/*
* We are creating copy here since we will modify contents of that
* attribute.
*/
auto attr = std::make_shared<SaiAttr>(inattr->getStrAttrId(), inattr->getStrAttrValue());
if (!attr->isObjectIdAttr())
{
return attr;
}
/*
* We need temporary VID translation to current view RID.
*
* We also need simpler version that will translate simple VID for
* oids present inside non object id structs.
*/
uint32_t count = 0;
sai_object_id_t *objectIdList = NULL;
auto &at = *attr->getRWSaiAttr();
switch (attr->getAttrMetadata()->attrvaluetype)
{
case SAI_ATTR_VALUE_TYPE_OBJECT_ID:
count = 1;
objectIdList = &at.value.oid;
break;
case SAI_ATTR_VALUE_TYPE_OBJECT_LIST:
count = at.value.objlist.count;
objectIdList = at.value.objlist.list;
break;
case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_ID:
if (at.value.aclfield.enable)
{
count = 1;
objectIdList = &at.value.aclfield.data.oid;
}
break;
case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_LIST:
if (at.value.aclfield.enable)
{
count = at.value.aclfield.data.objlist.count;
objectIdList = at.value.aclfield.data.objlist.list;
}
break;
case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_ID:
if (at.value.aclaction.enable)
{
count = 1;
objectIdList = &at.value.aclaction.parameter.oid;
}
break;
case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_LIST:
if (at.value.aclaction.enable)
{
count = at.value.aclaction.parameter.objlist.count;
objectIdList = at.value.aclaction.parameter.objlist.list;
}
break;
default:
// TODO check is is oid object and throw as not translated
break;
}
for (uint32_t i = 0; i < count; ++i)
{
sai_object_id_t tvid = objectIdList[i];
if (tvid == SAI_NULL_OBJECT_ID)
{
continue;
}
/*
* Do actual translation.
*/
objectIdList[i] = translateTemporaryVidToCurrentVid(currentView, temporaryView, tvid);
}
/*
* Since we probably performed updates on this attribute, since VID was
* exchanged, we need to update value string of that attribute.
*/
attr->updateValue();
return attr;
}
/**
* @brief Set attribute on current object
*
* This function will set given attribute to current object adding new
* attribute or replacing existing one. Given attribute can be either one
* temporary attribute missing or different from current object object or
* default attribute value if we need to bring some attribute to default value.
*
* @param currentView Current view.
* @param temporaryView Temporary view.
* @param currentObj Current object on which we set attribute.
* @param inattr Attribute to be set on current object.
*/
void ComparisonLogic::setAttributeOnCurrentObject(
_In_ AsicView ¤tView,
_In_ const AsicView &temporaryView,
_In_ const std::shared_ptr<SaiObj> ¤tObj,
_In_ const std::shared_ptr<SaiAttr> &inattr)
{
SWSS_LOG_ENTER();
/*
* At the beginning just small assert to check if object is create and set.
*/
const auto meta = inattr->getAttrMetadata();
if (!SAI_HAS_FLAG_CREATE_AND_SET(meta->flags))
{
SWSS_LOG_THROW("can't set attribute %s on current object %s:%s since it's not CREATE_AND_SET",
meta->attridname,
currentObj->m_str_object_type.c_str(),
currentObj->m_str_object_id.c_str());
}
/*
* NOTE: Since attribute we SET can be OID attribute, and that can be a VID
* or list of VIDs from temporary view. So after this "set" operation we
* will end up with MIXED VIDs on current view, some object will contain
* VIDs from temporary view, and this can lead to fail in
* translate_vid_to_rid since we will need to look inside two views to find
* right RID.
*
* We need a flag to say that VID is for object that was created
* and we can mix only those VID's for objects that was created.
*
* In here we need to translate attribute VIDs to current VIDs to keep
* track in dependency tree, since if we use temporary VID that will point
* to the same RID then we will lost track in dependency tree of that VID
* reference count.
*
* This should also done for object id's inside non object ids.
*/
std::shared_ptr<SaiAttr> attr = translateTemporaryVidsToCurrentVids(currentView, temporaryView, currentObj, inattr);
currentView.asicSetAttribute(currentObj, attr);
}
/**
* @brief Create new object from temporary object.
*
* Since best match was not found we need to create a brand new object and put
* it into current view as well.
*
* @param currentView Current view.
* @param temporaryView Temporary view.
* @param temporaryObj Temporary object to be cloned to current view.
*/
void ComparisonLogic::createNewObjectFromTemporaryObject(
_In_ AsicView ¤tView,
_In_ const AsicView &temporaryView,
_In_ const std::shared_ptr<SaiObj> &temporaryObj)
{
SWSS_LOG_ENTER();
SWSS_LOG_INFO("creating object %s:%s",
temporaryObj->m_str_object_type.c_str(),
temporaryObj->m_str_object_id.c_str());
/*
* This trap can be default trap group, so let's check if it's, then we
* can't create it, we need to set new attributes if that possible. But
* this case should not happen since on syncd start we are putting default
* trap group to asic view, so if user will query default one, they will be
* matched by RID.
*
* Default trap group is transferred to view on init if it doesn't exist.
*
* There should be no such action here, since this scenario would mean that
* there is default switch object in temporary view, but not in current
* view. All default objects are put to ASIC state table when switch is
* created. So what can happen is opposite scenario.
*/
/*
* NOTE: Since attributes we pass to create can be OID attributes, and that
* can be a VID or list of VIDs from temporary view. So after this create
* operation we will end up with MIXED VIDs on current view, some object
* will contain VIDs from temporary view, and this can lead to fail in
* translate_vid_to_rid since we will need to look inside two views to find
* right RID.
*
* We need a flag to say that VID is for object that was created and we can
* mix only those VID's for objects that was created.
*
* In here we need to translate attribute VIDs to current VIDs to keep
* track in dependency tree, since if we use temporary VID that will point
* to the same RID then we will lost track in dependency tree of that VID
* reference count.
*
* This should also be done for object id's inside non object ids.
*/
/*
* We need to loop through all attributes and create copy of this object
* and translate all attributes for getting correct VIDs like in set
* operation, we can't use VID's from temporary view to not mix them with
* current view, except created object ID's, since we won't be creating any
* new VID on the way (but we could) maybe we can do that later - but for
* that we will need some kind of RID which would later need to be removed.
*/
std::shared_ptr<SaiObj> currentObj = std::make_shared<SaiObj>();
/*
* TODO Find out better way to do this, copy operator ?
*/
currentObj->m_str_object_type = temporaryObj->m_str_object_type;
currentObj->m_str_object_id = temporaryObj->m_str_object_id; // temporary VID / non object id
currentObj->m_meta_key = temporaryObj->m_meta_key; // temporary VID / non object id
currentObj->m_info = temporaryObj->m_info;
if (!temporaryObj->isOidObject())
{
/*
* Since object contains OID inside struct, this OID may already exist
* in current view, so we need to do translation here.
*/
for (size_t j = 0; j < currentObj->m_info->structmemberscount; ++j)
{
const sai_struct_member_info_t *m = currentObj->m_info->structmembers[j];
if (m->membervaluetype != SAI_ATTR_VALUE_TYPE_OBJECT_ID)
{
continue;
}
sai_object_id_t vid = m->getoid(¤tObj->m_meta_key);
vid = translateTemporaryVidToCurrentVid(currentView, temporaryView, vid);
m->setoid(¤tObj->m_meta_key, vid);
/*
* Bind new vid reference is already done inside asicCreateObject.
*/
}
/*
* Since it's possible that object id may had been changed, we need to
* update string as well.
*/
switch (temporaryObj->getObjectType())
{
case SAI_OBJECT_TYPE_ROUTE_ENTRY:
currentObj->m_str_object_id = sai_serialize_route_entry(currentObj->m_meta_key.objectkey.key.route_entry);
break;
case SAI_OBJECT_TYPE_NEIGHBOR_ENTRY:
currentObj->m_str_object_id = sai_serialize_neighbor_entry(currentObj->m_meta_key.objectkey.key.neighbor_entry);
break;
case SAI_OBJECT_TYPE_FDB_ENTRY:
currentObj->m_str_object_id = sai_serialize_fdb_entry(currentObj->m_meta_key.objectkey.key.fdb_entry);
break;
case SAI_OBJECT_TYPE_NAT_ENTRY:
currentObj->m_str_object_id = sai_serialize_nat_entry(currentObj->m_meta_key.objectkey.key.nat_entry);
break;
case SAI_OBJECT_TYPE_INSEG_ENTRY:
currentObj->m_str_object_id = sai_serialize_inseg_entry(currentObj->m_meta_key.objectkey.key.inseg_entry);
break;
default:
SWSS_LOG_THROW("unexpected non object id type: %s",
sai_serialize_object_type(temporaryObj->getObjectType()).c_str());
}
}
/*
* CreateObject flag is set to true, so when we we will be looking to
* translate VID between temporary view and current view (since status will
* be FINAL) then we will know that there is no actual RID yet, and both
* VIDs are the same. So this is how we cam match both objects between
* views.
*/
currentObj->m_createdObject = true;
temporaryObj->m_createdObject = true;
for (const auto &pair: temporaryObj->getAllAttributes())
{
const auto &tmpattr = pair.second;
std::shared_ptr<SaiAttr> attr = translateTemporaryVidsToCurrentVids(currentView, temporaryView, currentObj, tmpattr);
if (attr->isObjectIdAttr())
{
/*
* This is new attribute so we don't need to release any new links.
* But we need to bind new links on current object.
*/
currentObj->setAttr(attr);
}
else
{
/*
* This create attribute don't contain any OIDs so no extra
* operations are required, we don't need to break any references
* and decrease any reference count.
*/
currentObj->setAttr(attr);
}
}
/*
* Asic create object inserts new reference to track inside asic view.
*/
currentView.asicCreateObject(currentObj);
/*
* Move both object status to FINAL since both objects were processed
* successfully and object was created.
*/
currentObj->setObjectStatus(SAI_OBJECT_STATUS_FINAL);
temporaryObj->setObjectStatus(SAI_OBJECT_STATUS_FINAL);
}
void ComparisonLogic::updateObjectStatus(
_In_ AsicView ¤tView,
_In_ AsicView &temporaryView,
_In_ const std::shared_ptr<SaiObj> ¤tBestMatch,
_In_ const std::shared_ptr<SaiObj> &temporaryObj)
{
SWSS_LOG_ENTER();
bool notprocessed = ((temporaryObj->getObjectStatus() == SAI_OBJECT_STATUS_NOT_PROCESSED) &&
(currentBestMatch->getObjectStatus() == SAI_OBJECT_STATUS_NOT_PROCESSED));
bool matched = ((temporaryObj->getObjectStatus() == SAI_OBJECT_STATUS_MATCHED) &&
(currentBestMatch->getObjectStatus() == SAI_OBJECT_STATUS_MATCHED));
if (notprocessed || matched)
{
/*
* Since everything was processed, then move those object status to
* FINAL.
*/
temporaryObj->setObjectStatus(SAI_OBJECT_STATUS_FINAL);
currentBestMatch->setObjectStatus(SAI_OBJECT_STATUS_FINAL);
if (temporaryObj->isOidObject())
{
/*
* When objects are object id type, we need to update map in
* temporary view for correct VID/RID.
*
* In here RID should always exists, since when objects are MATCHED
* then RID exists and both VID's are the same, and when both
* objects are not processed, then RID also exists since current
* object was selected as current best match. Other options are
* object was removed, but the it could not be selected, or object
* was created, but then is in FINAL state so also could not be
* selected here.
*/
sai_object_id_t tvid = temporaryObj->getVid();
sai_object_id_t cvid = currentBestMatch->getVid();
sai_object_id_t rid = currentView.m_vidToRid.at(cvid);
SWSS_LOG_INFO("remapped current VID %s to temp VID %s using RID %s",
sai_serialize_object_id(cvid).c_str(),
sai_serialize_object_id(tvid).c_str(),
sai_serialize_object_id(rid).c_str());
temporaryView.m_ridToVid[rid] = tvid;
temporaryView.m_vidToRid[tvid] = rid;
/*
* TODO: Set new VID if it doesn't exist in current view with NULL RID
* that will mean we created new object, this VID will be later
* used to count references and as a sanity check if we are
* increasing valid reference.
*
* Revisit. We have create flag now.
*/
}
else
{
/*
* For non object id we will create map by object type and map
* current to temporary and vice versa. For some objects like TRAP
* or VLAN values will be the same but for ROUTES or NEIGHBORS can
* be different since they contain VID which in temporary view can
* be different and because of that we will need a map.
*/
sai_object_type_t objectType = temporaryObj->getObjectType();
SWSS_LOG_INFO("remapped %s current %s to temp %s",
sai_serialize_object_type(temporaryObj->getObjectType()).c_str(),
currentBestMatch->m_str_object_id.c_str(),
temporaryObj->m_str_object_id.c_str());
temporaryView.m_nonObjectIdMap[objectType][temporaryObj->m_str_object_id] = currentBestMatch->m_str_object_id;
currentView.m_nonObjectIdMap[objectType][currentBestMatch->m_str_object_id] = temporaryObj->m_str_object_id;
}
}
else
{
/*
* This method should be used only for objects that have status MATCHED
* or NON_PROCESSED, other combinations are not supported since other
* actions will be either create new object from temporary object or
* remove existing current object.
*/
SWSS_LOG_THROW("unexpected status combination: current %d, temporary %d",
currentBestMatch->getObjectStatus(),
temporaryObj->getObjectStatus());
}
}
bool ComparisonLogic::performObjectSetTransition(
_In_ AsicView ¤tView,
_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 ¤tAttr = 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;
}
/**
* @brief Process SAI object for ASIC view transition
*
* Purpose of this function is to find matching SAI object in current view
* corresponding to new temporary view for which we want to make switch current
* ASIC configuration.
*
* This function is recursive since it checks all object attributes including
* attributes that contain other objects which at this stage may not be
* processed yet.
*
* Processing may result in different actions:
*
* - no action is taken if objects are the same
* - update existing object for new attributes if possible
* - remove current object and create new object if updating current attributes
* is not possible or best matching object was not fount in current view
*
* All those actions will be generated "in memory" no actual SAI ASIC
* operations will be performed at this stage. After entire object dependency
* graph will be processed and consistent, list of generated actions will be
* executed on actual ASIC. This approach is safer than making changes right
* away since if some object is not supported we will return return but ASIC
* still will be in consistent state.
*
* NOTE: Development is in progress, not all corner cases are supported yet.
*
* @param currentView Current view.
* @param temporaryView Temporary view.
* @param temporaryObj Temporary object to be processed.
*/
void ComparisonLogic::processObjectForViewTransition(
_In_ AsicView ¤tView,
_In_ AsicView &temporaryView,
_Inout_ std::shared_ptr<SaiObj> temporaryObj)
{
SWSS_LOG_ENTER();
if (temporaryObj->getObjectStatus() == SAI_OBJECT_STATUS_FINAL)
{
/*
* If object is in final state, no need to process it again, nothing
* about this object will change.
*/
return;
}
SWSS_LOG_INFO("processing: %s:%s", temporaryObj->m_str_object_type.c_str(), temporaryObj->m_str_object_id.c_str());
procesObjectAttributesForViewTransition(currentView, temporaryView, temporaryObj);
/*
* Now since all object ids (VIDs) were processed for temporary object we
* can try to find current best match.
*/
auto bcf = std::make_shared<BestCandidateFinder>(currentView, temporaryView, m_switch);
std::shared_ptr<SaiObj> currentBestMatch = bcf->findCurrentBestMatch(temporaryObj);
/*
* So there will be interesting problem, when we don't find best matching
* object, but actual object will exist, and it will have the same KEY
* attribute like, and some other attribute will be create_only and it will
* be different then we can't just create temporary object because of key
* will match and we have conflict, so we need first in this case remove
* previous object with key
*
* so there are 2 options here:
*
* - either our finding best match function will always include matching
* keys (if more keys, they all should (or any?) match should be right
* away taken) and this should be only object returned on list possible
* candidates as best match then below logic should figure out that we
* cant do "set" and we need to destroy object and destroy it now, and
* create temporary later this means that finding best logic can return
* objects that are not upgradable "cant do set"
*
* - or second solution, in this case, finding best match will not include
* this object on list, since other create only attribute will be
* different which also means we need to destroy and recreate, but in
* this case finding best match will figure this out and will destroy
* object on the way since it will know that set transition is not
* possible so here when we find best match (other object) in that case
* we always will be able to do "SET", or when no object will be returned
* then we can just right away create this object since all keys objects
* were removed by finding best match
*
* In both cases logic is the same, someone needs to figure out whether
* updating object and set is possible or whether we leave object alone, or
* we delete it before creating new one object.
*
* Preferably this logic could be in both but that duplicates compare
* actions i would rather want this in finding best match, but that would
* modify current view which i don't like much and here it seems like to
* much hassle since after finding best match here we would have simple
* operations.
*
* Currently KEY are in: vlan, queue, port, hostif_trap
* - port - hw lane, we don't need to worry since we match them all
* - queue - all other attributes can be set
* - vlan - all other attributes can be set
* - hostif_trap - all other attributes can be set
*
* We could add a check for that in sanity.
*/
if (currentBestMatch == nullptr)
{
/*
* Since we didn't found current best match, just create this object as
* a new object, and put it also in current view in final state.
*/
SWSS_LOG_INFO("failed to find best match %s %s in current view, will create new object",
temporaryObj->m_str_object_type.c_str(),
temporaryObj->m_str_object_id.c_str());
/*
* We can hit this scenario when, we will not have current best match,
* and we still want to create new object, but resources are limited,
* so it can happen that in this place we also would need to remove
* some objects first, before we create new one.
*/
breakBeforeMake(currentView, temporaryView, currentBestMatch, temporaryObj);
createNewObjectFromTemporaryObject(currentView, temporaryView, temporaryObj);
return;
}
SWSS_LOG_INFO("found best match %s: current: %s temporary: %s",
currentBestMatch->m_str_object_type.c_str(),
currentBestMatch->m_str_object_id.c_str(),
temporaryObj->m_str_object_id.c_str());
/*
* We need to two passes, for not matched parameters since if first
* attribute will modify current object like SET operation, but second
* attribute will not be able to do update because of CREATE_ONLY etc, then
* we will end up with half modified current object and VIEW. So we have
* two choices here when update is not possible:
*
* - remove current object and it's childs and create temporary one, or
* - leave current object as unprocessed (dry run) for maybe future best
* match, and just create temporary object
*
* We will choose approach witch leaving object untouched if something will
* go wrong, we can always switch back to destroy current best match if
* that would be better approach. It could be actually param of comparison
* logic.
*
* NOTE: this function is called twice if first time will be successful
* then logs will be doubled in syslog.
*/
bool passed = performObjectSetTransition(currentView, temporaryView, currentBestMatch, temporaryObj, false);
if (!passed)
{
if (temporaryObj->getObjectStatus() == SAI_OBJECT_STATUS_MATCHED)
{
/*
* If object status is MATCHED, then we have the same object in
* current view and temporary view, so it must be possible to make
* transition to temporary view, hence for MATCHED objects
* performObjectSetTransition must pass. There can be some corner
* cases like existing objects after switch create and their attributes
* that are OID set to existing objects (like QUEUES).
*/
SWSS_LOG_THROW("performObjectSetTransition on MATCHED object (%s) FAILED! bug?",
temporaryObj->m_str_object_id.c_str());
}
/*
* First pass was a failure, so we can't update existing object with
* temporary one, probably because of CRATE_ONLY attributes.
*
* Our strategy here will be to leave untouched current object (second
* strategy will be to remove it and it's children if link can't be
* broken, Objects with non id like ROUTE FDB NEIGHBOR they need to be
* removed first anyway, since we got best match, and for those best
* match is via struct elements so we can't create next route with the
* same elements in the struct.
*
* Then we create new object from temporary one and set it state to
* final.
*/
if (temporaryObj->getObjectType() == SAI_OBJECT_TYPE_SWITCH)
{
SWSS_LOG_THROW("we should not get switch object here, bug?");
}
if (!temporaryObj->isOidObject())
{
removeExistingObjectFromCurrentView(currentView, temporaryView, currentBestMatch);
}
else
{
/*
* Later on if we decide we want to remove objects before creating
* new one's we need to put here this action, or just remove this
* entire switch and call remove.
*/
breakBeforeMake(currentView, temporaryView, currentBestMatch, temporaryObj);
}
// No need to store VID since at this point we don't have RID yet, it will be
// created on execute asic and RID will be saved to maps in both views.
SWSS_LOG_INFO("found best match, but set failed: %s: current: %s temporary: %s",
currentBestMatch->m_str_object_type.c_str(),
currentBestMatch->m_str_object_id.c_str(),
temporaryObj->m_str_object_id.c_str());
createNewObjectFromTemporaryObject(currentView, temporaryView, temporaryObj);
return;
}
/*
* First pass was successful, so we can do update on current object, lets do that now!
*/
if (temporaryObj->isOidObject() && (temporaryObj->getObjectStatus() != SAI_OBJECT_STATUS_MATCHED))
{
/*
* We track all oid object references, and since this object was
* matched we need to insert it's vid to reference map since when we
* will update attributes in current view from temporary view
* attributes, then this object must be in reference map. This is
* sanity check. Object can't be in matched state since matched objects
* have the same RID and VID in both views, so VID already exists in
* reference map.
*
* This object VID will be remapped inside updateObjectStatus to
* rid/vid map since if we are here then we matched one of current
* objects to this temporary object and update can be performed.
*/
/*
* TODO: This is temporary VID, it's not available in current view so
* we need to remove this code here, since we will translate this VID
* in temporary view to current view.
*/
sai_object_id_t vid = temporaryObj->getVid();
currentView.insertNewVidReference(vid);
// save temporary vid to rid after match
// can't be here since vim to rid map will not match
// currentView.m_vidToRid[vid] = currentView.m_vidToRid.at(currentBestMatch->getVid());
}
performObjectSetTransition(currentView, temporaryView, currentBestMatch, temporaryObj, true);
/*
* Since we got here, that means matching or updating current best match
* object was successful, and we can now set their state to final.
*/
updateObjectStatus(currentView, temporaryView, currentBestMatch, temporaryObj);
}
void ComparisonLogic::removeCurrentObjectDependencyTree(
_In_ AsicView ¤tView,
_In_ AsicView &temporaryView,
_In_ const std::shared_ptr<SaiObj>& currentObj)
{
SWSS_LOG_ENTER();
if (!currentObj->isOidObject())
{
// we should be able to remove non object right away since it's leaf
removeExistingObjectFromCurrentView(currentView, temporaryView, currentObj);
return;
}
// check reference count and remove other objects if necessary
const int count = currentView.getVidReferenceCount(currentObj->getVid());
if (count)
{
SWSS_LOG_INFO("similar best match has reference count: %d, will need to remove other objects", count);
auto* info = sai_metadata_get_object_type_info(currentObj->getObjectType());
// use reverse dependency graph to locate objects where current object can be used
// need to lookout on loops on some objects
for (size_t i = 0; i < info->revgraphmemberscount; i++)
{
auto *revgraph = info->revgraphmembers[i];
if (revgraph->structmember)
{
SWSS_LOG_THROW("struct fields not supported yet, FIXME");
}
SWSS_LOG_INFO("used on %s:%s",
sai_serialize_object_type(revgraph->depobjecttype).c_str(),
revgraph->attrmetadata->attridname);
// TODO it could be not processed, or matched, since on matched we
// can still break the link
auto objs = currentView.getObjectsByObjectType(revgraph->depobjecttype);
for (auto& obj: objs)
{
// NOTE: current object can have multiple OID attributes that
// they can have the same value, but they can have different
// attributes (like set/create_only)
auto status = obj->getObjectStatus();
if (status != SAI_OBJECT_STATUS_NOT_PROCESSED &&
status != SAI_OBJECT_STATUS_MATCHED)
{
continue;
}
auto attr = obj->tryGetSaiAttr(revgraph->attrmetadata->attrid);
if (attr == nullptr)
{
// no such attribute
continue;
}
if (revgraph->attrmetadata->attrvaluetype != SAI_ATTR_VALUE_TYPE_OBJECT_ID &&
revgraph->attrmetadata->attrvaluetype != SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_ID)
{
// currently we only support reference on OID, not list
SWSS_LOG_THROW("attr value type %d, not supported yet, FIXME",
revgraph->attrmetadata->attrvaluetype);
}
if (attr->getOid() != currentObj->getVid())
{
// VID is not matching, skip this attribute
continue;
}
// we found object and attribute that is using current VID,
// it's possible this VID is used on multiple attributes on the
// same object
SWSS_LOG_INFO("found reference object: %s", obj->m_str_object_id.c_str());
if (revgraph->attrmetadata->iscreateonly && status == SAI_OBJECT_STATUS_NOT_PROCESSED)
{
// attribute is create only, and object was not processed
// yet this means that we need to remove object, since we
// can't break the link
removeCurrentObjectDependencyTree(currentView, temporaryView, obj); // recursion
}
else if (revgraph->attrmetadata->iscreateandset && status == SAI_OBJECT_STATUS_NOT_PROCESSED)
{
if (revgraph->attrmetadata->allownullobjectid)
{
// we can also remove entire object here too
SWSS_LOG_THROW("break the link is not implemented yet, FIXME");
}
else
{
// attribute is create and set, but not allow null object id
// so probably attribute is mandatory on create, we can't break
// the link, but we can remove entire object
removeCurrentObjectDependencyTree(currentView, temporaryView, obj); // recursion
}
}
else if (revgraph->attrmetadata->iscreateandset && status == SAI_OBJECT_STATUS_MATCHED)
{
SWSS_LOG_THROW("matched break the link is not implemented yet, FIXME");
}
else
{
SWSS_LOG_THROW("remove on %s, obj status: %d, not supported, FIXME", revgraph->attrmetadata->attridname, status);
}
}
}
}
removeExistingObjectFromCurrentView(currentView, temporaryView, currentObj);
}
void ComparisonLogic::breakBeforeMake(
_In_ AsicView ¤tView,
_In_ AsicView &temporaryView,
_In_ const std::shared_ptr<SaiObj>& currentBestMatch, // can be nullptr
_In_ const std::shared_ptr<SaiObj>& temporaryObj)
{
SWSS_LOG_ENTER();
/*
* Break Before Make rule.
*
* We can have 2 paths here:
*
* - current best match object was not found (nullptr), in case of limited
* resources, this mean that we want to remove some existing object
* before creating new, so number of existing objects will not exceed
* initial value, for example we remove acl table, then we create acl
* table. Of course this will not hold true, if temporary number of
* objects of given type is greater than current existing objects.
*
* - current best match was found, but perform Object Set Transition
* operation failed, so we can't move current object attributes to
* temporary object attributes, because for example some are create only.
*/
if (!m_breakConfig->shouldBreakBeforeMake(temporaryObj->getObjectType()))
{
// skip object if not in break config
return;
}
if (currentBestMatch == nullptr)
{
// it can happen that current best match is not found for temporary
// object then we need to find best object for removal from existing
// ones the most suitable should be the one with most same attributes,
// since maybe only one read only attribute has been changed, and this
// will automatically result in null best match
auto bcf = std::make_shared<BestCandidateFinder>(currentView, temporaryView, m_switch);
std::shared_ptr<SaiObj> similarBestMatch = bcf->findSimilarBestMatch(temporaryObj);
if (similarBestMatch == nullptr)
{
SWSS_LOG_WARN("similar best match is null, not removing");
return;
}
removeCurrentObjectDependencyTree(currentView, temporaryView, similarBestMatch);
return;
}
removeCurrentObjectDependencyTree(currentView, temporaryView, currentBestMatch);
}
void ComparisonLogic::checkSwitch(
_In_ const AsicView ¤tView,
_In_ const AsicView &temporaryView)
{
SWSS_LOG_ENTER();
auto csws = currentView.getObjectsByObjectType(SAI_OBJECT_TYPE_SWITCH);
auto tsws = temporaryView.getObjectsByObjectType(SAI_OBJECT_TYPE_SWITCH);
size_t csize = csws.size();
size_t tsize = tsws.size();
if (csize == 0 && tsize == 0)
{
return;
}
if (csize == 1 && tsize == 1)
{
/*
* VID on both switches must match.
*/
auto csw = csws.at(0);
auto tsw = tsws.at(0);
if (csw->getVid() != tsw->getVid())
{
SWSS_LOG_THROW("Current switch VID %s is different than temporary VID %s",
sai_serialize_object_id(csw->getVid()).c_str(),
sai_serialize_object_id(tsw->getVid()).c_str());
}
/*
* TODO: We need to check create only attributes (hardware info), if
* they differ then throw since update will not be possible, we can
* separate this to different case to remove existing switch and
* recreate new one check switches and VID matching if it's the same.
*/
return;
}
/*
* Current logic supports only 1 instance of switch so in both views we
* should have only 1 instance of switch and they must match.
*/
SWSS_LOG_THROW("unsupported number of switches: current %zu, temp %zu", csize, tsize);
}
// TODO this needs to be done in generic way for all
// default OID values set to attrvalue
void ComparisonLogic::bringDefaultTrapGroupToFinalState(
_In_ AsicView ¤tView,
_In_ AsicView &temporaryView)
{
SWSS_LOG_ENTER();
sai_object_id_t rid = currentView.m_defaultTrapGroupRid;
if (temporaryView.hasRid(rid))
{
/*
* Default trap group is defined inside temporary view
* so it will be matched by RID, no need to process.
*/
return;
}
sai_object_id_t vid = currentView.m_ridToVid.at(rid);
const auto &dtgObj = currentView.m_oOids.at(vid);
if (dtgObj->getObjectStatus() != SAI_OBJECT_STATUS_NOT_PROCESSED)
{
/*
* Object was processed, no further actions are required.
*/
return;
}
/*
* Trap group was not processed, it can't be removed so bring it to default
* state and release existing references.
*/
bringNonRemovableObjectToDefaultState(currentView, dtgObj);
}
void ComparisonLogic::populateExistingObjects(
_In_ AsicView ¤tView,
_In_ AsicView &temporaryView)
{
SWSS_LOG_ENTER();
SWSS_LOG_NOTICE("populate existing objects");
auto rids = m_switch->getDiscoveredRids();
/*
* We should transfer existing objects from current view to temporary view.
* But not all objects, since user could remove some default removable
* objects like vlan member, bridge port etc. We collected those removed
* objects to initViewRemovedVidSet and we will use this as a reference to
* transfer objects to temporary view.
*
* TODO: still sairedis metadata database have no idea about existing
* objects so user can create object which already exists like vlan 1 if he
* didn't queried it yet. We need to transfer all dependency tree to
* sairedis after switch create.
*/
if (temporaryView.getObjectsByObjectType(SAI_OBJECT_TYPE_SWITCH).size() == 0)
{
SWSS_LOG_NOTICE("no switch present in temporary view, skipping");
return;
}
auto coldBootDiscoveredVids = m_switch->getColdBootDiscoveredVids();
auto warmBootDiscoveredVids = m_switch->getWarmBootDiscoveredVids();
auto warmBootNewDiscoveredVids = m_switch->getWarmBootNewDiscoveredVids();
/*
* If some objects that are existing objects on switch are not present in
* temporary view, just populate them with empty values. Since vid2rid
* maps must match on both view after apply view.
*/
for (const sai_object_id_t rid: rids)
{
if (rid == SAI_NULL_OBJECT_ID)
{
continue;
}
/*
* Rid's are not matched yet, but if VID is the same then we have the
* same object.
*/
if (temporaryView.hasRid(rid))
{
SWSS_LOG_INFO("temporary view has existing %s RID %s",
sai_serialize_object_type(m_vendorSai->objectTypeQuery(rid)).c_str(),
sai_serialize_object_id(rid).c_str());
continue;
}
auto it = currentView.m_ridToVid.find(rid);
if (it == currentView.m_ridToVid.end())
{
SWSS_LOG_THROW("unable to find existing object RID %s in current view",
sai_serialize_object_id(rid).c_str());
}
sai_object_id_t vid = it->second;
if (m_initViewRemovedVids.find(vid) != m_initViewRemovedVids.end())
{
/*
* If this vid was removed during init view mode, it still exits in
* current view, but don't put it to existing objects, then
* comparison logic will take care of that and, it will remove it
* from current view as well.
*/
SWSS_LOG_INFO("object was removed in init view: %s RID %s VID %s",
sai_serialize_object_type(m_vendorSai->objectTypeQuery(rid)).c_str(),
sai_serialize_object_id(rid).c_str(),
sai_serialize_object_id(vid).c_str());
continue;
}
/*
* In case of warm boot, it may happen that user set some created
* objects on default existing objects, like for example buffer profile
* on ingress priority group. In this case buffer profile should not
* be considered as matched object and copied to temporary view, since
* this object was not default existing object (on 1st cold boot) so in
* this case it must be processed by comparison logic and matched with
* possible new buffer profile created in temporary view. This may
* happen if OA will not care what was set previously on ingress
* priority group and just create new buffer profile and assign it. In
* this case we don't want any asic operations to happen. Also if we
* would pass this buffer profile as existing to temporary view, it
* would not be matched by comparison logic, and in the result we will
* end up with 2 buffer profiles, which 1st of them will be not
* assigned anywhere and this will be memory leak.
*
* Also a bunch of new asic operations will be generated for setting
* new user created buffer profile. That's why we need default existing
* vid list to distinguish between user created and default switch
* created objects.
*
* For default existing objects, we don't need to copy attributes, since
* if user didn't set them, we want them to be back to default values.
*
* NOTE: If we are here, then this RID exists only in current view, and
* if this object contains any OID attributes, discovery logic queried
* them so they are also existing in current view.
*
* Also in warm boot, when user removed port, and then created some new
* ports, new QUEUEs, IPGs and SGs will be created automatically by
* SAI. Those new created objects mot likely will have different RID
* values then previous instances for given port. Those values should
* also be copied to temporary view, since they will not exist on cold
* boot discovered VIDs. If not, then comparison logic will try to remove
* them which is not what we want.
*
* This is tricky scenario, and there could be some issues also when
* other object types would be created by user.
*/
bool performColdCheck = true;
if (warmBootDiscoveredVids.find(vid) != warmBootDiscoveredVids.end())
{
sai_object_type_t ot = VidManager::objectTypeQuery(vid);
switch (ot)
{
case SAI_OBJECT_TYPE_QUEUE:
case SAI_OBJECT_TYPE_INGRESS_PRIORITY_GROUP:
case SAI_OBJECT_TYPE_SCHEDULER_GROUP:
// TODO this case may require adjustment, if user will do a
// warm boot then remove/add some ports and make another
// warm boot, it may happen that current logic will be
// confused which of those objects are from previous warm
// boot or second one, need better way to mark changes to
// those objects in redis DB between warm boots
performColdCheck = false;
break;
default:
break;
}
}
if (warmBootNewDiscoveredVids.find(vid) != warmBootNewDiscoveredVids.end())
{
// We have some new discovered VIDs after warm boot, we need to
// create temporary objects from them, so comparison logic will not
// get confused and will not remove them.
//
// TODO: there could be potential issue here, when user will remove
// one of the new discovered object in init view phase, then we
// can't put that object to DB, and it should be removed.
performColdCheck = false;
sai_object_type_t ot = VidManager::objectTypeQuery(vid);
SWSS_LOG_NOTICE("creating temporary object for new discovered VID %s:%s",
sai_serialize_object_type(ot).c_str(),
sai_serialize_object_id(vid).c_str());
}
if (performColdCheck && coldBootDiscoveredVids.find(vid) == coldBootDiscoveredVids.end())
{
SWSS_LOG_INFO("object is not on default existing list: %s RID %s VID %s",
sai_serialize_object_type(m_vendorSai->objectTypeQuery(rid)).c_str(),
sai_serialize_object_id(rid).c_str(),
sai_serialize_object_id(vid).c_str());
continue;
}
temporaryView.createDummyExistingObject(rid, vid);
SWSS_LOG_INFO("populate existing %s RID %s VID %s",
sai_serialize_object_type(m_vendorSai->objectTypeQuery(rid)).c_str(),
sai_serialize_object_id(rid).c_str(),
sai_serialize_object_id(vid).c_str());
/*
* Move both objects to matched state since match oids was already
* called, and here we created some new objects that should be matched.
*/
currentView.m_oOids.at(vid)->setObjectStatus(SAI_OBJECT_STATUS_MATCHED);
temporaryView.m_oOids.at(vid)->setObjectStatus(SAI_OBJECT_STATUS_MATCHED);
}
}
bool ComparisonLogic::checkAsicVsDatabaseConsistency(
_In_ std::shared_ptr<VirtualOidTranslator> translator)
{
SWSS_LOG_ENTER();
bool hasErrors = false;
{
SWSS_LOG_TIMER("consistency check");
SWSS_LOG_WARN("performing consistency check");
for (const auto &pair: m_temp->m_soAll)
{
const auto &obj = pair.second;
const auto &attrs = obj->getAllAttributes();
// get object meta key for get (object id or *entry)
sai_object_meta_key_t meta_key = obj->m_meta_key;
// translate all VID's to RIDs in non object is's
translator->translateVidToRid(meta_key);
auto info = sai_metadata_get_object_type_info(obj->getObjectType());
sai_attribute_t attr;
memset(&attr, 0, sizeof(attr));
if (attrs.size() == 0)
{
// get first attribute and do a get query to see if object exist's
auto meta = info->attrmetadata[0];
sai_status_t status = m_vendorSai->get(meta_key, 1, &attr);
switch (status)
{
case SAI_STATUS_SUCCESS:
case SAI_STATUS_BUFFER_OVERFLOW:
continue;
case SAI_STATUS_NOT_IMPLEMENTED:
case SAI_STATUS_NOT_SUPPORTED:
SWSS_LOG_WARN("GET api for %s is not implemented on %s",
meta->attridname,
obj->m_str_object_id.c_str());
continue;
}
SWSS_LOG_ERROR("failed to get %s on %s: %s",
meta->attridname,
obj->m_str_object_id.c_str(),
sai_serialize_status(status).c_str());
hasErrors = true;
continue;
}
for (const auto &ap: attrs)
{
const auto &saiAttr = ap.second;
auto meta = saiAttr->getAttrMetadata();
// deserialize existing attribute so deserialize will allocate
// memory for all list's
attr.id = meta->attrid;
sai_deserialize_attr_value(saiAttr->getStrAttrValue(), *meta, attr, false);
// translate all VIDs from DB to RIDs for compare
translator->translateVidToRid(obj->getObjectType(), 1, &attr);
// get attr value with RIDs
const std::string& dbValue = sai_serialize_attr_value(*meta, attr);
sai_status_t status = m_vendorSai->get(meta_key, 1, &attr);
if (meta->attrid == SAI_QOS_MAP_ATTR_MAP_TO_VALUE_LIST && meta->objecttype == SAI_OBJECT_TYPE_QOS_MAP && status == SAI_STATUS_SUCCESS)
{
// order does not matter on this list
if (BestCandidateFinder::hasEqualQosMapList(attr.value.qosmap, saiAttr->getSaiAttr()->value.qosmap))
{
sai_deserialize_free_attribute_value(meta->attrvaluetype, attr);
continue;
}
}
// free possible allocated lists
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_ERROR("failed to get %s on %s %s",
meta->attridname,
obj->m_str_object_id.c_str(),
sai_serialize_status(status).c_str());
hasErrors = true;
sai_deserialize_free_attribute_value(meta->attrvaluetype, attr);
continue;
}
const std::string &asicValue = sai_serialize_attr_value(*meta, attr);
sai_deserialize_free_attribute_value(meta->attrvaluetype, attr);
// pointers will not be equal since those will be from
// different process memory maps so just check if both pointers
// are NULL or both are SET
if (meta->attrvaluetype == SAI_ATTR_VALUE_TYPE_POINTER)
{
if (attr.value.ptr == NULL && saiAttr->getSaiAttr()->value.ptr == NULL)
continue;
if (attr.value.ptr != NULL && saiAttr->getSaiAttr()->value.ptr != NULL)
continue;
}
if (asicValue == dbValue)
continue;
SWSS_LOG_ERROR("value missmatch: %s on %s: ASIC: %s DB: %s, inconsistent state!",
meta->attridname,
obj->m_str_object_id.c_str(),
asicValue.c_str(),
dbValue.c_str());
hasErrors = true;
}
}
swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_INFO);
}
swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_NOTICE);
return hasErrors == false;
}
void ComparisonLogic::checkMap(
_In_ const AsicView::ObjectIdMap& firstR2V,
_In_ const char* firstR2Vname,
_In_ const AsicView::ObjectIdMap& firstV2R,
_In_ const char * firstV2Rname,
_In_ const AsicView::ObjectIdMap& secondR2V,
_In_ const char* secondR2Vname,
_In_ const AsicView::ObjectIdMap& secondV2R,
_In_ const char *secondV2Rname) const
{
SWSS_LOG_ENTER();
for (auto it: firstR2V)
{
sai_object_id_t r = it.first;
sai_object_id_t v = it.second;
if (firstV2R.find(v) == firstV2R.end())
SWSS_LOG_ERROR("%s (0x%" PRIx64 ":0x%" PRIx64 ") is missing from %s", firstR2Vname, r, v, firstV2Rname);
else if (firstV2R.at(v) != r)
SWSS_LOG_ERROR("mismatch on %s (0x%" PRIx64 ":0x%" PRIx64 ") vs %s (0x%" PRIx64 ":0x%" PRIx64 ")", firstR2Vname, r, v, firstV2Rname, v, firstV2R.at(v));
if (secondR2V.find(r) == secondR2V.end())
SWSS_LOG_ERROR("%s (0x%" PRIx64 ":0x%" PRIx64 ") is missing from %s", firstR2Vname, r, v, secondR2Vname);
else if (secondV2R.find(secondR2V.at(r)) == secondV2R.end())
SWSS_LOG_ERROR("%s (0x%" PRIx64 ":0x%" PRIx64 ") is missing from %s", firstR2Vname, r, secondR2V.at(r), secondV2Rname);
}
}
void ComparisonLogic::createPreMatchMapForObject(
_In_ const AsicView& cur,
_Inout_ AsicView& tmp,
_In_ std::shared_ptr<const SaiObj> cObj,
_In_ std::shared_ptr<const SaiObj> tObj,
_Inout_ std::set<std::string>& processed)
{
SWSS_LOG_ENTER();
if (processed.find(tObj->m_str_object_id) != processed.end())
return;
processed.insert(tObj->m_str_object_id);
if (cObj->getObjectType() != tObj->getObjectType())
return;
// this object is matched, so it have same vid/rid in both views
// but it can have attributes with objects which are not matched
// for those we want to create pre match map
for (auto& ak: tObj->getAllAttributes())
{
auto id = ak.first;
const auto& tAttr = ak.second;
if (cObj->hasAttr(id) == false)
continue;
// both objects has the same attribute
const auto& cAttr = cObj->getSaiAttr(id);
const auto& tVids = tAttr->getOidListFromAttribute();
const auto& cVids = cAttr->getOidListFromAttribute();
if (tVids.size() != cVids.size())
continue; // if number of attributes is different then skip
if (tVids.size() != 1)
continue; // for now skip list attributes
for (size_t i = 0; i < tVids.size(); ++i)
{
sai_object_id_t tVid = tVids[i];
sai_object_id_t cVid = cVids[i];
if (tVid == SAI_NULL_OBJECT_ID || cVid == SAI_NULL_OBJECT_ID)
continue;
if (tmp.m_preMatchMap.find(tVid) != tmp.m_preMatchMap.end())
continue;
// since on one attribute sometimes different object types can be set
// check if both object types are correct
if (cur.m_oOids.at(cVid)->getObjectType() != tmp.m_oOids.at(tVid)->getObjectType())
continue;
SWSS_LOG_INFO("inserting pre match entry for %s:%s: 0x%" PRIx64 " (tmp) -> 0x%" PRIx64 " (cur)",
tObj->m_str_object_id.c_str(),
cAttr->getAttrMetadata()->attridname,
tVid,
cVid);
tmp.m_preMatchMap[tVid] = cVid;
// continue recursively through object dependency tree:
createPreMatchMapForObject(cur, tmp, cur.m_oOids.at(cVid), tmp.m_oOids.at(tVid), processed);
}
}
}
void ComparisonLogic::cretePreMatchForLagMembers(
_In_ const AsicView& cur,
_Inout_ AsicView& tmp,
_Inout_ std::set<std::string>& processed)
{
SWSS_LOG_ENTER();
// match lag members that have the same port and use that lag members
// as createPreMatchMapForObject so it will create pre match entry for LAG
// and then for acl table group
auto cLagMembers = cur.getObjectsByObjectType(SAI_OBJECT_TYPE_LAG_MEMBER);
auto tLagMembers = tmp.getObjectsByObjectType(SAI_OBJECT_TYPE_LAG_MEMBER);
for (auto& cLagMember: cLagMembers)
{
if (processed.find(cLagMember->m_str_object_id) != processed.end())
continue;
for (auto& tLagMember: tLagMembers)
{
if (processed.find(tLagMember->m_str_object_id) != processed.end())
continue;
auto cPort = cLagMember->getSaiAttr(SAI_LAG_MEMBER_ATTR_PORT_ID);
auto tPort = tLagMember->getSaiAttr(SAI_LAG_MEMBER_ATTR_PORT_ID);
if (cPort->getOid() != tPort->getOid())
{
continue;
}
// at this point current and temporary lag member share the same
// port VID, then we can assume that those objects are the same
SWSS_LOG_INFO("pre match Lag Member: cur: %s, tmp: %s",
cLagMember->m_str_object_id.c_str(),
tLagMember->m_str_object_id.c_str());
tmp.m_preMatchMap[tLagMember->getVid()] = cLagMember->getVid();
createPreMatchMapForObject(cur, tmp, cLagMember, tLagMember, processed);
break;
}
}
}
#define PFC_ACL_RULE_PRIORITY "999"
void ComparisonLogic::cretePreMatchForAclEntries(
_In_ const AsicView& cur,
_Inout_ AsicView& tmp,
_Inout_ std::set<std::string>& processed)
{
SWSS_LOG_ENTER();
// match acl entries which have the same priority attribute
auto cAclEntries = cur.getObjectsByObjectType(SAI_OBJECT_TYPE_ACL_ENTRY);
auto tAclEntries = tmp.getObjectsByObjectType(SAI_OBJECT_TYPE_ACL_ENTRY);
for (auto& cAclEntry: cAclEntries)
{
if (processed.find(cAclEntry->m_str_object_id) != processed.end())
continue;
for (auto& tAclEntry: tAclEntries)
{
if (processed.find(tAclEntry->m_str_object_id) != processed.end())
continue;
auto cPrio = cAclEntry->getSaiAttr(SAI_ACL_ENTRY_ATTR_PRIORITY);
auto tPrio = tAclEntry->getSaiAttr(SAI_ACL_ENTRY_ATTR_PRIORITY);
if (cPrio->getStrAttrValue() != tPrio->getStrAttrValue())
{
continue;
}
if (cPrio->getStrAttrValue() == PFC_ACL_RULE_PRIORITY)
{
// this is special case, all pfc rules to prevent packet storm
// will have same priority, we will need to check SAI_ACL_ENTRY_ATTR_FIELD_TC
auto cTc = cAclEntry->tryGetSaiAttr(SAI_ACL_ENTRY_ATTR_FIELD_TC);
auto tTc = tAclEntry->tryGetSaiAttr(SAI_ACL_ENTRY_ATTR_FIELD_TC);
if (!cTc || !tTc || cTc->getStrAttrValue() != tTc->getStrAttrValue())
{
// TC attribute is different, not looking for this one
continue;
}
}
// at this point current and temporary acl entry share the same
// priority and in case of pfc priority, also the same TC field,
// then we can assume that those objects are the same
SWSS_LOG_NOTICE("pre match Acl Entry: cur: %s, tmp: %s, using prio: %s",
cAclEntry->m_str_object_id.c_str(),
tAclEntry->m_str_object_id.c_str(),
cPrio->getStrAttrValue().c_str());
tmp.m_preMatchMap[tAclEntry->getVid()] = cAclEntry->getVid();
createPreMatchMapForObject(cur, tmp, cAclEntry, tAclEntry, processed);
break;
}
}
}
void ComparisonLogic::createPreMatchMap(
_In_ const AsicView& cur,
_Inout_ AsicView& tmp)
{
SWSS_LOG_ENTER();
/*
* When comparison logic is running on object A it runs recursively on each
* object that was fount in object A attributes, because we need to make
* sure all objects are matched before actually processing object A. For
* example before processing ROUTE_ENTRY, we process first NEXT_HOP and
* before that ROUTER_INTERFACE and before that PORT. Object processing
* could be described going from down to top. But figuring out for top
* object ex. WRED could be hard since we would need to check not matched
* yet buffer profile and buffer pool before we use QUEUE or IPG as an
* anchor object. We can actually leverage that and when processing graph
* from top to bottom, we can create helper map which will contain
* predictions which object will be suitable for current processing
* objects. We can have N candidates objects and instead of choosing 1 at
* random, to reduce number of ASIC operations we will use a pre match map
* which is created in this method.
*
* This map should be almost exact match in warn boot case, but it will be
* treated only as hint, not as actual mapping.
*
* We will create map for all matched objects and for route_entry,
* fdb_entry and neighbor_entry.
*/
std::set<std::string> processed;
SWSS_LOG_TIMER("create preMatch map");
for (auto& ok: tmp.m_soAll)
{
auto& tObj = ok.second;
if (tObj->getObjectStatus() != SAI_OBJECT_STATUS_MATCHED)
continue;
sai_object_id_t cObjVid = cur.m_ridToVid.at(tmp.m_vidToRid.at(tObj->getVid()));
auto& cObj = cur.m_oOids.at(cObjVid);
createPreMatchMapForObject(cur, tmp, cObj, tObj, processed);
}
for (auto&pk: tmp.m_routesByPrefix)
{
auto& prefix = pk.first;
// look only for unique prefixes
if (pk.second.size() != 1)
continue;
auto it = cur.m_routesByPrefix.find(prefix);
if (it == cur.m_routesByPrefix.end())
continue;
if (it->second.size() != 1)
continue;
auto& tObj = tmp.m_soAll.at(pk.second.at(0));
auto& cObj = cur.m_soAll.at(it->second.at(0));
createPreMatchMapForObject(cur, tmp, cObj, tObj, processed);
}
for (auto&pk: tmp.m_neighborsByIp)
{
auto& ip = pk.first;
// look only for unique neighbors
if (pk.second.size() != 1)
continue;
auto it = cur.m_neighborsByIp.find(ip);
if (it == cur.m_neighborsByIp.end())
continue;
if (it->second.size() != 1)
continue;
auto& tObj = tmp.m_soAll.at(pk.second.at(0));
auto& cObj = cur.m_soAll.at(it->second.at(0));
createPreMatchMapForObject(cur, tmp, cObj, tObj, processed);
sai_object_id_t tRifVid = tObj->m_meta_key.objectkey.key.neighbor_entry.rif_id;
sai_object_id_t cRifVid = cObj->m_meta_key.objectkey.key.neighbor_entry.rif_id;
if (processed.find(cObj->m_str_object_id) == processed.end())
{
SWSS_LOG_INFO("pre match Neighbor RIF: cur: %s, tmp: %s",
sai_serialize_object_id(cRifVid).c_str(),
sai_serialize_object_id(tRifVid).c_str());
tmp.m_preMatchMap[tRifVid] = cRifVid;
auto& cO = cur.m_oOids.at(cRifVid);
auto& tO = tmp.m_oOids.at(tRifVid);
createPreMatchMapForObject(cur, tmp, cO, tO, processed);
}
}
cretePreMatchForLagMembers(cur, tmp, processed);
size_t count = 0;
for (auto& ok: tmp.m_soOids)
{
if (ok.second->getObjectStatus() != SAI_OBJECT_STATUS_MATCHED)
count++;
}
cretePreMatchForAclEntries(cur, tmp, processed);
SWSS_LOG_NOTICE("preMatch map size: %zu, tmp oid obj: %zu",
tmp.m_preMatchMap.size(),
count);
}
void ComparisonLogic::transferNotProcessed(
_In_ AsicView& current,
_In_ AsicView& temp)
{
SWSS_LOG_ENTER();
SWSS_LOG_NOTICE("calling transferNotProcessed");
/*
* It may happen that after performing view transition, some objects will
* not be processed. This may happen is scenario where buffer pool is
* assigned to buffer profile, and then buffer profile is assigned to
* queue. If OA will query only for oid for that buffer profile, then
* buffer pool will not be processed. Normally if it would be removed by
* comparison logic. More on this issue can be found on github:
* https://github.com/Azure/sonic-sairedis/issues/899
*
* So what we will do, we will transfer all not processed objects to
* temporary view with the same RID and VID. If nothing will happen to them,
* they will stay there until next warm boot where they will be removed.
*/
/*
* We need a loop (or recursion) since not processed objects may have oid
* attributes as well.
*/
while (current.getAllNotProcessedObjects().size())
{
SWSS_LOG_WARN("we have %zu not processed objects on current view, moving to temp view", current.getAllNotProcessedObjects().size());
for (const auto& obj: current.getAllNotProcessedObjects())
{
auto vid = obj->getVid();
auto rid = current.m_vidToRid.at(vid);
/*
* We should have only oid objects here, since all non oid objects
* are leafs in graph and has been removed.
*/
auto tmp = temp.createDummyExistingObject(rid, vid);
/*
* Move both objects to matched state since match oids was already
* called, and here we created some new objects that should be matched.
*/
current.m_oOids.at(vid)->setObjectStatus(SAI_OBJECT_STATUS_FINAL);
temp.m_oOids.at(vid)->setObjectStatus(SAI_OBJECT_STATUS_FINAL);
SWSS_LOG_WARN("moved %s VID %s RID %s to temporary view, and marked FINAL",
obj->m_str_object_type.c_str(),
obj->m_str_object_id.c_str(),
sai_serialize_object_id(rid).c_str());
for (auto& kvp: obj->getAllAttributes())
{
auto& sh = kvp.second;
auto attr = std::make_shared<SaiAttr>(sh->getStrAttrId(), sh->getStrAttrValue());
tmp->setAttr(attr);
SWSS_LOG_WARN(" * with attr: %s: %s",
sh->getStrAttrId().c_str(),
sh->getStrAttrValue().c_str());
}
}
}
}
void ComparisonLogic::applyViewTransition(
_In_ AsicView ¤t,
_In_ AsicView &temp)
{
SWSS_LOG_ENTER();
SWSS_LOG_TIMER("comparison logic");
checkSwitch(current, temp);
checkMatchedPorts(temp);
/*
* Process all objects
*/
/*
* During iteration no object from temp view are removed, so no need to
* worry about any iterator issues here since removed objects are only from
* current view.
*
* Order here can't be random, since it can mean that we will be adding
* routes here, and on some ASICs there is limitation that default routes
* must be put to ASIC first, so we need to compensate for that.
*
* TODO what about remove? can they be removed first ?
*
* There is another issue that when we are removing next hop group member
* and it's the last next hop group member in group where group is still in
* use by some group, then we can't remove it, we need to first remove
* route that uses this group, this puts this task in conflict when
* creating routes.
*
* XXX this is workaround. FIXME
*/
for (auto &obj: temp.m_soAll)
{
// TODO make generic list of root objects (or meta SAI for this)
if (obj.second->getObjectType() == SAI_OBJECT_TYPE_ACL_TABLE_GROUP ||
obj.second->getObjectType() == SAI_OBJECT_TYPE_ACL_ENTRY)
{
if (temp.m_preMatchMap.find(obj.second->getVid()) == temp.m_preMatchMap.end())
{
// object not in pre match
continue;
}
SWSS_LOG_INFO("processing explicit pre match: %s:%s",
obj.second->m_str_object_type.c_str(),
obj.second->m_str_object_id.c_str());
processObjectForViewTransition(current, temp, obj.second);
}
}
for (auto &obj: temp.m_soAll)
{
/*
* Make sure we will create all neighbor entries before next hop that
* have the same IP address as neighbor entry. In Broadcom platform
* neighbor entry needs to be created before next hop with the same ip
* address otherwise create next hop will fail (hardware limitation?).
*/
if (obj.second->getObjectType() == SAI_OBJECT_TYPE_NEIGHBOR_ENTRY)
{
processObjectForViewTransition(current, temp, obj.second);
}
}
for (auto &obj: temp.m_soAll)
{
if (obj.second->getObjectType() != SAI_OBJECT_TYPE_ROUTE_ENTRY)
{
processObjectForViewTransition(current, temp, obj.second);
}
}
for (auto &obj: temp.m_soAll)
{
if (obj.second->getObjectType() == SAI_OBJECT_TYPE_ROUTE_ENTRY)
{
bool isDefault = obj.second->m_str_object_id.find("/0") != std::string::npos;
if (isDefault)
{
processObjectForViewTransition(current, temp, obj.second);
}
}
}
for (auto &obj: temp.m_soAll)
{
if (obj.second->getObjectType() == SAI_OBJECT_TYPE_ROUTE_ENTRY)
{
bool isDefault = obj.second->m_str_object_id.find("/0") != std::string::npos;
if (!isDefault)
{
processObjectForViewTransition(current, temp, obj.second);
}
}
}
/*
* There is a problem here with default trap group, since when other trap
* groups are created and used in traps, then when removing them we reset
* trap group on traps to default one, and this increases reference count
* so in this loop, default trap group will not be considered to be removed
* and it will stay not processed since it will hold reference count on
* traps. So what we can do here, we can explicitly move default trap group
* to FINAL state if it's not defined in temporary view, but this problem
* should go away when we will put all existing objects to temporary view
* if they don't exist. This apply to v0.9.4 and v1.0
*
* Similar thing can happen when we start using PROFILE_ID on
* SCHEDULER_GROUP.
*
* TODO Revisit, since v1.0
*/
bringDefaultTrapGroupToFinalState(current, temp);
/*
* Removing needs to be done from leaf with no references and it can be
* multiple passes since if in first pass object had non zero references,
* it can have zero in next pass so it is safe to remove.
*/
/*
* Another issue, since user may remove bridge port and vlan members and we
* don't have references on them on the first place, then it may happen
* that bridge port remove will be before vlan member remove and it will
* fail. Similar problem is on hard reinit when removing existing objects.
*
* TODO we need dependency tree during sai discovery! but if we put those
* to redis it will cause another problem, since some of those attributes
* are create only, so object will be selected to "SET" after vid processing
* but it wont be able to set create only attributes, we would need to skip
* those.
*
* XXX this is workaround. FIXME
*/
std::vector<sai_object_type_t> removeOrder = {
SAI_OBJECT_TYPE_VLAN_MEMBER,
SAI_OBJECT_TYPE_STP_PORT,
SAI_OBJECT_TYPE_BRIDGE_PORT };
for (const sai_object_type_t ot: removeOrder)
{
for (const auto &obj: current.getObjectsByObjectType(ot))
{
if (obj->getObjectStatus() == SAI_OBJECT_STATUS_NOT_PROCESSED)
{
if (current.getVidReferenceCount(obj->getVid()) == 0)
{
removeExistingObjectFromCurrentView(current, temp, obj);
}
}
}
}
for (int removed = 1; removed != 0 ;)
{
removed = 0;
for (const auto &obj: current.getAllNotProcessedObjects())
{
/*
* What can happen during this processing some object state during
* processing can change from not processed to removed, if it have
* references, currently we are only removing objects with zero
* references, so this will not happen but in general case this
* will be the case.
*/
if (obj->isOidObject())
{
if (current.getVidReferenceCount(obj->getVid()) == 0)
{
/*
* Reference count on this VID is zero, so it's safe to
* remove this object.
*/
removeExistingObjectFromCurrentView(current, temp, obj);
removed++;
}
}
else
{
/*
* Non object id objects don't have references count, they are leafs
* so we can remove them right away
*/
removeExistingObjectFromCurrentView(current, temp, obj);
removed++;
}
}
if (removed)
{
SWSS_LOG_NOTICE("loop removed %d objects", removed);
}
}
/*
* Check statuses will make sure that there are no objects with removed
* status.
*/
}
void ComparisonLogic::logViewObjectCount(
_In_ const AsicView ¤tView,
_In_ const AsicView &temporaryView)
{
SWSS_LOG_ENTER();
bool asic_changes = false;
// skip null object type
for (size_t i = 1; i < sai_metadata_enum_sai_object_type_t.valuescount; ++i)
{
sai_object_type_t ot = (sai_object_type_t)sai_metadata_enum_sai_object_type_t.values[i];
size_t c = currentView.getObjectsByObjectType(ot).size();
size_t t = temporaryView.getObjectsByObjectType(ot).size();
if (c == t)
continue;
asic_changes = true;
SWSS_LOG_WARN("object count for %s on current view %zu is different than on temporary view: %zu",
sai_serialize_object_type(ot).c_str(),
c,
t);
}
if (asic_changes)
{
SWSS_LOG_WARN("object count is different on both view, there will be ASIC OPERATIONS!");
}
}
/*
* Below we will duplicate asic execution logic for asic operations.
*
* Since we have objects on our disposal and actual attributes, we don't need
* to do serialize/deserialize twice, we can take advantage of that.
* Only VID to RID translation is needed, and GET api is not supported
* anyway since comparison logic will do only SET/REMOVE/CREATE then
* all objects can be set to consts.
*
* We can address that when actual asic switch will be more time
* consuming than we expect.
*
* NOTE: Also instead of passing views everywhere we could close this in a
* class that will have 2 members current and temporary view. This could be
* helpful when we will have to manage multiple switches at the same time.
*/
sai_object_id_t ComparisonLogic::asic_translate_vid_to_rid(
_In_ const AsicView ¤t,
_In_ const AsicView &temporary,
_In_ sai_object_id_t vid)
{
SWSS_LOG_ENTER();
if (vid == SAI_NULL_OBJECT_ID)
{
return SAI_NULL_OBJECT_ID;
}
auto currentIt = current.m_vidToRid.find(vid);
if (currentIt == current.m_vidToRid.end())
{
/*
* If object was removed it RID was moved to removed map. This is
* required since at the end we need to check whether rid/vid maps are
* the same for both views.
*/
currentIt = current.m_removedVidToRid.find(vid);
if (currentIt == current.m_removedVidToRid.end())
{
SWSS_LOG_THROW("unable to find VID %s in current view",
sai_serialize_object_id(vid).c_str());
}
}
sai_object_id_t rid = currentIt->second;
SWSS_LOG_INFO("translated VID 0x%" PRIx64 " to RID 0x%" PRIx64, vid, rid);
return rid;
}
void ComparisonLogic::asic_translate_list_vid_to_rid(
_In_ const AsicView ¤t,
_In_ const AsicView &temporary,
_Inout_ sai_object_list_t &element)
{
SWSS_LOG_ENTER();
for (uint32_t i = 0; i < element.count; i++)
{
element.list[i] = asic_translate_vid_to_rid(current, temporary, element.list[i]);
}
}
void ComparisonLogic::asic_translate_vid_to_rid_list(
_In_ const AsicView ¤t,
_In_ const AsicView &temporary,
_In_ sai_object_type_t object_type,
_In_ uint32_t attr_count,
_Inout_ sai_attribute_t *attr_list)
{
SWSS_LOG_ENTER();
/*
* All id's received from view should be virtual, so lets translate them to
* real id's.
*/
for (uint32_t i = 0; i < attr_count; i++)
{
sai_attribute_t &attr = attr_list[i];
auto meta = sai_metadata_get_attr_metadata(object_type, attr.id);
// this should not happen we should get list right away from SaiAttr
if (meta == NULL)
{
SWSS_LOG_THROW("unable to get metadata for object type %s, attribute %d",
sai_serialize_object_type(object_type).c_str(),
attr.id);
/*
* Asic will be in inconsistent state at this point
*/
}
switch (meta->attrvaluetype)
{
case SAI_ATTR_VALUE_TYPE_OBJECT_ID:
attr.value.oid = asic_translate_vid_to_rid(current, temporary, attr.value.oid);
break;
case SAI_ATTR_VALUE_TYPE_OBJECT_LIST:
asic_translate_list_vid_to_rid(current, temporary, attr.value.objlist);
break;
case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_ID:
if (attr.value.aclfield.enable)
{
attr.value.aclfield.data.oid = asic_translate_vid_to_rid(current, temporary, attr.value.aclfield.data.oid);
}
break;
case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_LIST:
if (attr.value.aclfield.enable)
{
asic_translate_list_vid_to_rid(current, temporary, attr.value.aclfield.data.objlist);
}
break;
case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_ID:
if (attr.value.aclaction.enable)
{
attr.value.aclaction.parameter.oid = asic_translate_vid_to_rid(current, temporary, attr.value.aclaction.parameter.oid);
}
break;
case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_LIST:
if (attr.value.aclaction.enable)
{
asic_translate_list_vid_to_rid(current, temporary, attr.value.aclaction.parameter.objlist);
}
break;
default:
if (meta->isoidattribute)
{
SWSS_LOG_THROW("attribute %s is oid attribute, but not handled, FIXME", meta->attridname);
}
break;
}
}
}
sai_status_t ComparisonLogic::asic_handle_generic(
_In_ AsicView ¤t,
_In_ AsicView &temporary,
_In_ sai_object_meta_key_t &meta_key,
_In_ sai_common_api_t api,
_In_ uint32_t attr_count,
_In_ const sai_attribute_t *attr_list)
{
SWSS_LOG_ENTER();
SWSS_LOG_DEBUG("generic %s for %s",
sai_serialize_common_api(api).c_str(),
sai_serialize_object_type(meta_key.objecttype).c_str());
/*
* If we will use split for generics we can avoid meta key if we will
* generate inside metadata generic pointers for all oid objects + switch
* special method.
*
* And this needs to be executed in sai_meta_apis_query.
*/
sai_object_id_t object_id = meta_key.objectkey.key.object_id;
switch (api)
{
case SAI_COMMON_API_CREATE:
{
/*
* We need to get switch ID, but we know we have one switch so
* we could use shortcut here, but let's do this proper way.
*/
sai_object_id_t switch_vid = VidManager::switchIdQuery(object_id);
sai_object_id_t switch_rid = asic_translate_vid_to_rid(current, temporary, switch_vid);
sai_status_t status = m_vendorSai->create(meta_key, switch_rid, attr_count, attr_list);
sai_object_id_t real_object_id = meta_key.objectkey.key.object_id;
if (status == SAI_STATUS_SUCCESS)
{
current.m_ridToVid[real_object_id] = object_id;
current.m_vidToRid[object_id] = real_object_id;
temporary.m_ridToVid[real_object_id] = object_id;
temporary.m_vidToRid[object_id] = real_object_id;
std::string str_vid = sai_serialize_object_id(object_id);
std::string str_rid = sai_serialize_object_id(real_object_id);
SWSS_LOG_INFO("saved VID %s to RID %s", str_vid.c_str(), str_rid.c_str());
}
else
{
SWSS_LOG_ERROR("failed to create %s %s",
sai_serialize_object_type(meta_key.objecttype).c_str(),
sai_serialize_status(status).c_str());
}
return status;
}
case SAI_COMMON_API_REMOVE:
{
sai_object_id_t rid = asic_translate_vid_to_rid(current, temporary, object_id);
meta_key.objectkey.key.object_id = rid;
std::string str_vid = sai_serialize_object_id(object_id);
std::string str_rid = sai_serialize_object_id(rid);
/*
* Since object was removed, then we also need to remove it
* from m_removedVidToRid map just in case if there is some bug.
*/
current.m_removedVidToRid.erase(object_id);
sai_status_t status = m_vendorSai->remove(meta_key);
if (status != SAI_STATUS_SUCCESS)
{
// TODO get object attributes
SWSS_LOG_ERROR("remove %s RID: %s VID %s failed: %s",
sai_serialize_object_type(meta_key.objecttype).c_str(),
str_rid.c_str(),
str_vid.c_str(),
sai_serialize_status(status).c_str());
}
else
{
/*
* When we remove object which was existing on the switch
* then we need to remove it from the SaiSwitch, because
* later on it will lead to creating this object again
* since isNonRemovable object will return true.
*/
if (m_switch->isDiscoveredRid(rid))
{
m_switch->removeExistingObjectReference(rid);
}
}
return status;
}
case SAI_COMMON_API_SET:
{
sai_object_id_t rid = asic_translate_vid_to_rid(current, temporary, object_id);
meta_key.objectkey.key.object_id = rid;
sai_status_t status = m_vendorSai->set(meta_key, attr_list);
if (Workaround::isSetAttributeWorkaround(meta_key.objecttype, attr_list->id, status))
{
return SAI_STATUS_SUCCESS;
}
return status;
}
default:
SWSS_LOG_ERROR("other apis not implemented");
return SAI_STATUS_FAILURE;
}
}
void ComparisonLogic::asic_translate_vid_to_rid_non_object_id(
_In_ const AsicView ¤t,
_In_ const AsicView &temporary,
_In_ sai_object_meta_key_t &meta_key)
{
SWSS_LOG_ENTER();
auto info = sai_metadata_get_object_type_info(meta_key.objecttype);
if (info->isobjectid)
{
meta_key.objectkey.key.object_id =
asic_translate_vid_to_rid(current, temporary, meta_key.objectkey.key.object_id);
return;
}
for (size_t idx = 0; idx < info->structmemberscount; ++idx)
{
const sai_struct_member_info_t *m = info->structmembers[idx];
if (m->membervaluetype == SAI_ATTR_VALUE_TYPE_OBJECT_ID)
{
sai_object_id_t vid = m->getoid(&meta_key);
sai_object_id_t rid = asic_translate_vid_to_rid(current, temporary, vid);
m->setoid(&meta_key, rid);
}
}
}
sai_status_t ComparisonLogic::asic_handle_non_object_id(
_In_ const AsicView ¤t,
_In_ const AsicView &temporary,
_In_ sai_object_meta_key_t &meta_key,
_In_ sai_common_api_t api,
_In_ uint32_t attr_count,
_In_ const sai_attribute_t *attr_list)
{
SWSS_LOG_ENTER();
asic_translate_vid_to_rid_non_object_id(current, temporary, meta_key);
switch (api)
{
case SAI_COMMON_API_CREATE:
return m_vendorSai->create(meta_key, SAI_NULL_OBJECT_ID, attr_count, attr_list);
case SAI_COMMON_API_REMOVE:
return m_vendorSai->remove(meta_key);
case SAI_COMMON_API_SET:
return m_vendorSai->set(meta_key, attr_list);
default:
SWSS_LOG_ERROR("other apis not implemented");
return SAI_STATUS_FAILURE;
}
}
sai_status_t ComparisonLogic::asic_process_event(
_In_ AsicView& current,
_In_ AsicView& temporary,
_In_ const swss::KeyOpFieldsValuesTuple &kco)
{
SWSS_LOG_ENTER();
/*
* Since we have stored our keys in asic view, we can actually keep api
* type right away so we don't have to serialize and deserialize everything
* twice. We can take a look into that if update execution time will not be
* sufficient.
*/
const std::string &key = kfvKey(kco);
const std::string &op = kfvOp(kco);
sai_object_meta_key_t meta_key;
sai_deserialize_object_meta_key(key, meta_key);
SWSS_LOG_INFO("key: %s op: %s", key.c_str(), op.c_str());
if (m_enableRefernceCountLogs)
{
SWSS_LOG_NOTICE("ASIC OP BEFORE : key: %s op: %s", key.c_str(), op.c_str());
current.dumpRef("current before");
temporary.dumpRef("temp before");
}
sai_common_api_t api = SAI_COMMON_API_MAX;
// TODO convert those in common to use sai_common_api
if (op == "set")
{
/*
* Most common operation will probably be set so let put it first.
*/
api = SAI_COMMON_API_SET;
}
else if (op == "create")
{
api = SAI_COMMON_API_CREATE;
}
else if (op == "remove")
{
api = SAI_COMMON_API_REMOVE;
}
else
{
SWSS_LOG_ERROR("api %s is not implemented on %s", op.c_str(), key.c_str());
return SAI_STATUS_NOT_SUPPORTED;
}
std::stringstream ss;
sai_object_type_t object_type = meta_key.objecttype;
const std::vector<swss::FieldValueTuple> &values = kfvFieldsValues(kco);
SaiAttributeList list(object_type, values, false);
sai_attribute_t *attr_list = list.get_attr_list();
uint32_t attr_count = list.get_attr_count();
asic_translate_vid_to_rid_list(current, temporary, object_type, attr_count, attr_list);
sai_status_t status;
auto info = sai_metadata_get_object_type_info(object_type);
if (object_type == SAI_OBJECT_TYPE_SWITCH)
{
// only because user could change notifications he wanted to subscribe
m_handler->updateNotificationsPointers(attr_count, attr_list);
}
if (info->isnonobjectid)
{
status = asic_handle_non_object_id(current, temporary, meta_key, api, attr_count, attr_list);
}
else
{
status = asic_handle_generic(current, temporary, meta_key, api, attr_count, attr_list);
}
if (m_enableRefernceCountLogs)
{
SWSS_LOG_NOTICE("ASIC OP AFTER : key: %s op: %s", key.c_str(), op.c_str());
current.dumpRef("current after");
temporary.dumpRef("temp after");
}
if (status == SAI_STATUS_SUCCESS)
{
return status;
}
for (const auto &v: values)
{
SWSS_LOG_ERROR("field: %s, value: %s", fvField(v).c_str(), fvValue(v).c_str());
}
/*
* ASIC here will be in inconsistent state, we need to terminate.
*/
SWSS_LOG_THROW("failed to execute api: %s, key: %s, status: %s",
op.c_str(),
key.c_str(),
sai_serialize_status(status).c_str());
}
void ComparisonLogic::executeOperationsOnAsic()
{
SWSS_LOG_ENTER();
AsicView& currentView = *m_current;
AsicView& temporaryView = *m_temp;
SWSS_LOG_NOTICE("operations to execute on ASIC: %zu", currentView.asicGetOperationsCount());
try
{
//swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_INFO);
SWSS_LOG_TIMER("asic apply");
currentView.dumpVidToAsicOperatioId();
SWSS_LOG_NOTICE("NOT optimized operations");
for (const auto &op: currentView.asicGetOperations())
{
const std::string &key = kfvKey(*op.m_op);
const std::string &opp = kfvOp(*op.m_op);
SWSS_LOG_NOTICE("%s: %s", opp.c_str(), key.c_str());
const auto &values = kfvFieldsValues(*op.m_op);
if (op.m_currentValue.size() && opp == "set")
{
SWSS_LOG_NOTICE("- %s %s (current: %s)",
fvField(values.at(0)).c_str(),
fvValue(values.at(0)).c_str(),
op.m_currentValue.c_str());
}
else
{
for (auto v: values)
{
SWSS_LOG_NOTICE("- %s %s", fvField(v).c_str(), fvValue(v).c_str());
}
}
}
SWSS_LOG_NOTICE("optimized operations!");
std::map<std::string, int> opByObjectType;
for (const auto &op: currentView.asicGetWithOptimizedRemoveOperations())
{
const std::string &key = kfvKey(*op.m_op);
const std::string &opp = kfvOp(*op.m_op);
SWSS_LOG_NOTICE("%s: %s", opp.c_str(), key.c_str());
// count operations by object type
opByObjectType[key.substr(0, key.find(":"))]++;
}
for (auto kvp: opByObjectType)
{
SWSS_LOG_NOTICE("operations on %s: %d", kvp.first.c_str(), kvp.second);
}
//for (const auto &op: currentView.asicGetOperations())
for (const auto &op: currentView.asicGetWithOptimizedRemoveOperations())
{
/*
* It is possible that this method will throw exception in that case we
* also should exit syncd since we can be in the middle of executing
* operations and if some problems will happen and we continue to stay
* alive and next apply view, we will be in inconsistent state which
* will lead to unexpected behaviour.
*/
sai_status_t status = asic_process_event(currentView, temporaryView, *op.m_op);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_THROW("status of last operation was: %s, ASIC will be in inconsistent state, exiting",
sai_serialize_status(status).c_str());
}
}
}
catch (const std::exception &e)
{
SWSS_LOG_ERROR("Error while executing asic operations, ASIC is in inconsistent state: %s", e.what());
/*
* Re-throw exception to main loop.
*/
throw;
}
SWSS_LOG_NOTICE("performed all operations on asic successfully");
}
void ComparisonLogic::checkMap(
_In_ const AsicView& current,
_In_ const AsicView& temp) const
{
SWSS_LOG_ENTER();
if ((current.m_ridToVid.size() != temp.m_ridToVid.size()) ||
(current.m_vidToRid.size() != temp.m_vidToRid.size()))
{
// Check all possible differences.
checkMap(current.m_ridToVid, "current R2V", current.m_vidToRid, "current V2R", temp.m_ridToVid, "temp R2V", temp.m_vidToRid, "temp V2R");
checkMap(temp.m_ridToVid, "temp R2V", temp.m_vidToRid, "temp V2R", current.m_ridToVid, "current R2V", current.m_vidToRid, "current V2R");
current.dumpVidToAsicOperatioId();
SWSS_LOG_THROW("wrong number of vid/rid items in map, forgot to translate? R2V: %zu:%zu, V2R: %zu:%zu, FIXME",
current.m_ridToVid.size(),
temp.m_ridToVid.size(),
current.m_vidToRid.size(),
temp.m_vidToRid.size());
}
}