Implement the Client create interface

This commit also implement the D-Bus service which would be
used for snmp client configuration and would add the
snmp manager/trap receiver D-Bus objects under
namespace /xyz/openbmc_project/network/snmp/manager/

It implements the delete interface for SNMP client D-Bus Object.

Resolves openbmc/openbmc#3057

Change-Id: I0df7b1475f81325b9ac497c1fb350c3f62329686
Signed-off-by: Ratan Gupta <ratagupt@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index e177077..7167b1d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,6 +2,51 @@
 nobase_include_HEADERS = snmp.hpp \
 		snmp_notification.hpp
 
+nobase_nodist_include_HEADERS = \
+		xyz/openbmc_project/Network/Client/Create/server.hpp
+
+sbin_PROGRAMS = phosphor-network-snmpconf
+
+noinst_HEADERS = \
+		snmp_client.hpp \
+		snmp_conf_manager.hpp
+
+phosphor_network_snmpconf_SOURCES = \
+		snmp_main.cpp \
+		snmp_conf_manager.cpp \
+		snmp_client.cpp \
+		xyz/openbmc_project/Network/Client/Create/server.cpp
+
+CLEANFILES = \
+		xyz/openbmc_project/Network/Client/Create/server.cpp \
+		xyz/openbmc_project/Network/Client/Create/server.hpp
+
+BUILT_SOURCES = \
+        xyz/openbmc_project/Network/Client/Create/server.cpp \
+        xyz/openbmc_project/Network/Client/Create/server.hpp
+
+phosphor_network_snmpconf_LDFLAGS = \
+		$(SYSTEMD_LIBS) \
+		$(SDBUSPLUS_LIBS) \
+		$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+		$(PHOSPHOR_LOGGING_LIBS) \
+		-lstdc++fs
+
+phosphor_network_snmpconf_CXXFLAGS = \
+		$(SYSTEMD_CFLAGS) \
+		$(SDBUSPLUS_CFLAGS) \
+		$(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+		$(PHOSPHOR_LOGGING_CFLAGS)
+
+xyz/openbmc_project/Network/Client/Create/server.cpp: xyz/openbmc_project/Network/Client/Create.interface.yaml xyz/openbmc_project/Network/Client/Create/server.hpp
+	@mkdir -p `dirname $@`
+	$(SDBUSPLUSPLUS) -r $(srcdir) interface server-cpp xyz.openbmc_project.Network.Client.Create > $@
+
+xyz/openbmc_project/Network/Client/Create/server.hpp: xyz/openbmc_project/Network/Client/Create.interface.yaml
+	@mkdir -p `dirname $@`
+	$(SDBUSPLUSPLUS) -r $(srcdir) interface server-header xyz.openbmc_project.Network.Client.Create > $@
+	sed -i '5i #include \"xyz\/openbmc_project\/Network\/Client\/server.hpp\"' $@
+
 libsnmpdir = ${libdir}
 
 libsnmp_LTLIBRARIES = libsnmp.la
diff --git a/configure.ac b/configure.ac
index a32dbdc..a0cdbe2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -12,13 +12,43 @@
 AM_PROG_AR
 AC_PROG_INSTALL
 AC_PROG_MAKE_SET
+AC_PROG_MKDIR_P
 
 LT_PREREQ([2.4.6])
 LT_INIT([dlopen disable-static shared])
 
+# Checks for libraries.
+AX_PKG_CHECK_MODULES([SYSTEMD], [], [libsystemd >= 221], [],\
+[AC_MSG_ERROR(["Systemd version should be greater then 221."])])
+
+AX_PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus], [], [],\
+[AC_MSG_ERROR(["Requires sdbusplus package."])])
+
+
+AC_PATH_PROG([SDBUSPLUSPLUS], [sdbus++])
+
+AX_PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [], [phosphor-logging], [],\
+[AC_MSG_ERROR(["Requires phosphor-logging package."])])
+
+# Checks for header files.
+AC_CHECK_HEADER(systemd/sd-bus.h, ,\
+[AC_MSG_ERROR([Could not find systemd/sd-bus.h...systemd development package required])])
+
+AX_PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [], [phosphor-dbus-interfaces],\
+[], [AC_MSG_ERROR(["phosphor-dbus-interfaces required and not found."])])
+
 AC_CHECK_HEADERS([net-snmp/net-snmp-config.h],,\
     AC_MSG_ERROR(["Requires net-snmp headers"]))
 
+AC_ARG_VAR(BUSNAME_NETWORK_SNMP, [The Dbus busname to own])
+AS_IF([test "x$BUSNAME_NETWORK_SNMP" == "x"], [BUSNAME_NETWORK_SNMP="xyz.openbmc_project.Network.SNMP"])
+AC_DEFINE_UNQUOTED([BUSNAME_NETWORK_SNMP], ["$BUSNAME_NETWORK_SNMP"], [The DBus busname to own])
+
+AC_ARG_VAR(OBJ_NETWORK_SNMP, [The network snmp root DBus object path])
+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])
+
+
 # Checks for library functions
 LT_INIT # Required for systemd linking
 
