Skip to content

Commit

Permalink
Implement functionality to connect RSU Health Monitor to multiple RSU…
Browse files Browse the repository at this point in the history
…s(4) from same instance (#606)

<!-- Thanks for the contribution, this is awesome. -->

# PR Details
## Description
Update the V2X Hub's RSU Health Monitor plugin supports monitoring the
health status of multiple Roadside Units (RSUs) per V2X Hub instance.

<!--- Describe your changes in detail -->

## Related Issue
NA
<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an
issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps
to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context
NA
<!--- Why is this change required? What problem does it solve? -->

## How Has This Been Tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran
to -->
<!--- see how your change affects other areas of the code, etc. -->

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->

- [ ] Defect fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that cause existing functionality
to change)

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->

- [ ] I have added any new packages to the sonar-scanner.properties file
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
[V2XHUB Contributing
Guide](https://github.com/usdot-fhwa-OPS/V2X-Hub/blob/develop/Contributing.md)
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
  • Loading branch information
dan-du-car authored Apr 18, 2024
1 parent ff9dd7c commit c1759dd
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 72 deletions.
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;
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();
}
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: "
<< _securityLevel;
auto _snmpClientPtr = std::make_unique<snmp_client>(_rsuIp, _snmpPort, "", _securityUser, _securityLevel, _authPassPhrase, SNMP_VERSION_3, timeout);

Expand Down
Loading

0 comments on commit c1759dd

Please sign in to comment.