Persist the snmp manager configuration
This commit persist the manager configuration D-Bus objects
and restores it once service starts.
This commit also deletes the associated persistent file whenever
snmp client D-Bus object gets deleted.
Change-Id: I1526b52870ee5dfea30e6527bad3fd12d1191a13
Signed-off-by: Ratan Gupta <ratagupt@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 015a558..c133dd3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,13 +10,15 @@
noinst_HEADERS = \
snmp_client.hpp \
snmp_conf_manager.hpp \
- snmp_util.hpp
+ snmp_util.hpp \
+ snmp_serialize.hpp
phosphor_network_snmpconf_SOURCES = \
snmp_main.cpp \
snmp_conf_manager.cpp \
snmp_client.cpp \
snmp_util.cpp \
+ snmp_serialize.cpp \
xyz/openbmc_project/Network/Client/Create/server.cpp
CLEANFILES = \
diff --git a/configure.ac b/configure.ac
index a0cdbe2..f73d576 100644
--- a/configure.ac
+++ b/configure.ac
@@ -48,6 +48,16 @@
AS_IF([test "x$OBJ_NETWORK_SNMP" == "x"], [OBJ_NETWORK_SNMP="/xyz/openbmc_project/network/snmp/manager"])
AC_DEFINE_UNQUOTED([OBJ_NETWORK_SNMP], ["$OBJ_NETWORK_SNMP"], [The network snmp root DBus object path])
+AC_ARG_VAR(CLASS_VERSION, [Class version to register with Cereal])
+AS_IF([test "x$CLASS_VERSION" == "x"], [CLASS_VERSION=1])
+AC_DEFINE_UNQUOTED([CLASS_VERSION], [$CLASS_VERSION], [Class version to register with Cereal])
+
+AC_ARG_VAR(SNMP_CONF_PERSIST_PATH, [path of directory having persisted snmp managers.])
+AS_IF([test "x$SNMP_CONF_PERSIST_PATH" == "x"], \
+ [SNMP_CONF_PERSIST_PATH="/var/lib/phosphor-snmp/managers/"])
+AC_DEFINE_UNQUOTED([SNMP_CONF_PERSIST_PATH], ["$SNMP_CONF_PERSIST_PATH"], \
+ [Path of directory having persisted snmp managers.])
+
# Checks for library functions
LT_INIT # Required for systemd linking
diff --git a/snmp_client.hpp b/snmp_client.hpp
index 5b27f1b..fbc76fb 100644
--- a/snmp_client.hpp
+++ b/snmp_client.hpp
@@ -46,6 +46,16 @@
Client(sdbusplus::bus::bus &bus, const char *objPath, ConfManager &parent,
const std::string &address, uint16_t port);
+ /** @brief Constructor to put object onto bus at a dbus path.
+ * @param[in] bus - Bus to attach to.
+ * @param[in] objPath - Path to attach at.
+ * @param[in] parent - Parent D-bus Object.
+ */
+ Client(sdbusplus::bus::bus &bus, const char *objPath, ConfManager &parent) :
+ Ifaces(bus, objPath, true), parent(parent)
+ {
+ }
+
/** @brief Delete this d-bus object.
*/
void delete_() override;
diff --git a/snmp_conf_manager.cpp b/snmp_conf_manager.cpp
index 3c7eaa6..831fd4b 100644
--- a/snmp_conf_manager.cpp
+++ b/snmp_conf_manager.cpp
@@ -1,5 +1,6 @@
#include "config.h"
#include "snmp_conf_manager.hpp"
+#include "snmp_serialize.hpp"
#include "snmp_util.hpp"
#include "xyz/openbmc_project/Common/error.hpp"
@@ -22,7 +23,9 @@
using Argument = xyz::openbmc_project::Common::InvalidArgument;
ConfManager::ConfManager(sdbusplus::bus::bus& bus, const char* objPath) :
- details::CreateIface(bus, objPath, true), bus(bus), objectPath(objPath)
+ details::CreateIface(bus, objPath, true),
+ dbusPersistentLocation(SNMP_CONF_PERSIST_PATH), bus(bus),
+ objectPath(objPath)
{
}
@@ -51,9 +54,12 @@
objPath /= objectPath;
objPath /= generateId(address, port);
- this->clients.emplace(
- address, std::make_unique<phosphor::network::snmp::Client>(
- bus, objPath.string().c_str(), *this, address, port));
+ auto client = std::make_unique<phosphor::network::snmp::Client>(
+ bus, objPath.string().c_str(), *this, address, port);
+ // save the D-Bus object
+ serialize(*client, dbusPersistentLocation);
+
+ this->clients.emplace(address, std::move(client));
}
std::string ConfManager::generateId(const std::string& address, uint16_t port)
@@ -76,9 +82,65 @@
entry("ADDRESS=%s", address.c_str()));
return;
}
+
+ std::error_code ec;
+ // remove the persistent file
+ fs::path fileName = dbusPersistentLocation;
+ fileName /=
+ it->second->address() + SEPRATOR + std::to_string(it->second->port());
+
+ if (fs::exists(fileName))
+ {
+ if (!fs::remove(fileName, ec))
+ {
+ log<level::ERR>("Unable to delete the file",
+ entry("FILE=%s", fileName.c_str()),
+ entry("ERROR=%d", ec.value()));
+ }
+ }
+ else
+ {
+ log<level::ERR>("File doesn't exist",
+ entry("FILE=%s", fileName.c_str()));
+ }
+ // remove the D-Bus Object.
this->clients.erase(it);
}
+void ConfManager::restoreClients()
+{
+ if (!fs::exists(dbusPersistentLocation) ||
+ fs::is_empty(dbusPersistentLocation))
+ {
+ return;
+ }
+
+ for (auto& confFile :
+ fs::recursive_directory_iterator(dbusPersistentLocation))
+ {
+ if (!fs::is_regular_file(confFile))
+ {
+ continue;
+ }
+
+ auto managerID = confFile.path().filename().string();
+ auto pos = managerID.find(SEPRATOR);
+ auto ipaddress = managerID.substr(0, pos);
+ auto port_str = managerID.substr(pos + 1);
+ uint16_t port = stoi(port_str, nullptr);
+
+ fs::path objPath = objectPath;
+ objPath /= generateId(ipaddress, port);
+ auto manager =
+ std::make_unique<Client>(bus, objPath.string().c_str(), *this);
+ if (deserialize(confFile.path(), *manager))
+ {
+ manager->emit_object_added();
+ this->clients.emplace(ipaddress, std::move(manager));
+ }
+ }
+}
+
} // namespace snmp
} // namespace network
} // namespace phosphor
diff --git a/snmp_conf_manager.hpp b/snmp_conf_manager.hpp
index 0f1ee4b..d9164ae 100644
--- a/snmp_conf_manager.hpp
+++ b/snmp_conf_manager.hpp
@@ -5,6 +5,7 @@
#include <xyz/openbmc_project/Network/Client/Create/server.hpp>
#include <sdbusplus/bus.hpp>
+#include <experimental/filesystem>
#include <string>
namespace phosphor
@@ -13,8 +14,10 @@
{
namespace snmp
{
+
using IPAddress = std::string;
using ClientList = std::map<IPAddress, std::unique_ptr<Client>>;
+namespace fs = std::experimental::filesystem;
namespace details
{
@@ -38,7 +41,7 @@
ConfManager& operator=(ConfManager&&) = delete;
virtual ~ConfManager() = default;
- /** @brief Constructor to put object onto bus at a dbus path.
+ /** @brief Constructor to put object onto bus at a D-Bus path.
* @param[in] bus - Bus to attach to.
* @param[in] objPath - Path to attach at.
*/
@@ -50,11 +53,19 @@
*/
void client(std::string address, uint16_t port) override;
- /* @brief delete the dbus object of the given ipaddress.
+ /* @brief delete the D-Bus object of the given ipaddress.
* @param[in] address - IP address/Hostname.
*/
void deleteSNMPClient(const std::string& address);
+ /** @brief Construct manager/client D-Bus objects from their persisted
+ * representations.
+ */
+ void restoreClients();
+
+ /** @brief location of the persisted D-Bus object.*/
+ fs::path dbusPersistentLocation;
+
protected:
/** @brief generates the id by doing hash of ipaddress, port
* @param[in] address - IP address/Hostname.
diff --git a/snmp_main.cpp b/snmp_main.cpp
index d11ed1e..123c05e 100644
--- a/snmp_main.cpp
+++ b/snmp_main.cpp
@@ -45,5 +45,7 @@
auto manager = std::make_unique<phosphor::network::snmp::ConfManager>(
bus, OBJ_NETWORK_SNMP);
+ manager->restoreClients();
+
return sd_event_loop(eventPtr.get());
}
diff --git a/snmp_notification.cpp b/snmp_notification.cpp
index b0df691..5f2aea9 100644
--- a/snmp_notification.cpp
+++ b/snmp_notification.cpp
@@ -134,9 +134,9 @@
log<level::ERR>("Failed to send the snmp trap.");
elog<InternalFailure>();
}
- }
- log<level::DEBUG>("Sent SNMP Trap");
+ log<level::DEBUG>("Sent SNMP Trap", entry("MGR=%s", mgr.c_str()));
+ }
}
} // namespace snmp
diff --git a/snmp_serialize.cpp b/snmp_serialize.cpp
new file mode 100644
index 0000000..ea47945
--- /dev/null
+++ b/snmp_serialize.cpp
@@ -0,0 +1,99 @@
+#include <cereal/types/string.hpp>
+#include <cereal/types/vector.hpp>
+#include <cereal/archives/binary.hpp>
+#include <fstream>
+
+#include "snmp_serialize.hpp"
+#include "snmp_client.hpp"
+#include <phosphor-logging/log.hpp>
+#include "config.h"
+
+// Register class version
+// From cereal documentation;
+// "This macro should be placed at global scope"
+CEREAL_CLASS_VERSION(phosphor::network::snmp::Client, CLASS_VERSION);
+
+namespace phosphor
+{
+namespace network
+{
+namespace snmp
+{
+
+using namespace phosphor::logging;
+
+/** @brief Function required by Cereal to perform serialization.
+ * @tparam Archive - Cereal archive type (binary in our case).
+ * @param[in] archive - reference to Cereal archive.
+ * @param[in] manager - const reference to snmp manager info.
+ * @param[in] version - Class version that enables handling
+ * a serialized data across code levels
+ */
+template <class Archive>
+void save(Archive& archive, const Client& manager, const std::uint32_t version)
+{
+ archive(manager.address(), manager.port());
+}
+
+/** @brief Function required by Cereal to perform deserialization.
+ * @tparam Archive - Cereal archive type (binary in our case).
+ * @param[in] archive - reference to Cereal archive.
+ * @param[in] manager - reference to snmp manager info.
+ * @param[in] version - Class version that enables handling
+ * a serialized data across code levels
+ */
+template <class Archive>
+void load(Archive& archive, Client& manager, const std::uint32_t version)
+{
+ std::string ipaddress{};
+ uint16_t port{};
+
+ archive(ipaddress, port);
+
+ manager.address(ipaddress);
+ manager.port(port);
+}
+
+fs::path serialize(const Client& manager, const fs::path& dir)
+{
+ fs::path fileName = dir;
+ fs::create_directories(dir);
+ auto address = manager.address();
+ auto port = manager.port();
+ fileName /= address + SEPRATOR + std::to_string(port);
+
+ std::ofstream os(fileName.string(), std::ios::binary);
+ cereal::BinaryOutputArchive oarchive(os);
+ oarchive(manager);
+ return fileName;
+}
+
+bool deserialize(const fs::path& path, Client& manager)
+{
+ try
+ {
+ if (fs::exists(path))
+ {
+ std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
+ cereal::BinaryInputArchive iarchive(is);
+ iarchive(manager);
+ return true;
+ }
+ return false;
+ }
+ catch (cereal::Exception& e)
+ {
+ log<level::ERR>(e.what());
+ std::error_code ec;
+ fs::remove(path, ec);
+ return false;
+ }
+ catch (const fs::filesystem_error& e)
+ {
+ return false;
+ }
+}
+
+} // namespace snmp
+} // namespace network
+} // namespace phosphor
diff --git a/snmp_serialize.hpp b/snmp_serialize.hpp
new file mode 100644
index 0000000..044d784
--- /dev/null
+++ b/snmp_serialize.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <experimental/filesystem>
+#include "snmp_client.hpp"
+
+namespace phosphor
+{
+namespace network
+{
+namespace snmp
+{
+
+constexpr auto SEPRATOR = "_";
+
+namespace fs = std::experimental::filesystem;
+
+/** @brief Serialize and persist SNMP manager/client D-Bus object
+ * @param[in] manager - const reference to snmp client/manager object.
+ * @param[in] path - path of persistent location where D-Bus object would be
+ * saved.
+ * @return fs::path - pathname of persisted snmp manager/client file.
+ */
+fs::path serialize(const Client& manager, const fs::path& path);
+
+/** @brief Deserialze SNMP manager/client info into a D-Bus object
+ * @param[in] path - pathname of persisted manager/client file.
+ * @param[in] manager - reference to snmp client/manager object
+ * which is the target of deserialization.
+ * @return bool - true if the deserialization was successful, false otherwise.
+ */
+bool deserialize(const fs::path& path, Client& manager);
+
+} // namespace snmp
+} // namespace network
+} // namespace phosphor
diff --git a/test/Makefile.am.include b/test/Makefile.am.include
index f99ab24..97b018c 100644
--- a/test/Makefile.am.include
+++ b/test/Makefile.am.include
@@ -30,10 +30,12 @@
test_notification_SOURCES = \
%reldir%/test_error_notification.cpp \
%reldir%/test_snmp_conf_manager.cpp \
- %reldir%/test_snmp_util.cpp
+ %reldir%/test_snmp_util.cpp \
+ %reldir%/test_snmp_serialize.cpp
test_notification_LDADD = $(top_builddir)/phosphor_network_snmpconf-snmp_conf_manager.o \
$(top_builddir)/phosphor_network_snmpconf-snmp_client.o \
+ $(top_builddir)/phosphor_network_snmpconf-snmp_serialize.o \
$(top_builddir)/phosphor_network_snmpconf-snmp_util.o \
$(top_builddir)/xyz/openbmc_project/Network/Client/Create/phosphor_network_snmpconf-server.o
diff --git a/test/test_snmp_conf_manager.cpp b/test/test_snmp_conf_manager.cpp
index ab29215..ebf492f 100644
--- a/test/test_snmp_conf_manager.cpp
+++ b/test/test_snmp_conf_manager.cpp
@@ -18,11 +18,16 @@
sdbusplus::bus::bus bus;
ConfManager manager;
std::string confDir;
- TestSNMPConfManager() :
- bus(sdbusplus::bus::new_default()), manager(bus, ""){};
+ TestSNMPConfManager() : bus(sdbusplus::bus::new_default()), manager(bus, "")
+ {
+ char tmp[] = "/tmp/snmpManager.XXXXXX";
+ std::string confDir = mkdtemp(tmp);
+ manager.dbusPersistentLocation = confDir;
+ }
~TestSNMPConfManager()
{
+ fs::remove_all(manager.dbusPersistentLocation);
}
void createSNMPClient(std::string ipaddress, uint16_t port)
@@ -37,11 +42,8 @@
void deleteSNMPClient(std::string ipaddress)
{
- if (manager.clients.find(ipaddress) != manager.clients.end())
- {
- auto &it = manager.clients[ipaddress];
- it->delete_();
- }
+ auto &it = manager.clients[ipaddress];
+ it->delete_();
}
};
diff --git a/test/test_snmp_serialize.cpp b/test/test_snmp_serialize.cpp
new file mode 100644
index 0000000..60a51aa
--- /dev/null
+++ b/test/test_snmp_serialize.cpp
@@ -0,0 +1,82 @@
+#include <experimental/filesystem>
+#include <fstream>
+#include <gtest/gtest.h>
+#include <netinet/in.h>
+
+#include "snmp_client.hpp"
+#include "snmp_conf_manager.hpp"
+#include "snmp_serialize.hpp"
+
+namespace phosphor
+{
+namespace network
+{
+namespace snmp
+{
+
+constexpr auto clientObjPath = "/xyz/openbmc_test/snmp/client";
+namespace fs = std::experimental::filesystem;
+
+class TestSerialize : public testing::Test
+{
+ public:
+ sdbusplus::bus::bus bus;
+ ConfManager manager;
+ TestSerialize() :
+ bus(sdbusplus::bus::new_default()),
+ manager(bus, "/xyz/openbmc_test/snmp/manager")
+ {
+ char tmp[] = "/tmp/snmpManager.XXXXXX";
+ std::string confDir = mkdtemp(tmp);
+ manager.dbusPersistentLocation = confDir;
+ }
+ ~TestSerialize()
+ {
+ std::error_code ec;
+ fs::remove_all(manager.dbusPersistentLocation, ec);
+ }
+};
+
+TEST_F(TestSerialize, serialize)
+{
+ Client client(bus, clientObjPath, manager, "1.1.1.1", 23);
+
+ auto path = serialize(client, manager.dbusPersistentLocation);
+ Client restoreClient(bus, clientObjPath, manager);
+
+ deserialize(path, restoreClient);
+
+ EXPECT_EQ("1.1.1.1", restoreClient.address());
+ EXPECT_EQ(23, restoreClient.port());
+}
+
+TEST_F(TestSerialize, deserialize_non_existent_file)
+{
+ Client client(bus, clientObjPath, manager);
+ fs::path path = manager.dbusPersistentLocation;
+ path /= "snmpTest";
+
+ auto ret = deserialize(path, client);
+
+ EXPECT_EQ(false, ret);
+}
+
+TEST_F(TestSerialize, deserialize_empty_file)
+{
+ Client restoreClient(bus, clientObjPath, manager);
+
+ std::fstream file;
+
+ fs::path path = manager.dbusPersistentLocation;
+ path /= "snmpTest";
+
+ file.open(path.string(), std::ofstream::out);
+ file.close();
+ // deserialize the object with empty file
+ auto ret = deserialize(path, restoreClient);
+ EXPECT_EQ(false, ret);
+}
+
+} // namespace snmp
+} // namespace network
+} // namespace phosphor