@@ -47,9 +77,8 @@
     AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags])
 )
 
-
+# Create configured output
+AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_FILES([Makefile])
 AC_CONFIG_FILES([phosphor-snmp.pc])
 AC_OUTPUT
-
-
diff --git a/snmp_client.cpp b/snmp_client.cpp
new file mode 100644
index 0000000..4049e2d
--- /dev/null
+++ b/snmp_client.cpp
@@ -0,0 +1,30 @@
+#include "snmp_client.hpp"
+#include "snmp_conf_manager.hpp"
+
+namespace phosphor
+{
+namespace network
+{
+namespace snmp
+{
+
+Client::Client(sdbusplus::bus::bus& bus, const char* objPath,
+               ConfManager& parent, const std::string& address, uint16_t port) :
+    Ifaces(bus, objPath, true),
+    parent(parent)
+{
+    this->address(address);
+    this->port(port);
+
+    // Emit deferred signal.
+    emit_object_added();
+}
+
+void Client::delete_()
+{
+    parent.deleteSNMPClient(this->address());
+}
+
+} // namespace snmp
+} // namespace network
+} // namespace phosphor
diff --git a/snmp_client.hpp b/snmp_client.hpp
new file mode 100644
index 0000000..5b27f1b
--- /dev/null
+++ b/snmp_client.hpp
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "xyz/openbmc_project/Network/Client/server.hpp"
+#include "xyz/openbmc_project/Object/Delete/server.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+
+#include <string>
+
+namespace phosphor
+{
+namespace network
+{
+namespace snmp
+{
+
+class ConfManager;
+
+using Ifaces = sdbusplus::server::object::object<
+    sdbusplus::xyz::openbmc_project::Network::server::Client,
+    sdbusplus::xyz::openbmc_project::Object::server::Delete>;
+
+/** @class Client
+ *  @brief represents the snmp client configuration
+ *  @details A concrete implementation for the
+ *  xyz.openbmc_project.Network.Client Dbus interface.
+ */
+class Client : public Ifaces
+{
+  public:
+    Client() = delete;
+    Client(const Client &) = delete;
+    Client &operator=(const Client &) = delete;
+    Client(Client &&) = delete;
+    Client &operator=(Client &&) = delete;
+    virtual ~Client() = default;
+
+    /** @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.
+     *  @param[in] address - IPaddress/Hostname.
+     *  @param[in] port - network port.
+     */
+    Client(sdbusplus::bus::bus &bus, const char *objPath, ConfManager &parent,
+           const std::string &address, uint16_t port);
+
+    /** @brief Delete this d-bus object.
+     */
+    void delete_() override;
+
+  private:
+    /** @brief Parent D-Bus Object. */
+    ConfManager &parent;
+};
+
+} // namespace snmp
+} // namespace network
+} // namespace phosphor
diff --git a/snmp_conf_manager.cpp b/snmp_conf_manager.cpp
new file mode 100644
index 0000000..30d0815
--- /dev/null
+++ b/snmp_conf_manager.cpp
@@ -0,0 +1,61 @@
+#include "config.h"
+#include "snmp_conf_manager.hpp"
+#include <phosphor-logging/log.hpp>
+
+#include <experimental/filesystem>
+
+namespace phosphor
+{
+namespace network
+{
+namespace snmp
+{
+
+using namespace phosphor::logging;
+
+ConfManager::ConfManager(sdbusplus::bus::bus& bus, const char* objPath) :
+    details::CreateIface(bus, objPath, true), bus(bus), objectPath(objPath)
+{
+}
+
+void ConfManager::client(std::string address, uint16_t port)
+{
+    auto clientEntry = this->clients.find(address);
+    if (clientEntry == this->clients.end())
+    {
+        std::experimental::filesystem::path objPath;
+        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));
+    }
+}
+
+std::string ConfManager::generateId(const std::string& address, uint16_t port)
+{
+    std::stringstream hexId;
+    std::string hashString = address;
+    hashString += std::to_string(port);
+
+    // Only want 8 hex digits.
+    hexId << std::hex << ((std::hash<std::string>{}(hashString)) & 0xFFFFFFFF);
+    return hexId.str();
+}
+
+void ConfManager::deleteSNMPClient(const std::string& address)
+{
+    auto it = clients.find(address);
+    if (it == clients.end())
+    {
+        log<level::ERR>("Unable to delete the snmp client.",
+                        entry("ADDRESS=%s", address.c_str()));
+        return;
+    }
+    this->clients.erase(it);
+}
+
+} // namespace snmp
+} // namespace network
+} // namespace phosphor
diff --git a/snmp_conf_manager.hpp b/snmp_conf_manager.hpp
new file mode 100644
index 0000000..0f1ee4b
--- /dev/null
+++ b/snmp_conf_manager.hpp
@@ -0,0 +1,81 @@
+#pragma once
+
+#include "snmp_client.hpp"
+
+#include <xyz/openbmc_project/Network/Client/Create/server.hpp>
+#include <sdbusplus/bus.hpp>
+
+#include <string>
+
+namespace phosphor
+{
+namespace network
+{
+namespace snmp
+{
+using IPAddress = std::string;
+using ClientList = std::map<IPAddress, std::unique_ptr<Client>>;
+
+namespace details
+{
+
+using CreateIface = sdbusplus::server::object::object<
+    sdbusplus::xyz::openbmc_project::Network::Client::server::Create>;
+
+} // namespace details
+
+class TestSNMPConfManager;
+/** @class Manager
+ *  @brief OpenBMC SNMP config  implementation.
+ */
+class ConfManager : public details::CreateIface
+{
+  public:
+    ConfManager() = delete;
+    ConfManager(const ConfManager&) = delete;
+    ConfManager& operator=(const ConfManager&) = delete;
+    ConfManager(ConfManager&&) = delete;
+    ConfManager& operator=(ConfManager&&) = delete;
+    virtual ~ConfManager() = default;
+
+    /** @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.
+     */
+    ConfManager(sdbusplus::bus::bus& bus, const char* objPath);
+
+    /** @brief Function to create snmp manager details D-Bus object.
+     *  @param[in] address- IP address/Hostname.
+     *  @param[in] port - network port.
+     */
+    void client(std::string address, uint16_t port) override;
+
+    /* @brief delete the dbus object of the given ipaddress.
+     * @param[in] address - IP address/Hostname.
+     */
+    void deleteSNMPClient(const std::string& address);
+
+  protected:
+    /** @brief generates the id by doing hash of ipaddress, port
+     *  @param[in] address - IP address/Hostname.
+     *  @param[in] port - network port.
+     *  @return hash string.
+     */
+    static std::string generateId(const std::string& address, uint16_t port);
+
+  private:
+    /** @brief sdbusplus DBus bus object. */
+    sdbusplus::bus::bus& bus;
+
+    /** @brief Path of Object. */
+    std::string objectPath;
+
+    /** @brief map of IPAddress dbus objects and their names */
+    ClientList clients;
+
+    friend class TestSNMPConfManager;
+};
+
+} // namespace snmp
+} // namespace network
+} // namespace phosphor
diff --git a/snmp_main.cpp b/snmp_main.cpp
new file mode 100644
index 0000000..932b9f4
--- /dev/null
+++ b/snmp_main.cpp
@@ -0,0 +1,49 @@
+#include "config.h"
+#include "snmp_conf_manager.hpp"
+
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/manager.hpp>
+
+#include <memory>
+
+/* Need a custom deleter for freeing up sd_event */
+struct EventDeleter
+{
+    void operator()(sd_event* event) const
+    {
+        event = sd_event_unref(event);
+    }
+};
+
+using EventPtr = std::unique_ptr<sd_event, EventDeleter>;
+
+int main(int argc, char* argv[])
+{
+    using namespace phosphor::logging;
+
+    auto bus = sdbusplus::bus::new_default();
+
+    sd_event* event = nullptr;
+    auto r = sd_event_default(&event);
+    if (r < 0)
+    {
+        log<level::ERR>("Error creating a default sd_event handler");
+        return r;
+    }
+
+    EventPtr eventPtr{event};
+    event = nullptr;
+
+    // Attach the bus to sd_event to service user requests
+    bus.attach_event(eventPtr.get(), SD_EVENT_PRIORITY_NORMAL);
+
+    // Add sdbusplus Object Manager for the 'root' path of the snmp.
+    sdbusplus::server::manager::manager objManager(bus, OBJ_NETWORK_SNMP);
+    bus.request_name(BUSNAME_NETWORK_SNMP);
+
+    auto manager = std::make_unique<phosphor::network::snmp::ConfManager>(
+        bus, OBJ_NETWORK_SNMP);
+
+    return sd_event_loop(eventPtr.get());
+}
diff --git a/test/Makefile.am.include b/test/Makefile.am.include
index 9c5435a..7926d09 100644
--- a/test/Makefile.am.include
+++ b/test/Makefile.am.include
@@ -2,17 +2,37 @@
 	-I${top_srcdir} \
 	-I${top_builddir} \
 	-Igtest \
