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