Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement functionality to connect RSU Health Monitor to multiple RSUs(4) from same instance #606

Merged
merged 12 commits into from
Apr 18, 2024
Merged
2 changes: 1 addition & 1 deletion src/v2i-hub/RSUHealthMonitorPlugin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ TARGET_LINK_LIBRARIES(${PROJECT_NAME} PUBLIC tmxutils jsoncpp NemaTode)
#############
enable_testing()
include_directories(${PROJECT_SOURCE_DIR}/src)
add_library(${PROJECT_NAME}_lib src/RSUHealthMonitorWorker.cpp)
add_library(${PROJECT_NAME}_lib src/RSUHealthMonitorWorker.cpp src/RSUConfigurationList)
target_link_libraries(${PROJECT_NAME}_lib PUBLIC
tmxutils
NemaTode
Expand Down
33 changes: 4 additions & 29 deletions src/v2i-hub/RSUHealthMonitorPlugin/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,10 @@
"default":"1",
"description": "Sending RSU SNMP GET request at every configured interval. Default every 1 second. Unit of measure: second."
},
{
"key":"RSUIp",
"default":"192.168.XX.XX",
"description":"An IP address of the RSU the V2X hub is connected to."
},
{
"key":"SNMPPort",
"default":"161",
"description":"The SNMP port for sending message or command."
},
{
"key":"AuthPassPhrase",
"default":"dummy",
"description":"SNMP v3 authentication passphrase"
},
{
"key":"SecurityUser",
"default":"authOnlyUser",
"description":"SNMP Security Name"
},
{
"key":"SecurityLevel",
"default":"authPriv",
"description":"SNMP Security level"
},
{
"key":"RSUMIBVersion",
"default":"RSU4.1",
"description":"The version of RSU MIB (Management Information Base). E.G. RSU4.1 or RSU1218. Currently only support RSU4.1"
{
"key":"RSUConfigurationList",
"default":"{ \"RSUS\": [ { \"RSUIp\": \"192.168.XX.XX\", \"SNMPPort\": \"161\", \"AuthPassPhrase\": \"dummy\", \"User\": \"authOnlyUser\", \"RSUMIBVersion\": \"RSU4.1\" },{ \"RSUIp\": \"192.168.00.XX\", \"SNMPPort\": \"162\", \"AuthPassPhrase\": \"tester\", \"User\": \"authPrivUser\", \"RSUMIBVersion\": \"RSU4.1\" }] }",
"description":"Configurations of the RSUs the V2X hub is connected to."
}
]
}
10 changes: 10 additions & 0 deletions src/v2i-hub/RSUHealthMonitorPlugin/src/RSUConfigurationException.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

namespace RSUHealthMonitor
{
class RSUConfigurationException : public std::runtime_error
{
public:
using runtime_error::runtime_error;
};
}
128 changes: 128 additions & 0 deletions src/v2i-hub/RSUHealthMonitorPlugin/src/RSUConfigurationList.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@

#include "RSUConfigurationList.h"