-	$(GTEST_CPPFLAGS) \
-	$(CODE_COVERAGE_CPPFLAGS)
+	$(GTEST_CPPFLAGS)
 
-AM_LDFLAGS = -lgtest_main -lgtest -lstdc++fs \
+AM_CFLAGS = \
+	$(PTHREAD_CFLAGS) \
+	$(CODE_COVERAGE_CFLAGS) \
+	$(SDBUSPLUS_CFLAGS) \
+	$(PHOSPHOR_LOGGING_CFLAGS) \
+	$(PHOSPHOR_DBUS_INTERFACES_CFLAGS)
+
+AM_CXXFLAGS = \
+	$(PTHREAD_CXXFLAGS) \
+	$(CODE_COVERAGE_CXXFLAGS) \
+	$(SDBUSPLUS_CXXFLAGS) \
+	$(PHOSPHOR_LOGGING_CXXFLAGS) \
+	$(PHOSPHOR_DBUS_INTERFACES_CXXFLAGS)
+
+AM_LDFLAGS = \
+	-lgtest_main -lgtest -lstdc++fs \
+	$(PTHREAD_CFLAGS) \
+	$(SDBUSPLUS_LIBS) \
+	$(PHOSPHOR_LOGGING_LIBS) \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
 	$(OESDK_TESTCASE_FLAGS) \
 	$(CODE_COVERAGE_LIBS)
 
