Add first version

First version of C++ SDL API.

Refer README file for instructions how to generate API documentation.

Current version will find database via environment variables, thus RIC
DBaaS service must be up and running before starting the application
pod which uses SDL C++ API.

Change-Id: Ia3c95ac856b293827b680a9f6451a229f69f35f9
Signed-off-by: Rolf Badorek <rolf.badorek@nokia.com>
diff --git a/src/cli/commandmap.cpp b/src/cli/commandmap.cpp
new file mode 100644
index 0000000..e0e8304
--- /dev/null
+++ b/src/cli/commandmap.cpp
@@ -0,0 +1,185 @@
+#include "private/cli/commandmap.hpp"
+#include <sstream>
+#include <iomanip>
+#include <memory>
+#include <cstdlib>
+#include <boost/io/ios_state.hpp>
+
+using namespace shareddatalayer::cli;
+
+namespace
+{
+    std::string buildCommandNameAlreadyRegisteredError(const std::string& commandName)
+    {
+        std::ostringstream os;
+        os << "command name \"" << commandName << "\" already registered";
+        return os.str();
+    }
+
+    std::string buildUnknownCommandNameError(const std::string& commandName)
+    {
+        std::ostringstream os;
+        os << "unknown command: \"" << commandName << '\"';
+        return os.str();
+    }
+
+    std::string buildCategoryOffsetAlreadyRegisteredError(const std::string& commandName, int offset)
+    {
+        std::ostringstream os;
+        os << commandName << ": Offset " << offset << " already registered";
+        return os.str();
+    }
+
+    std::string getCategoryName(CommandMap::Category category)
+    {
+        switch (category)
+        {
+            case CommandMap::Category::UTIL:
+                return "Utility";
+            default:
+                return "Unknown";
+        }
+    }
+}
+
+CommandMap::CommandNameAlreadyRegistered::CommandNameAlreadyRegistered(const std::string& commandName):
+    Exception(buildCommandNameAlreadyRegisteredError(commandName))
+{
+}
+
+CommandMap::UnknownCommandName::UnknownCommandName(const std::string& commandName):
+    Exception(buildUnknownCommandNameError(commandName))
+{
+}
+
+CommandMap::CategoryOffsetAlreadyRegistered::CategoryOffsetAlreadyRegistered(const std::string& commandName, int offset):
+    Exception(buildCategoryOffsetAlreadyRegisteredError(commandName, offset))
+{
+}
+
+struct CommandMap::Info
+{
+    CommandFunction function;
+    std::string shortHelp;
+    std::string longHelp;
+    boost::program_options::options_description options;
+
+    Info(const CommandFunction& function,
+         const std::string& shortHelp,
+         const std::string& longHelp);
+};
+
+CommandMap::Info::Info(const CommandFunction& function,
+                       const std::string& shortHelp,
+                       const std::string& longHelp):
+    function(function),
+    shortHelp(shortHelp),
+    longHelp(longHelp)
+{
+}
+
+CommandMap::CommandMap()
+{
+}
+
+CommandMap::~CommandMap()
+{
+}
+
+boost::program_options::options_description&
+CommandMap::registerCommand(const std::string& commandName,
+                            const std::string& shortHelp,
+                            const std::string& longHelp,
+                            const CommandFunction& commandFunction,
+                            Category category,
+                            int categoryOffset)
+{
+    const auto ret(map.insert(std::make_pair(commandName, Info(commandFunction, shortHelp, longHelp))));
+    if (!ret.second)
+        throw CommandNameAlreadyRegistered(commandName);
+    const auto retCat(categoryMap.insert(std::make_pair(CategoryKey(category, categoryOffset), commandName)));
+    if (!retCat.second)
+        throw CategoryOffsetAlreadyRegistered(commandName, categoryOffset);
+    return ret.first->second.options;
+}
+
+std::vector<std::string> CommandMap::getCommandNames() const
+{
+    std::vector<std::string> ret;
+    for (const auto& i : map)
+        ret.push_back(i.first);
+    return ret;
+}
+
+const boost::program_options::options_description&
+CommandMap::getCommandOptions(const std::string& commandName) const
+{
+    const auto i(map.find(commandName));
+    if (i == map.end())
+        throw UnknownCommandName(commandName);
+    return i->second.options;
+}
+
+void CommandMap::shortHelp(std::ostream& out) const
+{
+    boost::io::ios_all_saver guard(out);
+    size_t maxWidth(0U);
+    Category currentCategory(Category::UNDEFINED);
+    for (const auto& i : map)
+        if (maxWidth < i.first.size())
+            maxWidth = i.first.size();
+    for (const auto& i : categoryMap)
+    {
+        if (currentCategory != i.first.first)
+        {
+            currentCategory = i.first.first;
+            out << std::endl;
+            out << getCategoryName(currentCategory) << " commands:" << std::endl;
+        }
+        out << std::left << std::setw(maxWidth + 2U) << i.second << map.at(i.second).shortHelp << '\n';
+    }
+}
+
+void CommandMap::longHelp(std::ostream& out, const std::string& commandName) const
+{
+    const auto i(map.find(commandName));
+    if (i == map.end())
+        throw UnknownCommandName(commandName);
+    out << i->first;
+    if (!i->second.options.options().empty())
+    {
+        out << " OPTIONS\n\n" << i->second.longHelp << "\n\nOptions:\n";
+        i->second.options.print(out);
+    }
+    else
+    {
+        out << "\n\n" << i->second.longHelp << '\n';
+    }
+}
+
+int CommandMap::execute(const std::string& commandName,
+                        std::ostream& out,
+                        std::ostream& err,
+                        const boost::program_options::variables_map& params,
+                        size_t count)
+{
+    const auto i(map.find(commandName));
+    if (i == map.end())
+    {
+        err << "unknown command: \"" << commandName << '\"' << std::endl;
+        return EXIT_FAILURE;
+    }
+    const auto& function(i->second.function);
+    int ret(EXIT_SUCCESS);
+    for (size_t i = 0U; i < count; ++i)
+        if ((ret = function(out, err, params)) != EXIT_SUCCESS)
+            break;
+    return ret;
+}
+
+CommandMap& CommandMap::getCommandMap() noexcept
+{
+    static CommandMap instance;
+    return instance;
+}
+
diff --git a/src/cli/commandparserandexecutor.cpp b/src/cli/commandparserandexecutor.cpp
new file mode 100644
index 0000000..b0d52bf
--- /dev/null
+++ b/src/cli/commandparserandexecutor.cpp
@@ -0,0 +1,176 @@
+#include "config.h"
+#include "private/cli/commandparserandexecutor.hpp"
+#include <ostream>
+#include <set>
+#include <string>
+#include <exception>
+#include <boost/program_options.hpp>
+#include "private/cli/commandmap.hpp"
+
+using namespace shareddatalayer;
+using namespace shareddatalayer::cli;
+namespace po = boost::program_options;
+
+namespace
+{
+    void outputBashCompletion(std::ostream& os,
+                              const po::options_description& commonOpts,
+                              CommandMap& commandMap)
+    {
+        const auto commandNames(commandMap.getCommandNames());
+        std::set<std::string> optionNames;
+        for (const auto& i : commonOpts.options())
+            optionNames.insert(i->long_name());
+        for (const auto& i : commandNames)
+            for (const auto& j : commandMap.getCommandOptions(i).options())
+                optionNames.insert(j->long_name());
+
+        os << "_sdltool()\n{\n    COMPREPLY=()\n    local commands=\"";
+        for (const auto& i : commandNames)
+            os << i << ' ';
+        os << "\"\n    local options=\"";
+        for (const auto& i : optionNames)
+            os << "--" << i << ' ';
+        os <<
+R"bash("
+    local cur="${COMP_WORDS[COMP_CWORD]}"
+
+    if [[ ${cur} == -* ]] ; then
+        COMPREPLY=( $(compgen -W "${options}" -- ${cur}) )
+        return 0
+    else
+        COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) )
+        return 0
+    fi
+}
+complete -F _sdltool sdltool
+)bash"
+           << std::flush;
+    }
+
+    void printShortHelp(std::ostream& os,
+                        const po::options_description& commonOpts,
+                        CommandMap& commandMap)
+    {
+        os << "Usage: sdltool [OPTIONS] COMMAND [COMMAND SPECIFIC OPTIONS]\n\n";
+        commonOpts.print(os);
+        os << "\nAvailable commands:\n";
+        commandMap.shortHelp(os);
+    }
+
+    int privateParseAndExecute(int argc,
+                               char** argv,
+                               std::ostream& out,
+                               std::ostream& err,
+                               CommandMap& commandMap)
+    {
+        po::options_description commonHiddenOptsDescription("Common hidden options");
+        commonHiddenOptsDescription.add_options()
+            ("pos-command", po::value<std::string>(), "sdltool command to run")
+            ("pos-subargs", po::value<std::vector<std::string>>(), "Arguments for command");
+
+        po::options_description commonVisibleOptsDescription("Common options");
+        commonVisibleOptsDescription.add_options()
+            ("help", "Show help for COMMAND")
+            ("repeat", po::value<size_t>()->default_value(1U), "Times to repeat the command")
+            ("bash-completion", "Generate bash completion script")
+            ("version", "Show version information");
+
+        po::options_description commonOptsDescription("All common options");
+        commonOptsDescription.add(commonHiddenOptsDescription);
+        commonOptsDescription.add(commonVisibleOptsDescription);
+
+        po::positional_options_description commonPosOptsDescription;
+        commonPosOptsDescription
+            .add("pos-command", 1)
+            .add("pos-subargs", -1); // all positional arguments after 'command'
+
+        po::variables_map commonVarsMap;
+        po::parsed_options commonParsed(po::command_line_parser(argc, argv)
+                                        .options(commonOptsDescription)
+                                        .positional(commonPosOptsDescription)
+                                        .allow_unregistered()
+                                        .run());
+        po::store(commonParsed, commonVarsMap);
+        po::notify(commonVarsMap);
+
+        if (!commonVarsMap.count("pos-command"))
+        {
+            if (commonVarsMap.count("help"))
+            {
+                printShortHelp(out, commonVisibleOptsDescription, commandMap);
+                return EXIT_SUCCESS;
+            }
+            if (commonVarsMap.count("bash-completion"))
+            {
+                outputBashCompletion(out, commonVisibleOptsDescription, commandMap);
+                return EXIT_SUCCESS;
+            }
+            if (commonVarsMap.count("version"))
+            {
+                out << PACKAGE_STRING << std::endl;
+                return EXIT_SUCCESS;
+            }
+            err << "missing command\n\n";
+            printShortHelp(err, commonVisibleOptsDescription, commandMap);
+            return EXIT_FAILURE;
+        }
+
+        auto leftOverOpts = po::collect_unrecognized(commonParsed.options, po::include_positional);
+        if (!leftOverOpts.empty())
+            leftOverOpts.erase(leftOverOpts.begin());
+
+        const auto commandName(commonVarsMap["pos-command"].as<std::string>());
+
+        if (commonVarsMap.count("help"))
+        {
+            bool found(false);
+            try
+            {
+                commandMap.longHelp(out, commandName);
+                out << '\n';
+                found = true;
+            }
+            catch (const CommandMap::UnknownCommandName&)
+            {
+            }
+            if (!found)
+            {
+                err << "unknown command: \"" << commandName << "\"\n";
+                return EXIT_FAILURE;
+            }
+            return EXIT_SUCCESS;
+        }
+
+        const auto& commandOptions(commandMap.getCommandOptions(commandName));
+
+        po::variables_map subcmdVarsMap;
+        auto subcmdParsed(po::command_line_parser(leftOverOpts).options(commandOptions).run());
+        po::store(subcmdParsed, subcmdVarsMap);
+        po::notify(subcmdVarsMap);
+
+        return commandMap.execute(commandName,
+                                  out,
+                                  err,
+                                  subcmdVarsMap,
+                                  commonVarsMap["repeat"].as<size_t>());
+    }
+}
+
+int shareddatalayer::cli::parseAndExecute(int argc,
+                                         char** argv,
+                                         std::ostream& out,
+                                         std::ostream& err,
+                                         CommandMap& commandMap)
+{
+    try
+    {
+        return privateParseAndExecute(argc, argv, out, err, commandMap);
+    }
+    catch (const std::exception& e)
+    {
+        err << e.what() << std::endl;
+        return EXIT_FAILURE;
+    }
+}
+
diff --git a/src/cli/dumpconfigurationcommand.cpp b/src/cli/dumpconfigurationcommand.cpp
new file mode 100644
index 0000000..438f075
--- /dev/null
+++ b/src/cli/dumpconfigurationcommand.cpp
@@ -0,0 +1,83 @@
+#include <ostream>
+#include <cstdlib>
+#include <fstream>
+#include <string>
+#include <iostream>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/json_parser.hpp>
+#include "private/cli/commandmap.hpp"
+#include "private/configurationpaths.hpp"
+#include "private/configurationreader.hpp"
+
+using namespace shareddatalayer;
+using namespace shareddatalayer::cli;
+
+namespace
+{
+    bool parseConfiguration(const std::string& file)
+    {
+        boost::property_tree::ptree propertyTree;
+
+        try
+        {
+            boost::property_tree::read_json(file, propertyTree);
+        }
+        catch (const boost::property_tree::json_parser::json_parser_error& e)
+        {
+            std::ostringstream os;
+            os << "error in SDL configuration " << file << " at line " << e.line() << ": ";
+            os << e.message();
+            std::cerr << os.str().c_str() << std::endl;
+            return false;
+        }
+        return true;
+    }
+
+
+    int dumpConfigurationCommand(std::ostream& out)
+    {
+        std::string line;
+        bool status(true);
+
+        for (const auto& i : findConfigurationFiles( getDefaultConfDirectories() ))
+        {
+            std::ifstream file(i);
+
+            out << "File: " << i << std::endl;
+
+            unsigned int lineNum = 1;
+
+            if(file.is_open())
+            {
+                bool parseStatus = parseConfiguration(i);
+
+                if(status && !parseStatus)
+                    status = false;
+
+                while(getline(file, line))
+                    out << lineNum++ << ": " << line << std::endl;
+                file.close();
+            }
+        }
+
+        const auto var(DB_HOST_ENV_VAR_NAME);
+        const auto conf(getenv(var));
+        if (conf == nullptr)
+            out << var << " not set." << std::endl;
+        else
+            out << var  << ": " << conf << std::endl;
+
+        if(!status)
+        {
+            return EXIT_FAILURE;
+        }
+        return EXIT_SUCCESS;
+    }
+}
+
+AUTO_REGISTER_COMMAND(std::bind(dumpConfigurationCommand, std::placeholders::_1),
+                      "dump-configuration",
+                      "Dump configuration",
+                      "Find, parse and dump all shareddatalayer configuration files contents.",
+                      CommandMap::Category::UTIL, 30000);
+
diff --git a/src/cli/main.cpp b/src/cli/main.cpp
new file mode 100644
index 0000000..9d84536
--- /dev/null
+++ b/src/cli/main.cpp
@@ -0,0 +1,22 @@
+#include <iostream>
+#include <exception>
+#include <cstdlib>
+#include "private/cli/commandmap.hpp"
+#include "private/cli/commandparserandexecutor.hpp"
+
+using namespace shareddatalayer;
+using namespace shareddatalayer::cli;
+
+int main(int argc, char** argv)
+{
+    try
+    {
+        return parseAndExecute(argc, argv, std::cout, std::cerr, CommandMap::getCommandMap());
+    }
+    catch (const std::exception& e)
+    {
+        std::cerr << "unexpected error: " << e.what() << std::endl;
+        return EXIT_FAILURE;
+    }
+}
+
diff --git a/src/cli/testconnectivitycommand.cpp b/src/cli/testconnectivitycommand.cpp
new file mode 100644
index 0000000..7c4b2ad
--- /dev/null
+++ b/src/cli/testconnectivitycommand.cpp
@@ -0,0 +1,196 @@
+#include <ostream>
+#include <cstdlib>
+#include <fstream>
+#include <string>
+#include <iostream>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/json_parser.hpp>
+#include <chrono>
+#include <arpa/inet.h>
+#include <sdl/asyncstorage.hpp>
+#include <boost/asio.hpp>
+#include <thread>
+#include "private/cli/commandmap.hpp"
+#include "private/configurationpaths.hpp"
+#include "private/createlogger.hpp"
+#include "private/engineimpl.hpp"
+#include "private/databaseconfigurationimpl.hpp"
+#include "private/configurationreader.hpp"
+#include "private/redis/databaseinfo.hpp"
+#include "private/asyncstorageimpl.hpp"
+#include "private/redis/asyncredisstorage.hpp"
+
+using namespace shareddatalayer;
+using namespace shareddatalayer::cli;
+using namespace shareddatalayer::redis;
+
+namespace
+{
+    void handler(std::shared_ptr<shareddatalayer::AsyncStorage> sdl, boost::asio::posix::stream_descriptor& sd)
+    {
+        sdl->handleEvents();
+        sd.async_read_some(boost::asio::null_buffers(), std::bind(handler, sdl, std::ref(sd)));
+    }
+
+    std::shared_ptr<AsyncStorage> createStorage(const std::string& nsStr, std::ostream& out)
+    {
+        try
+        {
+        	std::shared_ptr<AsyncStorage> sdl(AsyncStorage::create());
+            boost::asio::io_service ios;
+            boost::asio::posix::stream_descriptor sd(ios);
+            sd.assign(sdl->fd());
+            sd.async_read_some(boost::asio::null_buffers(), std::bind(handler, sdl, std::ref(sd)));
+            sdl->waitReadyAsync(nsStr, [&ios](const std::error_code& error)
+                                {
+                                    if (error)
+                                        std::cerr << "SDL waitReadyAsync failed. Error:\n" << error.message() << std::endl;
+                                    ios.stop();
+                                });
+            ios.run();
+            sd.release();
+            out << "Storage to namespace " << nsStr << " created." << std::endl;
+            return sdl;
+        }
+        catch (const shareddatalayer::Exception& error)
+        {
+            out << "Storage create failed: " << error.what() << std::endl;
+        }
+        return nullptr;
+    }
+
+    std::string getHosts(const DatabaseConfiguration::Addresses& databaseAddresses)
+    {
+        std::string hosts("");
+        for (auto i(databaseAddresses.begin()); i != databaseAddresses.end(); ++i)
+            hosts = hosts + i->getHost() + " ";
+        return hosts;
+    }
+
+    std::string getPorts(const DatabaseConfiguration::Addresses& databaseAddresses)
+    {
+        std::string ports("");
+        for (auto i(databaseAddresses.begin()); i != databaseAddresses.end(); ++i)
+            ports = ports + std::to_string(ntohs(i->getPort())) + " ";
+        return ports;
+    }
+
+    void PrintStaticConfiguration(std::ostream& out)
+    {
+        auto engine(std::make_shared<EngineImpl>());
+        DatabaseConfigurationImpl databaseConfigurationImpl;
+        ConfigurationReader configurationReader(createLogger(SDL_LOG_PREFIX));
+        configurationReader.readDatabaseConfiguration(databaseConfigurationImpl);
+        auto staticAddresses(databaseConfigurationImpl.getServerAddresses());
+        auto defaultAddresses(databaseConfigurationImpl.getDefaultServerAddresses());
+        auto staticDbType(databaseConfigurationImpl.getDbType());
+        if (!staticAddresses.empty())
+        {
+            out << "\nStatic Server Addresses:" << std::endl;
+            out << "Static Host: " << getHosts(staticAddresses) << std::endl;
+            out << "Static Port: " << getPorts(staticAddresses) << std::endl;
+            if (staticDbType == DatabaseConfiguration::DbType::REDIS_CLUSTER)
+                out << "Static DB type: redis-cluster" << std::endl;
+            else if (staticDbType == DatabaseConfiguration::DbType::REDIS_STANDALONE)
+                out << "Static DB type: redis-standalone" << std::endl;
+            else
+                out << "Static DB type not defined" << std::endl;
+        }
+        if (!defaultAddresses.empty() && staticAddresses.empty())
+        {
+            out << "\nDefault Server Addresses:" << std::endl;
+            out << "Default Host: " << getHosts(defaultAddresses) << std::endl;
+            out << "Default Port: " << getPorts(defaultAddresses) << std::endl;
+        }
+        const auto var(DB_HOST_ENV_VAR_NAME);
+        const auto conf(getenv(var));
+        if (conf != nullptr)
+            out << var  << ": " << conf << std::endl;
+    }
+
+    void PrintDatabaseInfo(const DatabaseInfo& databaseInfo, std::ostream& out)
+    {
+        out << "Used database configuration (databaseInfo):" << std::endl;
+        out << "Host: " << getHosts(databaseInfo.hosts) << std::endl;
+        out << "Port: " << getPorts(databaseInfo.hosts) << std::endl;
+        switch (databaseInfo.type)
+        {
+            case DatabaseInfo::Type::SINGLE:
+                out << "Database type: SINGLE" << std::endl;
+                break;
+            case DatabaseInfo::Type::REDUNDANT:
+                out << "Database type: REDUNDANT" << std::endl;
+                break;
+            case DatabaseInfo::Type::CLUSTER:
+                out << "Database type: CLUSTER" << std::endl;
+                break;
+        }
+        switch (databaseInfo.discovery)
+        {
+            case DatabaseInfo::Discovery::HIREDIS:
+                out << "Discovery type:: HIREDIS" << std::endl;
+                PrintStaticConfiguration(out);
+                break;
+        }
+    }
+
+    [[noreturn]] void timeoutThread(const int& timeout)
+    {
+        std::this_thread::sleep_for(std::chrono::seconds(timeout));
+        std::cerr << "Storage create timeout, aborting after " << timeout << " seconds"<< std::endl;
+        PrintStaticConfiguration(std::cerr);
+        std::exit(EXIT_FAILURE);
+    }
+
+    void setTimeout(const int& timeout)
+    {
+        if (timeout)
+        {
+            std::thread t(timeoutThread, timeout);
+            t.detach();
+        }
+    }
+
+    int TestConnectivityCommand(std::ostream& out,
+                                const boost::program_options::variables_map& map)
+    {
+        const auto ns(map["ns"].as<std::string>());
+        const auto timeout(map["timeout"].as<int>());
+        setTimeout(timeout);
+        auto sdl(createStorage(ns, out));
+        if (sdl != nullptr)
+        {
+            auto asyncStorageImpl(std::dynamic_pointer_cast<AsyncStorageImpl>(sdl));
+            if (asyncStorageImpl != nullptr)
+            {
+            	AsyncStorage& operationalHandler(asyncStorageImpl->getOperationHandler(ns));
+            	AsyncRedisStorage* redisStorage = dynamic_cast<AsyncRedisStorage*>(&operationalHandler);
+                if (redisStorage != nullptr)
+                {
+                	auto databaseinfo (redisStorage->getDatabaseInfo());
+                	PrintDatabaseInfo(databaseinfo, out);
+                }
+                else
+                {
+                	// @TODO Improve output for the case if dummy backend is used.
+                    out << "Cannot get AsyncRedisStorage." << std::endl;
+                    return EXIT_FAILURE;
+                }
+            }
+            else
+            {
+                out << "Cannot get AsyncStorageImpl." << std::endl;
+                return EXIT_FAILURE;
+            }
+        }
+        return EXIT_SUCCESS;
+    }
+}
+
+AUTO_REGISTER_COMMAND(std::bind(TestConnectivityCommand, std::placeholders::_1, std::placeholders::_3),
+                      "test-connectivity",
+                      "Test SDL backend connectivity",
+                      "Check that SDL database backend is available and show discovered redis host address and port",
+                      CommandMap::Category::UTIL, 30020,
+                      ("ns", boost::program_options::value<std::string>()->default_value("sdltoolns"), "Used namespace")
+                      ("timeout", boost::program_options::value<int>()->default_value(0), "Timeout (in seconds), Default is no timeout"));
diff --git a/src/cli/testgetsetcommand.cpp b/src/cli/testgetsetcommand.cpp
new file mode 100644
index 0000000..81bdd1c
--- /dev/null
+++ b/src/cli/testgetsetcommand.cpp
@@ -0,0 +1,144 @@
+#include <ostream>
+#include <cstdlib>
+#include <fstream>
+#include <string>
+#include <iostream>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/json_parser.hpp>
+#include <chrono>
+#include <thread>
+#include "private/cli/commandmap.hpp"
+#include "private/configurationpaths.hpp"
+#include <sdl/syncstorage.hpp>
+
+using namespace shareddatalayer;
+using namespace shareddatalayer::cli;
+
+namespace
+{
+    std::shared_ptr<shareddatalayer::SyncStorage> createSyncStorage(std::ostream& out)
+    {
+        try
+        {
+            auto sdl(shareddatalayer::SyncStorage::create());
+            return sdl;
+        }
+        catch (const shareddatalayer::Exception& error)
+        {
+            out << "SyncStorage create failed: " << error.what() << std::endl;
+        }
+
+        out << "Test suspended!" << std::endl;
+        return nullptr;
+    }
+
+    void execRemoveAll(shareddatalayer::SyncStorage& sdl, const std::string& nsStr, std::ostream& out)
+    {
+        try
+        {
+            sdl.removeAll(nsStr);
+        }
+        catch (const shareddatalayer::Exception& error)
+        {
+            out << "removeAll failed: " << error.what() << std::endl;
+        }
+    }
+
+    void execSet(shareddatalayer::SyncStorage& sdl, const std::string& nsStr, const std::string& key, const std::vector<uint8_t>& val, std::ostream& out)
+    {
+        try
+        {
+            sdl.set(nsStr, { { key, val } });
+        }
+        catch (const shareddatalayer::Exception& error)
+        {
+            out << "Set " << key << " failed: " << error.what() << std::endl;
+        }
+    }
+
+    void execGet(shareddatalayer::SyncStorage& sdl, const std::string& nsStr, const std::string& key, const std::vector<uint8_t>& val, std::ostream& out)
+    {
+        try
+        {
+            auto map(sdl.get(nsStr, { key }));
+            auto i(map.find(key));
+            if (i == map.end())
+                out << "Get " << key << ": Not found!" << std::endl;
+            else if (i->second != val)
+                out << "Get " << key << ": Wrong value!" << std::endl;
+        }
+        catch (const shareddatalayer::Exception& error)
+        {
+            out << "Get " << key << " failed: " << error.what() << std::endl;
+        }
+    }
+
+    void timeoutThread(const int& timeout)
+    {
+        std::this_thread::sleep_for(std::chrono::seconds(timeout));
+        std::cerr << "SyncStorage create timeout, aborting after " << timeout << " seconds"<< std::endl;
+        std::exit(EXIT_FAILURE);
+    }
+
+    void setTimeout(const int& timeout)
+    {
+        if (timeout)
+        {
+            std::thread t(timeoutThread, timeout);
+            t.detach();
+        }
+    }
+
+    int TestGetSetCommand(std::ostream& out, const boost::program_options::variables_map& map)
+    {
+        auto keyCount(map["key-count"].as<int>());
+        const auto timeout(map["timeout"].as<int>());
+        auto ns("sdltoolns");
+        setTimeout(timeout);
+        auto sdl(createSyncStorage(out));
+        if (sdl == nullptr)
+            return EXIT_FAILURE;
+
+        out << "namespace\t"
+            << "key\t"
+            << "value\t"
+            << "Write\tRead" << std::endl;
+
+        for (uint8_t val(0); 0 < keyCount--; ++val)
+        {
+            auto key("key_" + std::to_string(val));
+
+            auto wStart(std::chrono::high_resolution_clock::now());
+            execSet(std::ref(*sdl), ns, key, {val}, out);
+            auto wEnd(std::chrono::high_resolution_clock::now());
+            auto writeLatency_us = std::chrono::duration_cast<std::chrono::microseconds>(wEnd - wStart);
+
+            auto rStart(std::chrono::high_resolution_clock::now());
+            execGet(std::ref(*sdl), ns, key, {val}, out);
+            auto rEnd(std::chrono::high_resolution_clock::now());
+            auto readLatency_us = std::chrono::duration_cast<std::chrono::microseconds>(rEnd - rStart);
+
+            out << ns << '\t'
+                << key << '\t'
+                << std::dec << static_cast<int>(val) << "\t"
+                << std::dec << writeLatency_us.count() << "\t"
+                << std::dec << readLatency_us.count() << std::endl;
+        }
+
+        auto start(std::chrono::high_resolution_clock::now());
+        execRemoveAll(std::ref(*sdl), ns, out);
+        auto end(std::chrono::high_resolution_clock::now());
+        auto used_us = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+        out << "All test keys removed in " << used_us.count() << " microseconds" << std::endl;
+
+        return EXIT_SUCCESS;
+    }
+}
+
+AUTO_REGISTER_COMMAND(std::bind(TestGetSetCommand, std::placeholders::_1, std::placeholders::_3),
+                      "test-get-set",
+                      "Write and read to DB and check latency",
+                      "Check that basic SDL api commands (set/get) works normally and measure latency.",
+                      CommandMap::Category::UTIL, 30010,
+                      ("key-count", boost::program_options::value<int>()->default_value(10), "Number of write/read keys")
+                      ("timeout", boost::program_options::value<int>()->default_value(0), "Timeout (in seconds), Default is no timeout"));