Skip to content

Commit

Permalink
[OCONF-782] Add Apricot backend (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
awegrzyn authored Dec 18, 2023
1 parent f2c7a5d commit adfc272
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 2 deletions.
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

0 comments on commit adfc272

Please sign in to comment.