namespace RSUHealthMonitor
{
Json::Value RSUConfigurationList::parseJson(const std::string &rsuConfigsStr) const
{
JSONCPP_STRING err;
Json::Value root;
auto length = static_cast<int>(rsuConfigsStr.length());
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
if (!reader->parse(rsuConfigsStr.c_str(), rsuConfigsStr.c_str() + length, &root, &err))
{
std::stringstream ss;
ss << "Parse RSUs raw string error: " << err;
throw RSUConfigurationException(ss.str().c_str());
}
return root;
}

void RSUConfigurationList::parseRSUs(const std::string &rsuConfigsStr)
{
auto json = parseJson(rsuConfigsStr);
std::vector<RSUConfiguration> tempConfigs;
RSUConfiguration config;
auto rsuArray = json[RSUSKey];
if (!rsuArray.isArray())
{
throw RSUConfigurationException("RSUConfigurationList: Missing RSUS array.");
}
for (auto i = 0; i != rsuArray.size(); i++)
{
if (rsuArray[i].isMember(RSUIpKey))
{
config.rsuIp = rsuArray[i][RSUIpKey].asString();
}
else
{
auto errMsg = "RSUConfigurationList [" + std::to_string(i + 1) + "]: RSU IP [" + std::string(RSUIpKey) + "] is required.";
throw RSUConfigurationException(errMsg);
}

if (rsuArray[i].isMember(SNMPPortKey))
{
auto port = static_cast<uint16_t>(atoi(rsuArray[i][SNMPPortKey].asCString()));
auto errMsg = "RSUConfigurationList [" + std::to_string(i + 1) + "]: Invalid SNMP port number in string format.";
port != 0 ? config.snmpPort = port : throw RSUConfigurationException(errMsg);
}
else
{
auto errMsg = "RSUConfigurationList [" + std::to_string(i + 1) + "]: SNMP port [" + std::string(SNMPPortKey) + "] is required.";
throw RSUConfigurationException(errMsg);
}

if (rsuArray[i].isMember(AuthPassPhraseKey))
{
config.authPassPhrase = rsuArray[i][AuthPassPhraseKey].asString();
}
else
{
auto errMsg = "RSUConfigurationList [" + std::to_string(i + 1) + "]: Authentication pass phrase [" + std::string(AuthPassPhraseKey) + "] is required.";
throw RSUConfigurationException(errMsg);
}

if (rsuArray[i].isMember(UserKey))
{
config.user = rsuArray[i][UserKey].asString();
}
else
{
auto errMsg = "RSUConfigurationList [" + std::to_string(i + 1) + "]: User [" + std::string(UserKey) + "] is required.";
throw RSUConfigurationException(errMsg);
}

if (rsuArray[i].isMember(RSUMIBVersionKey))
{
auto rsuMIBVersionStr = rsuArray[i][RSUMIBVersionKey].asString();
config.mibVersion = strToMibVersion(rsuMIBVersionStr);
}
else
{
auto errMsg = "RSUConfigurationList [" + std::to_string(i + 1) + "]: RSU MIB version [" + std::string(RSUMIBVersionKey) + "] is required.";
throw RSUConfigurationException(errMsg);
}
tempConfigs.push_back(config);
}
// Only update RSU configurations when all configs are processed correctly.
configs.clear();
configs.assign(tempConfigs.begin(), tempConfigs.end());
}

RSUMibVersion RSUConfigurationList::strToMibVersion(std::string &mibVersionStr) const
{
boost::trim_left(mibVersionStr);
boost::trim_right(mibVersionStr);
// Only support RSU MIB version 4.1
if (boost::iequals(mibVersionStr, RSU4_1_str))
{
return RSUMibVersion::RSUMIB_V_4_1;
}
else
{
std::stringstream ss;
ss << "Uknown RSU MIB version: " << mibVersionStr;
throw RSUConfigurationException(ss.str().c_str());
}
}

std::vector<RSUConfiguration> RSUConfigurationList::getConfigs() const
{
return configs;
}

std::ostream &operator<<(std::ostream &os, const RSUMibVersion &mib)
{
const std::vector<std::string> nameMibs = {"UNKOWN MIB",
"RSU4.1",
"NTCIP1218"};
return os << nameMibs[static_cast<int>(mib)];
}

std::ostream &operator<<(std::ostream &os, const RSUConfiguration &config)
{
os << RSUIpKey << ": " << config.rsuIp << ", " << SNMPPortKey << ": " << config.snmpPort << ", " << UserKey << ": " << config.user << ", " << AuthPassPhraseKey << ": " << config.authPassPhrase << ", " << SecurityLevelKey << ": " << config.securityLevel << ", " << RSUMIBVersionKey << ": " << config.mibVersion;
return os;
}
}
65 changes: 65 additions & 0 deletions src/v2i-hub/RSUHealthMonitorPlugin/src/RSUConfigurationList.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#pragma once
#include <string>
#include <vector>
#include <iostream>
#include <jsoncpp/json/json.h>
#include <boost/algorithm/string.hpp>
#include "RSUConfigurationException.h"

