Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OCONF-782] Add Apricot backend #77

Merged
merged 6 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ endif()

# Define project
project(Configuration
VERSION 2.6.3
VERSION 2.7.0
DESCRIPTION "O2 Configuration library"
LANGUAGES CXX
)
Expand Down Expand Up @@ -74,6 +74,7 @@ endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")

find_package(Boost 1.56.0 COMPONENTS unit_test_framework program_options REQUIRED)
find_package(CURL MODULE REQUIRED)
find_package(Git QUIET)
find_package(ppconsul CONFIG)

Expand Down Expand Up @@ -105,6 +106,7 @@ set(SRCS
src/Backends/Ini/IniBackend.cxx
src/Backends/String/StringBackend.cxx
src/Backends/Json/JsonBackend.cxx
src/Backends/Apricot/ApricotBackend.cxx
src/ConfigurationInterface.cxx
src/ConfigurationFactory.cxx
)
Expand All @@ -131,6 +133,7 @@ target_link_libraries(Configuration
Boost::boost
PRIVATE
$<$<BOOL:${ppconsul_FOUND}>:ppconsul>
CURL::libcurl
)

# Handle Ppconsul optional dependency
Expand Down Expand Up @@ -159,6 +162,7 @@ set(TEST_SRCS
test/TestIni.cxx
test/TestJson.cxx
test/TestString.cxx
test/TestApricot.cxx
)

if(ppconsul_FOUND)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ The URI is constructed based on the table below:
| Consul JSON | `consul-json://` | Consul host | Consul port | Path to a value with JSON data | [ppconsul](https://github.com/oliora/ppconsul) |
| Consul INI | `consul-ini://` | Consul host | Consul port | Path to a value with INI data | [ppconsul](https://github.com/oliora/ppconsul) |
| String | `str://` | - | - | List of `;` separated key-values; `.` is used to define levels (as in `ptree`) | - |
| Apricot | `apricot://` | Server's hostname | Server's port | - | `cURL` |


## Getting values
Expand Down
117 changes: 117 additions & 0 deletions src/Backends/Apricot/ApricotBackend.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
// All rights not expressly granted are reserved.
//
// This software is distributed under the terms of the GNU General Public
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
//
// In applying this license CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

/// \file ApricotBackend.h
/// \brief Configuration interface to the Apricot key-value store
///
/// \author Pascal Boeschoten, CERN

#include "ApricotBackend.h"
#include <boost/property_tree/json_parser.hpp>

namespace o2
{
namespace configuration
{
namespace backends
{

std::size_t WriteData(const char* in, std::size_t size, std::size_t num, std::string* out) {
const std::size_t totalBytes(size * num);
out->append(in, totalBytes);
return totalBytes;
};

ApricotBackend::ApricotBackend(const std::string& host, int port) :
mUrl(host + ":" + std::to_string(port))
{
mCurl = curl_easy_init();
curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(mCurl, CURLOPT_CONNECTTIMEOUT, 3);
curl_easy_setopt(mCurl, CURLOPT_TIMEOUT, 3);
}

ApricotBackend::~ApricotBackend()
{
curl_easy_cleanup(mCurl);
curl_global_cleanup();
}

auto ApricotBackend::replaceDefaultWithSlash(const std::string& path) -> std::string
{
auto p = path;
std::replace(p.begin(), p.end(), getSeparator(), '/');
return p;
}

void ApricotBackend::putString(const std::string& /*path*/, const std::string& /*value*/)
{
throw std::runtime_error("Apricot backend does not support putting values");
}

boost::optional<std::string> ApricotBackend::getString(const std::string& path)
{
return get(path);
}


std::string ApricotBackend::get(const std::string& path) {
std::string response;
std::string url = mUrl + "/" + replaceDefaultWithSlash(addApricotPrefix(path)) + mQueryParams;
CURLcode res;
long responseCode;
curl_easy_setopt(mCurl, CURLOPT_URL, url.c_str());
curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, WriteData);
curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, &response);

res = curl_easy_perform(mCurl);
curl_easy_getinfo(mCurl, CURLINFO_RESPONSE_CODE, &responseCode);

if (res != CURLE_OK) {
throw std::runtime_error(std::string(curl_easy_strerror(res)) + " " + url);
return {};
}
if (responseCode < 200 || responseCode > 206) {
throw std::runtime_error("Wrong status code: " + std::to_string(responseCode));
}
return response;
}

boost::property_tree::ptree ApricotBackend::getRecursive(const std::string& path)
{
std::istringstream ss;
ss.str(get(path));
boost::property_tree::ptree tree;
boost::property_tree::read_json(ss, tree);
return tree;
}

KeyValueMap ApricotBackend::getRecursiveMap(const std::string& path)
{
KeyValueMap map;
auto subTree = getRecursive(path);

// define lambda to recursively interate tree
using boost::property_tree::ptree;
std::function<void(const ptree&, std::string)> parse = [&](const ptree& node, std::string key) {
map[key] = std::move(node.data());
key = key.empty() ? "" : key + getSeparator();
for (auto const &it: node) {
parse(it.second, key + it.first);
}
};
parse(subTree, std::string());
return map;
}

} // namespace backends
} // namespace configuration
} // namespace o2
92 changes: 92 additions & 0 deletions src/Backends/Apricot/ApricotBackend.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
// All rights not expressly granted are reserved.
//
// This software is distributed under the terms of the GNU General Public
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
//
// In applying this license CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

/// \file ApricotBackend.h
/// \brief Configuration interface to the Apricot key-value store
///
/// \author Pascal Boeschoten, CERN
/// \author Adam Wegrzynek, CERN

#ifndef O2_CONFIGURATION_BACKENDS_APRICOTBACKEND_H_
#define O2_CONFIGURATION_BACKENDS_APRICOTBACKEND_H_