-AM_CXXFLAGS = $(PTHREAD_CFLAGS) \
-	$(CODE_COVERAGE_CXXFLAGS)
-
 test_notification_SOURCES = \
-	%reldir%/test_error_notification.cpp
+	%reldir%/test_error_notification.cpp \
+	%reldir%/test_snmp_conf_manager.cpp
+
+test_notification_LDADD = $(top_builddir)/phosphor_network_snmpconf-snmp_conf_manager.o \
+	$(top_builddir)/phosphor_network_snmpconf-snmp_client.o \
+	$(top_builddir)/xyz/openbmc_project/Network/Client/Create/phosphor_network_snmpconf-server.o
 
 check_PROGRAMS += %reldir%/notification
diff --git a/test/test_snmp_conf_manager.cpp b/test/test_snmp_conf_manager.cpp
new file mode 100644
index 0000000..ab29215
--- /dev/null
+++ b/test/test_snmp_conf_manager.cpp
@@ -0,0 +1,91 @@
+#include "snmp_conf_manager.hpp"
+
+#include "xyz/openbmc_project/Common/error.hpp"
+
+#include <gtest/gtest.h>
+#include <sdbusplus/bus.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+namespace snmp
+{
+
+class TestSNMPConfManager : public testing::Test
+{
+  public:
+    sdbusplus::bus::bus bus;
+    ConfManager manager;
+    std::string confDir;
+    TestSNMPConfManager() :
+        bus(sdbusplus::bus::new_default()), manager(bus, ""){};
+
+    ~TestSNMPConfManager()
+    {
+    }
+
+    void createSNMPClient(std::string ipaddress, uint16_t port)
+    {
+        manager.client(ipaddress, port);
+    }
+
+    ClientList &getSNMPClients()
+    {
+        return manager.clients;
+    }
+
+    void deleteSNMPClient(std::string ipaddress)
+    {
+        if (manager.clients.find(ipaddress) != manager.clients.end())
+        {
+            auto &it = manager.clients[ipaddress];
+            it->delete_();
+        }
+    }
+};
+
+// Add single SNMP client
+TEST_F(TestSNMPConfManager, AddSNMPClient)
+{
+    using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+    createSNMPClient("192.168.1.1", 24);
+
+    auto &clients = getSNMPClients();
+    EXPECT_EQ(1, clients.size());
+    EXPECT_EQ(true, clients.find("192.168.1.1") != clients.end());
+}
+
+// Add multiple SNMP client
+TEST_F(TestSNMPConfManager, AddMultipleSNMPClient)
+{
+    using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+    createSNMPClient("192.168.1.1", 24);
+    createSNMPClient("192.168.1.2", 24);
+
+    auto &clients = getSNMPClients();
+    EXPECT_EQ(2, clients.size());
+    EXPECT_EQ(true, clients.find("192.168.1.1") != clients.end());
+    EXPECT_EQ(true, clients.find("192.168.1.2") != clients.end());
+}
+
+// Delete SNMP client
+TEST_F(TestSNMPConfManager, DeleteSNMPClient)
+{
+    using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+    createSNMPClient("192.168.1.1", 24);
+    createSNMPClient("192.168.1.2", 24);
+
+    auto &clients = getSNMPClients();
+    EXPECT_EQ(2, clients.size());
+    deleteSNMPClient("192.168.1.1");
+    EXPECT_EQ(1, clients.size());
+    EXPECT_EQ(true, clients.find("192.168.1.2") != clients.end());
+}
+
+} // namespace snmp
+} // namespce network
+} // namespace phosphor