vslib/SaiUnittests.cpp (239 lines of code) (raw):
#include "Sai.h"
#include "SaiInternal.h"
#include "saivs.h"
#include "meta/sai_serialize.h"
#include "swss/logger.h"
#include "swss/select.h"
using namespace saivs;
void Sai::startUnittestThread()
{
SWSS_LOG_ENTER();
m_unittestChannelThreadEvent = std::make_shared<swss::SelectableEvent>();
auto ctx = getContext(m_globalContext);
m_dbNtf = std::make_shared<swss::DBConnector>(ctx->getContextConfig()->m_dbAsic, 0);
m_unittestChannelNotificationConsumer = std::make_shared<swss::NotificationConsumer>(m_dbNtf.get(), SAI_VS_UNITTEST_CHANNEL);
m_unittestChannelRun = true;
m_unittestChannelThread = std::make_shared<std::thread>(std::thread(&Sai::unittestChannelThreadProc, this));
}
void Sai::stopUnittestThread()
{
SWSS_LOG_ENTER();
SWSS_LOG_NOTICE("begin");
if (m_unittestChannelRun)
{
m_unittestChannelRun = false;
// notify thread that it should end
m_unittestChannelThreadEvent->notify();
m_unittestChannelThread->join();
}
SWSS_LOG_NOTICE("end");
}
void Sai::channelOpEnableUnittest(
_In_ const std::string &key,
_In_ const std::vector<swss::FieldValueTuple> &values)
{
SWSS_LOG_ENTER();
bool enable = (key == "true");
m_meta->meta_unittests_enable(enable);
}
void Sai::channelOpSetReadOnlyAttribute(
_In_ const std::string &key,
_In_ const std::vector<swss::FieldValueTuple> &values)
{
SWSS_LOG_ENTER();
for (const auto &v: values)
{
SWSS_LOG_DEBUG("attr: %s: %s", fvField(v).c_str(), fvValue(v).c_str());
}
if (values.size() != 1)
{
SWSS_LOG_ERROR("expected 1 value only, but given: %zu", values.size());
return;
}
const std::string &str_object_type = key.substr(0, key.find(":"));
const std::string &str_object_id = key.substr(key.find(":") + 1);
sai_object_type_t object_type;
sai_deserialize_object_type(str_object_type, object_type);
if (sai_metadata_is_object_type_valid(object_type) == false)
{
SWSS_LOG_ERROR("invalid object type: %d", object_type);
return;
}
auto info = sai_metadata_get_object_type_info(object_type);
if (info->isnonobjectid)
{
SWSS_LOG_ERROR("non object id %s is not supported yet", str_object_type.c_str());
return;
}
sai_object_id_t object_id;
sai_deserialize_object_id(str_object_id, object_id);
sai_object_type_t ot = objectTypeQuery(object_id);
if (ot != object_type)
{
SWSS_LOG_ERROR("object type is different than provided %s, but oid is %s",
str_object_type.c_str(), sai_serialize_object_type(ot).c_str());
return;
}
sai_object_id_t switch_id = switchIdQuery(object_id);
if (switch_id == SAI_NULL_OBJECT_ID)
{
SWSS_LOG_ERROR("failed to find switch id for oid %s", str_object_id.c_str());
return;
}
// oid is validated and we got switch id
const std::string &str_attr_id = fvField(values.at(0));
const std::string &str_attr_value = fvValue(values.at(0));
auto meta = sai_metadata_get_attr_metadata_by_attr_id_name(str_attr_id.c_str());
if (meta == NULL)
{
SWSS_LOG_ERROR("failed to find attr %s", str_attr_id.c_str());
return;
}
if (meta->objecttype != ot)
{
SWSS_LOG_ERROR("attr %s belongs to different object type than oid: %s",
str_attr_id.c_str(), sai_serialize_object_type(ot).c_str());
return;
}
// we got attr metadata
sai_attribute_t attr;
attr.id = meta->attrid;
sai_deserialize_attr_value(str_attr_value, *meta, attr);
SWSS_LOG_NOTICE("switch id is %s", sai_serialize_object_id(switch_id).c_str());
sai_status_t status = m_meta->meta_unittests_allow_readonly_set_once(meta->objecttype, meta->attrid);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_ERROR("failed to enable SET readonly attribute once: %s", sai_serialize_status(status).c_str());
return;
}
sai_object_meta_key_t meta_key = { .objecttype = ot, .objectkey = { .key = { .object_id = object_id } } };
status = m_meta->set(meta_key, &attr); // name hidden in base class (but same name overloaded in derived class)
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_ERROR("failed to set %s to %s on %s",
str_attr_id.c_str(), str_attr_value.c_str(), str_object_id.c_str());
}
else
{
SWSS_LOG_NOTICE("SUCCESS to set %s to %s on %s",
str_attr_id.c_str(), str_attr_value.c_str(), str_object_id.c_str());
}
sai_deserialize_free_attribute_value(meta->attrvaluetype, attr);
}
void Sai::channelOpSetStats(
_In_ const std::string &key,
_In_ const std::vector<swss::FieldValueTuple> &values)
{
SWSS_LOG_ENTER();
// NOTE: add support for non object id stats
sai_object_id_t oid;
sai_deserialize_object_id(key, oid);
sai_object_type_t ot = objectTypeQuery(oid);
if (ot == SAI_OBJECT_TYPE_NULL)
{
SWSS_LOG_ERROR("invalid object id: %s", key.c_str());
return;
}
sai_object_id_t switch_id = switchIdQuery(oid);
if (switch_id == SAI_NULL_OBJECT_ID)
{
SWSS_LOG_ERROR("unable to get switch_id from oid: %s", key.c_str());
return;
}
auto statenum = sai_metadata_get_object_type_info(ot)->statenum;
if (statenum == NULL)
{
SWSS_LOG_ERROR("object %s does not support statistics",
sai_serialize_object_type(ot).c_str());
return;
}
/*
* Check if object for statistics have statistic map created, if not
* create empty map.
*/
std::map<sai_stat_id_t, uint64_t> stats;
for (auto v: values)
{
// value format: stat_enum_name:uint64
auto name = fvField(v);
auto val = fvValue(v);
uint64_t value;
if (sscanf(val.c_str(), "%" PRIu64, &value) != 1)
{
SWSS_LOG_ERROR("failed to deserialize %s as counter value uint64_t", val.c_str());
continue;
}
// linear search
int enumvalue = -1;
for (size_t i = 0; i < statenum->valuescount; ++i)
{
if (statenum->valuesnames[i] == name)
{
enumvalue = statenum->values[i];
break;
}
}
if (enumvalue == -1)
{
SWSS_LOG_ERROR("failed to find enum value: %s", name.c_str());
continue;
}
SWSS_LOG_INFO("writing %s = %lu on %s", name.c_str(), value, key.c_str());
stats[enumvalue] = value;
}
m_vsSai->debugSetStats(oid, stats);
}
void Sai::handleUnittestChannelOp(
_In_ const std::string &op,
_In_ const std::string &key,
_In_ const std::vector<swss::FieldValueTuple> &values)
{
MUTEX();
SWSS_LOG_ENTER();
// at this point api are initialized since unit test thread is started if
// initialization will be successful
/*
* Since we will access and modify DB we need to be under mutex.
*
* NOTE: since this unittest channel is handled in thread, then that means
* there is a DELAY from producer and consumer thread in VS, so if user
* will set value on the specific READ_ONLY value he should wait for some
* time until that value will be propagated to virtual switch.
*/
SWSS_LOG_NOTICE("op = %s, key = %s", op.c_str(), key.c_str());
for (const auto &v: values)
{
SWSS_LOG_NOTICE("attr: %s: %s", fvField(v).c_str(), fvValue(v).c_str());
}
if (op == SAI_VS_UNITTEST_ENABLE_UNITTESTS)
{
channelOpEnableUnittest(key, values);
}
else if (op == SAI_VS_UNITTEST_SET_RO_OP)
{
channelOpSetReadOnlyAttribute(key, values);
}
else if (op == SAI_VS_UNITTEST_SET_STATS_OP)
{
channelOpSetStats(key, values);
}
else
{
SWSS_LOG_THROW("unknown unittest operation: %s", op.c_str());
}
}
void Sai::unittestChannelThreadProc()
{
SWSS_LOG_ENTER();
SWSS_LOG_NOTICE("enter VS unittest channel thread");
swss::Select s;
s.addSelectable(m_unittestChannelNotificationConsumer.get());
s.addSelectable(m_unittestChannelThreadEvent.get());
while (m_unittestChannelRun)
{
swss::Selectable *sel = nullptr;
int result = s.select(&sel);
if (sel == m_unittestChannelThreadEvent.get())
{
// user requested shutdown_switch
break;
}
if (result == swss::Select::OBJECT)
{
swss::KeyOpFieldsValuesTuple kco;
std::string op;
std::string data;
std::vector<swss::FieldValueTuple> values;
m_unittestChannelNotificationConsumer->pop(op, data, values);
SWSS_LOG_DEBUG("notification: op = %s, data = %s", op.c_str(), data.c_str());
try
{
handleUnittestChannelOp(op, data, values);
}
catch (const std::exception &e)
{
SWSS_LOG_ERROR("Exception: op = %s, data = %s, %s", op.c_str(), data.c_str(), e.what());
}
}
}
SWSS_LOG_NOTICE("exit VS unittest channel thread");
}