#include "../BackendBase.h"
#include <curl/curl.h>
#include <string>

namespace o2
{
namespace configuration
{
namespace backends
{

/// Backend for Apricot
class ApricotBackend final : public BackendBase
{
public:
/// Connects to Apricot backend
ApricotBackend(const std::string& host, int port);

virtual ~ApricotBackend();
virtual void putString(const std::string& path, const std::string& value) override;
virtual boost::optional<std::string> getString(const std::string& path) override;
virtual KeyValueMap getRecursiveMap(const std::string&) override;
virtual boost::property_tree::ptree getRecursive(const std::string& path) override;

void setBasePrefix(const std::string& path)
{
mBasePrefix = path;
}

auto setParams(const std::string& params)
{
mQueryParams = params;
}

private:
/// Query params
std::string mQueryParams;

/// Base prefix
std::string mBasePrefix;

/// CURL handle
CURL *mCurl;

/// Apricot URL
std::string mUrl;

/// Replaces DEFAULT_SEPARATOR with '/', this is required by ppconsul
/// \param path A path with DEFAULT_SEPARATOR
/// \retrun A path with '/' separator
std::string replaceDefaultWithSlash(const std::string& path);

/// Replaces '/' with DEFAULT_SEPARATOR
/// \param path A path with '/' separator
/// \return A path with DEFAULT_SEPARATOR
std::string replaceSlashWithDefault(const std::string& path);

/// Runs request against Apricot server
std::string get(const std::string& path);

/// Adds base prefix to requested path
auto addApricotPrefix(const std::string& path)
{
return mBasePrefix.empty() ? addPrefix(path) : mBasePrefix + getSeparator() + addPrefix(path);
}
};

} // namespace backends
} // namespace configuration
} // namespace o2

#endif // O2_CONFIGURATION_BACKENDS_APRICOTBACKEND_H_
18 changes: 17 additions & 1 deletion src/ConfigurationFactory.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "Backends/Json/JsonBackend.h"
#include "Backends/String/StringBackend.h"
#include <Backends/Ini/IniBackend.h>
#include <Backends/Apricot/ApricotBackend.h>
#include <functional>
#include <map>
#include <stdexcept>
Expand Down Expand Up @@ -72,6 +73,20 @@ auto getString(const http::url& uri) -> UniqueConfiguration
return backend;
}

auto getApricot(const http::url& uri) -> UniqueConfiguration
{
auto apricot = std::make_unique<backends::ApricotBackend>(uri.host, uri.port);
if (!uri.path.empty()) {
apricot->setBasePrefix(uri.path.substr(1));
}
if (!uri.search.empty()) {
apricot->setParams("?" + uri.search + "&process=true");
} else {
apricot->setParams("?process=true");
}
return apricot;
}

#ifdef FLP_CONFIGURATION_BACKEND_CONSUL_ENABLED
auto getConsul(const http::url& uri) -> UniqueConfiguration
{
Expand Down Expand Up @@ -130,7 +145,8 @@ auto ConfigurationFactory::getConfiguration(const std::string& uri) -> UniqueCon
{"consul", getConsul},
{"consul-ini", getConsulIni},
{"consul-json", getConsulJson},
{"str", getString}};
{"str", getString},
{"apricot", getApricot}};

auto iterator = map.find(parsedUrl.protocol);
if (iterator != map.end()) {
Expand Down
57 changes: 57 additions & 0 deletions test/TestApricot.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/// \file TestApricot.cxx
/// \brief Unit tests for the Configuration.
///
/// \author Adam Wegrzynek, CERN
///

#include "Configuration/ConfigurationFactory.h"
#include "Configuration/ConfigurationInterface.h"

#define BOOST_TEST_MODULE ApricotBackend
#define BOOST_TEST_MAIN
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>

using namespace o2::configuration;

namespace {

const std::string APRICOT_ENDPOINT = "127.0.0.1:32188";


BOOST_AUTO_TEST_SUITE(optionalTest, * boost::unit_test::disabled())


BOOST_AUTO_TEST_CASE(simpleCheck)
{
auto conf = ConfigurationFactory::getConfiguration("apricot://" + APRICOT_ENDPOINT);
auto tree = conf->getRecursive("components.qc.ANY.any.tpc-full-qcmn");
BOOST_CHECK_EQUAL(tree.get<std::string>("qc.config.database.implementation"), "CCDB");
BOOST_CHECK_EQUAL(tree.get<std::string>("qc.tasks.RawDigits.moduleName"), "QcTPC");

}


BOOST_AUTO_TEST_CASE(simpleWithProcess)
{
auto conf = ConfigurationFactory::getConfiguration("apricot://" + APRICOT_ENDPOINT + "/components/qc/ANY/apricottest/Adam?run_type=PHYSICS");
auto tree = conf->getRecursive("");
BOOST_CHECK_EQUAL(tree.get<std::string>("bookkeeping.url"), "localhost:4001");
BOOST_CHECK_EQUAL(tree.get<std::string>("Barth"), "true");
}

BOOST_AUTO_TEST_CASE(simpleWithoutProcess)
{
auto conf = ConfigurationFactory::getConfiguration("apricot://" + APRICOT_ENDPOINT + "/components/qc/ANY/apricottest/Adam");
auto tree = conf->getRecursive("");
BOOST_CHECK_EQUAL(tree.get<std::string>("Barth"), "true");
BOOST_CHECK_THROW(tree.get<std::string>("bookkeeping.url"), boost::wrapexcept<boost::property_tree::ptree_bad_path>);
}
BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_CASE(Dummy)
{
BOOST_CHECK(true);
}

} // Anonymous namespace
Loading