namespace RSUHealthMonitor
{
static constexpr const char *RSUSKey = "RSUS";
static constexpr const char *RSUIpKey = "RSUIp";
static constexpr const char *SNMPPortKey = "SNMPPort";
static constexpr const char *UserKey = "User";
static constexpr const char *AuthPassPhraseKey = "AuthPassPhrase";
static constexpr const char *RSUMIBVersionKey = "RSUMIBVersion";
static constexpr const char *SecurityLevelKey = "SecurityLevel";
static constexpr const char *RSU4_1_str = "RSU4.1";
static constexpr const char *RSU1218_str = "RSU1218";

enum class RSUMibVersion
{
UNKOWN_MIB_V = 0,
RSUMIB_V_4_1 = 1,
RSUMIB_V_1218 = 2
};

struct RSUConfiguration
{
std::string rsuIp;
uint16_t snmpPort;
std::string user;
std::string authPassPhrase;
std::string securityLevel = "authPriv";
RSUMibVersion mibVersion;
friend std::ostream &operator<<(std::ostream &os, const RSUConfiguration &config);
};

class RSUConfigurationList
{
private:
std::vector<RSUConfiguration> configs;
/***
* @brief Parse JSON string and return the corresponding JSON value.
* @param rsuConfigsStr A JSON string includes all RSUs related configrations.
* @return JSON::Value A JSON object that includes RSUS information.
*/
Json::Value parseJson(const std::string &rsuConfigsStr) const;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does parse JSON return a JSON value. Isn't the purpose of this object to store the configuration. I would imagine either we have a constructor or initializer that reads in the string and updates the object or we have a function that returns and instance of the object.

RSUConfigurationList parse_rsu_config(const std::string &json) {
...
}
...
RSUConfigurationList rsuConfig = parse_rsu_config(jsonString)

Not saying this way is inherently wrong, just trying to understand why we return Json value and what your thought process was in setting it up this way.

Copy link
Collaborator Author

@dan-du-car dan-du-car Apr 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseJson is a function used by parseRSUS() which is where we store the config objects. parseRSUS() does not return RSUConfigurationList because it stores the result to instance variable.

RSUMibVersion strToMibVersion(std::string &mibVersionStr) const;

public:
RSUConfigurationList() = default;
~RSUConfigurationList() = default;
/**
* @brief Parse RSUs configrations in JSON string representation, and update the memeber of list of RSUConfiguration struct.
* @param rsuConfigsStr A JSON string includes all RSUs related configrations.
*/
void parseRSUs(const std::string &rsuConfigsStr);
/**
* @brief Get a list of RSUConfiguration struct.
*/
std::vector<RSUConfiguration> getConfigs() const;
};

} // namespace RSUHealthMonitor
45 changes: 20 additions & 25 deletions src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,44 @@ namespace RSUHealthMonitor
{
_rsuWorker = std::make_shared<RSUHealthMonitorWorker>();
_rsuStatusTimer = make_unique<ThreadTimer>();
_rsuConfigListPtr = std::make_shared<RSUConfigurationList>();
UpdateConfigSettings();

// Send SNMP call to RSU periodically at configurable interval.
_timerThId = _rsuStatusTimer->AddPeriodicTick([this]()
{
// Periodic SNMP call to get RSU status based on RSU MIB version 4.1
auto rsuStatusJson = _rsuWorker->getRSUStatus(_rsuMibVersion, _rsuIp, _snmpPort, _securityUser, _authPassPhrase, _securityLevel, SEC_TO_MICRO);
PLOG(logINFO) << "Updating _interval: " << _interval;
//Broadcast RSU status periodically at _interval
BroadcastRSUStatus(rsuStatusJson); },
this->monitorRSUs();
PLOG(logINFO) << "Monitoring RSU at interval (second): " << _interval; },
std::chrono::milliseconds(_interval * SEC_TO_MILLI));
_rsuStatusTimer->Start();
}

void RSUHealthMonitorPlugin::monitorRSUs()
{
for (auto rsuConfig : _rsuConfigListPtr->getConfigs())
{
auto rsuStatusJson = _rsuWorker->getRSUStatus(rsuConfig.mibVersion, rsuConfig.rsuIp, rsuConfig.snmpPort, rsuConfig.user, rsuConfig.authPassPhrase, rsuConfig.securityLevel, SEC_TO_MICRO);
BroadcastRSUStatus(rsuStatusJson, rsuConfig.mibVersion);
}
}

void RSUHealthMonitorPlugin::UpdateConfigSettings()
{
PLOG(logINFO) << "Updating configuration settings.";

lock_guard<mutex> lock(_configMutex);
GetConfigValue<uint16_t>("Interval", _interval);
GetConfigValue<string>("RSUIp", _rsuIp);
GetConfigValue<uint16_t>("SNMPPort", _snmpPort);
GetConfigValue<string>("AuthPassPhrase", _authPassPhrase);
GetConfigValue<string>("SecurityUser", _securityUser);
GetConfigValue<string>("SecurityLevel", _securityLevel);
GetConfigValue<string>("RSUMIBVersion", _rsuMIBVersionStr);
boost::trim_left(_rsuMIBVersionStr);
boost::trim_right(_rsuMIBVersionStr);
// Support RSU MIB version 4.1
if (boost::iequals(_rsuMIBVersionStr, RSU4_1_str))
{
_rsuMibVersion = RSUMibVersion::RSUMIB_V_4_1;
}
else
{
_rsuMibVersion = RSUMibVersion::UNKOWN_MIB_V;
PLOG(logERROR) << "Uknown RSU MIB version: " << _rsuMIBVersionStr;
}
GetConfigValue<string>("RSUConfigurationList", _rsuConfigListStr);

try
{
_rsuConfigListPtr->parseRSUs(_rsuConfigListStr);
_rsuStatusTimer->ChangeFrequency(_timerThId, std::chrono::milliseconds(_interval * SEC_TO_MILLI));
}
catch (const RSUConfigurationException &ex)
{
PLOG(logERROR) << "Cannot update RSU configurations due to error: " << ex.what();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do we want to happen when there is an error? Will this clear the previous list?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will use the previous list. No clear. In the parseRSUS() function, I left a comment saying that it will not clear the list until all configs are updated correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this what we want? Is it better to clear the list?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you provide some scenarios where we need to clear this?

}
catch (const tmx::TmxException &ex)
{
PLOG(logERROR) << ex.what();
Expand All @@ -65,13 +60,13 @@ namespace RSUHealthMonitor
UpdateConfigSettings();
}

void RSUHealthMonitorPlugin::BroadcastRSUStatus(const Json::Value &rsuStatusJson)
void RSUHealthMonitorPlugin::BroadcastRSUStatus(const Json::Value &rsuStatusJson, const RSUMibVersion &mibVersion)
{
// Broadcast the RSU status info when there are RSU responses.
if (!rsuStatusJson.empty() && _rsuWorker)
{
auto rsuStatusFields = _rsuWorker->getJsonKeys(rsuStatusJson);
auto configTbl = _rsuWorker->GetRSUStatusConfig(_rsuMibVersion);
auto configTbl = _rsuWorker->GetRSUStatusConfig(mibVersion);

// Only broadcast RSU status when all required fields are present.
if (_rsuWorker->validateAllRequiredFieldsPresent(configTbl, rsuStatusFields))
Expand Down
15 changes: 5 additions & 10 deletions src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <jsoncpp/json/json.h>
#include "RSUStatusMessage.h"
#include "RSUHealthMonitorWorker.h"
#include "RSUConfigurationList.h"

using namespace tmx::utils;
using namespace std;
Expand All @@ -17,15 +18,8 @@ namespace RSUHealthMonitor
private:
mutex _configMutex;
uint16_t _interval;
string _rsuIp;
uint16_t _snmpPort;
string _authPassPhrase;
string _securityUser;
string _securityLevel;
string _rsuMIBVersionStr;
RSUMibVersion _rsuMibVersion;
const char *RSU4_1_str = "RSU4.1";
const char *RSU1218_str = "RSU1218";
string _rsuConfigListStr;
shared_ptr<RSUConfigurationList> _rsuConfigListPtr;
shared_ptr<RSUHealthMonitorWorker> _rsuWorker;
unique_ptr<ThreadTimer> _rsuStatusTimer;
uint _timerThId;
Expand All @@ -35,12 +29,13 @@ namespace RSUHealthMonitor
* @brief Broadcast RSU status
* @param Json::Value RSU status in JSON format
*/
void BroadcastRSUStatus(const Json::Value& rsuStatusJson);
void BroadcastRSUStatus(const Json::Value &rsuStatusJson, const RSUMibVersion &mibVersion);

public:
explicit RSUHealthMonitorPlugin(const std::string &name);
void UpdateConfigSettings();
void OnConfigChanged(const char *key, const char *value) override;
void monitorRSUs();
};

} // namespace RSUHealthMonitorPlugin
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ namespace RSUHealthMonitor
try
{
// Create SNMP client and use SNMP V3 protocol
PLOG(logINFO) << "Update SNMP client: RSU IP: " << _rsuIp << ", RSU port: " << _snmpPort << ", User: " << _securityUser << ", auth pass phrase: " << _authPassPhrase << ", security level: "
PLOG(logINFO) << "SNMP client: RSU IP: " << _rsuIp << ", RSU port: " << _snmpPort << ", User: " << _securityUser << ", auth pass phrase: " << _authPassPhrase << ", security level: "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue for this PR but we should figure out how we want to handle external authentication information. Having it written to a log is probably bad practice. Having it stored anywhere as plain text is probably bad practice. Not sure what convention for this is.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this authentication info provided from the UI?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that is why I think this is probably not solvable right now but eventually we want to hide the authentication information you enter similar to a login.

<< _securityLevel;
auto _snmpClientPtr = std::make_unique<snmp_client>(_rsuIp, _snmpPort, "", _securityUser, _securityLevel, _authPassPhrase, SNMP_VERSION_3, timeout);

Expand Down
Loading
Loading