From b121219ced3d643dad4030e4185ab10d648821d7 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Tue, 1 Apr 2025 17:39:34 +0200 Subject: [PATCH 01/33] Add RunLumi dto for CTP --- CrestApi/RunLumiDto.h | 60 +++++++++++++++++++++++++++++++++++++++++++ src/RunLumiDto.cxx | 27 +++++++++++++++++++ test/CMakeLists.txt | 2 +- test/test-json.cxx | 5 +++- 4 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 CrestApi/RunLumiDto.h create mode 100644 src/RunLumiDto.cxx diff --git a/CrestApi/RunLumiDto.h b/CrestApi/RunLumiDto.h new file mode 100644 index 0000000..bbd61e8 --- /dev/null +++ b/CrestApi/RunLumiDto.h @@ -0,0 +1,60 @@ +/* + Copyright (C) 2019-2024 CERN for the benefit of the ATLAS collaboration + */ + +#ifndef CREST_RUN_LUMI_DTO_HPP +#define CREST_RUN_LUMI_DTO_HPP + +#include <CrestApi/CrestException.h> +#include <_types/_uint64_t.h> + +#include <nlohmann/json.hpp> +#include <optional> +#include <string> + +using json = nlohmann::json; + +namespace Crest { + +/** + * @brief The RunLumiDto class + * It contains CTP information of run,lb,start and end time of each lumi block. + * + * @param runNumber The run number. + * @param lb The lumi block number. + * @param starttime The start time of the lumi block, in milliseconds since + * epoch. + * @param endtime The end time of the lumi block, in milliseconds since epoch. + */ + +class RunLumiDto { + private: + uint64_t starttime; + uint64_t endtime; + uint64_t runNumber; + uint64_t lb; + + public: + // Ctor + RunLumiDto(uint64_t runNumber, uint64_t lb, uint64_t starttime, + uint64_t endtime) + : runNumber(runNumber), lb(lb), starttime(starttime), endtime(endtime) {}; + // Default Ctor + RunLumiDto(); + const uint64_t &getRunNumber() const { return runNumber; } + const uint64_t &getLumiBlock() const { return lb; } + const uint64_t &getStartTime() const { return starttime; } + const uint64_t &getEndTime() const { return endtime; } + // Define setters + void setRunNumber(uint64_t runNumber) { this->runNumber = runNumber; } + void setLumiBlock(uint64_t lb) { this->lb = lb; } + void setStartTime(uint64_t starttime) { this->starttime = starttime; } + void setEndTime(uint64_t endtime) { this->endtime = endtime; } + + json toJson() const; + static RunLumiDto fromJson(const json &j); +}; + +} // namespace Crest + +#endif // CREST_RUN_LUMI_DTO_HPP diff --git a/src/RunLumiDto.cxx b/src/RunLumiDto.cxx new file mode 100644 index 0000000..e89e0b0 --- /dev/null +++ b/src/RunLumiDto.cxx @@ -0,0 +1,27 @@ +/* + Copyright (C) 2020-2024 CERN for the benefit of the ATLAS collaboration +*/ + +#include <CrestApi/RunLumiDto.h> + +namespace Crest { + +json RunLumiDto::toJson() const { + json rl = {}; + rl["runNumber"] = runNumber; + rl["lb"] = lb; + rl["starttime"] = starttime; + rl["endtime"] = endtime; + return rl; +} + +RunLumiDto RunLumiDto::fromJson(const json &j) { + RunLumiDto rl; + rl.runNumber = j.value<uint64_t>("runNumber", 0); + rl.lb = j.value<uint64_t>("lb", 0); + rl.starttime = j.value<uint64_t>("starttime", 0); + rl.endtime = j.value<uint64_t>("endtime", 0); + return rl; +} + +} // namespace Crest diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e12cbee..b3da9ac 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,4 +19,4 @@ endfunction() crestapi_add_test(CrestApi_test) crestapi_add_test(CrestApiFs_test) crestapi_add_test(json_parse) -#crestapi_add_test(test-json) +crestapi_add_test(test-json) diff --git a/test/test-json.cxx b/test/test-json.cxx index bfd3495..a93618a 100644 --- a/test/test-json.cxx +++ b/test/test-json.cxx @@ -79,10 +79,11 @@ int main() { std::cout << std::endl; // Fill a json object with data + bool test_boolean = true; json j4; j4["a_char"] = new char('c'); j4["a_string"] = "string"; - j4["a_bool"] = true; + j4["a_bool"] = test_boolean; j4["a_int"] = 42; j4["a_float"] = 3.141; j4["a_null"] = nullptr; @@ -90,6 +91,8 @@ int main() { j4["an_unsigned_long"] = 42ul; j4["an_unsigned_long_long"] = 42ull; + // Print the json object + std::cout << "Print JSON object j4:\n"; std::cout << j4 << '\n'; // Read back data using iterators -- GitLab From 816e3248a6f902845688f5759277b933e16bc01b Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Tue, 1 Apr 2025 17:45:07 +0200 Subject: [PATCH 02/33] Add run lumi set --- CMakeLists.txt | 4 ++++ CrestApi/RunLumiSetDto.h | 45 ++++++++++++++++++++++++++++++++++++++++ src/RunLumiSetDto.cxx | 30 +++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 CrestApi/RunLumiSetDto.h create mode 100644 src/RunLumiSetDto.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index c555d5b..78148b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,8 @@ set(HEADERS "CrestApi/TagMetaDto.h" "CrestApi/TagMetaSetDto.h" "CrestApi/TagSetDto.h" + "CrestApi/RunLumiDto.h" + "CrestApi/RunLumiSetDto.h" "${CRESTAPI_VERSION_FILE_DIR}/version.h") set(SOURCES @@ -103,6 +105,8 @@ set(SOURCES "src/StoreSetDto.cxx" "src/TagInfoDto.cxx" "src/TagSetDto.cxx" + "src/RunLumiDto.cxx" + "src/RunLumiSetDto.cxx" ) # Set up the build of the main library of the project. diff --git a/CrestApi/RunLumiSetDto.h b/CrestApi/RunLumiSetDto.h new file mode 100644 index 0000000..186217a --- /dev/null +++ b/CrestApi/RunLumiSetDto.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2019-2024 CERN for the benefit of the ATLAS collaboration + */ + +#ifndef CREST_RUN_LUMI_SET_DTO_HPP +#define CREST_RUN_LUMI_SET_DTO_HPP + +#include <CrestApi/CrestBaseResponse.h> +#include <CrestApi/CrestDetail.h> +#include <CrestApi/CrestException.h> +#include <CrestApi/RunLumiDto.h> + +#include <string> +#include <vector> + +#include "nlohmann/json.hpp" + +using json = nlohmann::json; + +namespace Crest { + +class RunLumiSetDto : public CrestBaseResponse { + private: + inline static const std::string s_dtoType = "RunLumiSetDto"; + std::vector<RunLumiDto> resources; + + public: + RunLumiSetDto(const std::vector<RunLumiDto> &resources) + : resources(resources) {} + RunLumiSetDto() : resources() {} + const std::string &getFormat() const { return s_dtoType; } + + size_t size() const { return resources.size(); } + + void add(const RunLumiDto &rl) { resources.push_back(rl); } + + const std::vector<RunLumiDto> &getResources() const { return resources; } + + json toJson() const; + static RunLumiSetDto fromJson(const json &j); +}; + +} // namespace Crest + +#endif // CREST_RUN_LUMI_SET_DTO_HPP diff --git a/src/RunLumiSetDto.cxx b/src/RunLumiSetDto.cxx new file mode 100644 index 0000000..a6499b1 --- /dev/null +++ b/src/RunLumiSetDto.cxx @@ -0,0 +1,30 @@ +/* + Copyright (C) 2020-2024 CERN for the benefit of the ATLAS collaboration +*/ + +#include <CrestApi/RunLumiSetDto.h> + +namespace Crest { + +json RunLumiSetDto::toJson() const { + json baseJson = CrestBaseResponse::toJson(); + json jsonResources = json::array(); + for (const auto &resource : resources) { + jsonResources.push_back(((RunLumiDto)resource).toJson()); + } + baseJson["resources"] = jsonResources; + return baseJson; +} + +RunLumiSetDto RunLumiSetDto::fromJson(const json &j) { + RunLumiSetDto rlSet; + rlSet.loadFromJson(j); + json jsonResources = j.value("resources", json::array()); + for (auto it = jsonResources.begin(); it != jsonResources.end(); ++it) { + rlSet.resources.push_back(RunLumiDto::fromJson(*it)); + } + + return rlSet; +} + +} // namespace Crest -- GitLab From 19a259127b0c016254fbca35e9324356eacdaf32 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Tue, 1 Apr 2025 18:03:05 +0200 Subject: [PATCH 03/33] Add function to retrieve run lumi --- CrestApi/CrestApi.h | 17 +++++++++++++++++ CrestApi/RunLumiDto.h | 4 ++-- src/CrestApi.cxx | 41 +++++++++++++++++++++++++++++++++++++++++ src/RunLumiDto.cxx | 2 ++ 4 files changed, 62 insertions(+), 2 deletions(-) diff --git a/CrestApi/CrestApi.h b/CrestApi/CrestApi.h index 6caa33f..5ed9713 100644 --- a/CrestApi/CrestApi.h +++ b/CrestApi/CrestApi.h @@ -34,6 +34,7 @@ #include <CrestApi/PayloadDto.h> #include <CrestApi/PayloadSetDto.h> #include <CrestApi/PayloadTagInfoSetDto.h> +#include <CrestApi/RunLumiSetDto.h> #include <CrestApi/StoreSetDto.h> #include <CrestApi/TagMetaSetDto.h> #include <CrestApi/TagSetDto.h> @@ -548,6 +549,22 @@ class CrestApi : public CrestApiBase { */ PayloadTagInfoSetDto listPayloadTagInfo(const std::string &tagname, int size, int page, const std::string &sort); + + /** + * This method retrieves run lumi information. + * @param since - since time (the beginning of the time interval), + * @param until - until time (end of the time interval), + * @param format - time format for since/until ([iso, number, run-lumi]), + * @param mode - the mode parameter is used to determine the meaning of + * since/until [daterange, runrange]. + * @param size - page size, + * @param page - page number, + * @param sort - sorting order (runNumber:ASC or runNumber:DESC), + * @return JSON run lumi info list as a RunLumiSetDto.<br> + */ + RunLumiSetDto listRunInfo(const std::string &since, const std::string &until, + const std::string format, const std::string mode, + int size, int page, const std::string &sort); }; } // namespace Crest diff --git a/CrestApi/RunLumiDto.h b/CrestApi/RunLumiDto.h index bbd61e8..10c49dc 100644 --- a/CrestApi/RunLumiDto.h +++ b/CrestApi/RunLumiDto.h @@ -29,10 +29,10 @@ namespace Crest { class RunLumiDto { private: - uint64_t starttime; - uint64_t endtime; uint64_t runNumber; uint64_t lb; + uint64_t starttime; + uint64_t endtime; public: // Ctor diff --git a/src/CrestApi.cxx b/src/CrestApi.cxx index 2aad225..a8df227 100644 --- a/src/CrestApi.cxx +++ b/src/CrestApi.cxx @@ -761,4 +761,45 @@ void CrestApi::checkHash(const std::string &hash, const std::string &str, return; } +RunLumiSetDto CrestApi::listRunInfo(const std::string &since, + const std::string &until, + const std::string format, + const std::string mode, int size, int page, + const std::string &sort) { + std::string current_path = m_PATH; + current_path += urlPaths[UrlPathIndex::RUNINFO]; + current_path += "?since="; + current_path += since; + current_path += "&until="; + current_path += until; + current_path += "&format="; + current_path += format; + current_path += "&mode="; + current_path += mode; + + if (size != -1) { + current_path += "&size="; + current_path += std::to_string(size); + } + if (page != -1) { + current_path += "&page="; + current_path += std::to_string(page); + } + if (sort != "") { + current_path += "&sort="; + current_path += sort; + } + + std::string retv; + + nlohmann::json js = nullptr; + retv = m_request.performRequest(current_path, Action::GET, js); + nlohmann::json response = StringUtils::getJson(retv); + + RunLumiSetDto dto = RunLumiSetDto::fromJson(response); + + return dto; +} +//============================================================================================================== + } // namespace Crest diff --git a/src/RunLumiDto.cxx b/src/RunLumiDto.cxx index e89e0b0..2b1e5d6 100644 --- a/src/RunLumiDto.cxx +++ b/src/RunLumiDto.cxx @@ -6,6 +6,8 @@ namespace Crest { +RunLumiDto::RunLumiDto() : runNumber(0), lb(0), starttime(0), endtime(0) {} + json RunLumiDto::toJson() const { json rl = {}; rl["runNumber"] = runNumber; -- GitLab From 03c46c76dad87af2b032068cd94c0b464771dbb2 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Wed, 2 Apr 2025 21:38:48 +0200 Subject: [PATCH 04/33] add crest resolver example for ACTS --- test/CMakeLists.txt | 1 + test/CrestFileResolver.cxx | 151 +++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 test/CrestFileResolver.cxx diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b3da9ac..a3aef12 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -20,3 +20,4 @@ crestapi_add_test(CrestApi_test) crestapi_add_test(CrestApiFs_test) crestapi_add_test(json_parse) crestapi_add_test(test-json) +crestapi_add_test(CrestFileResolver) diff --git a/test/CrestFileResolver.cxx b/test/CrestFileResolver.cxx new file mode 100644 index 0000000..f0da3d5 --- /dev/null +++ b/test/CrestFileResolver.cxx @@ -0,0 +1,151 @@ + +/** + * @file CrestApi/test/CrestApi_test.cxx + * @brief Some tests for server methods. + */ + +#include "CrestApi/GlobalTagMapDto.h" +#include "CrestApi/GlobalTagMapSetDto.h" +#include "CrestApi/IovSetDto.h" +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN +#define BOOST_TEST_MODULE TEST_CRESTAPI + +#include <fstream> +#include <iostream> +#include <string> + +#include "../CrestApi/CrestApi.h" +#include "../CrestApi/StringUtils.h" + +using namespace Crest; + +const std::string crest_server = "http://atlaf-alma9-02.cern.ch:8080/api-v6.0"; +const std::string global_tag = "TEST-GEOMETRY-01"; + +class CrestFileResolver { + private: + std::string m_crest_server; + CrestApi m_crestApi; + std::string geometry_global_tag; + std::string mat_tag = "none"; + std::string mat_label = "/Material/TrackingGeo"; + std::string m_file_path; + + public: + CrestFileResolver(const std::string &crest_server) + : m_crest_server(crest_server), + m_crestApi(crest_server), + geometry_global_tag("TEST-GEOMETRY-01"), + m_file_path("/tmp/crest_test/") { + // Check CrestApi version + if (m_crestApi.getCrestVersion() != "v6.0") { + std::cerr << "CrestApi version mismatch!" << std::endl; + } + } + + CrestFileResolver(const std::string &crest_server, + const std::string &file_path, + const std::string &geometry_gtag, const std::string &label) + : m_crest_server(crest_server), + m_crestApi(crest_server), + geometry_global_tag(geometry_gtag), + mat_tag("none"), + mat_label(label), + m_file_path(file_path) { + // Check CrestApi version + if (m_crestApi.getCrestVersion() != "v6.0") { + std::cerr << "CrestApi version mismatch!" << std::endl; + } + } + + std::string resolveFilePath(const std::string &geometry_gtag, + const std::string &file_name) { + // Here you can implement the logic to resolve the file path. + // Use crestapi to resolve mappings and detect the tag. + std::cout << "Load mappings for " << geometry_global_tag << std::endl; + GlobalTagMapSetDto mapset = + m_crestApi.findGlobalTagMap(geometry_global_tag, "Trace"); + std::cout << "test: listTagsInGlobalTag (Trace) =" << std::endl; + std::cout << mapset.toJson().dump(4) << std::endl; + + if (mapset.getResources().size() < 1) { + std::cerr << "No mapping found for the global tag: " + << geometry_global_tag << std::endl; + return ""; + } + // Get the mapping (geometry tag name) + GlobalTagMapDto map = mapset.getResources()[0]; + std::cout << "Found map " << map.toJson().dump(4) << std::endl; + std::cout << "Compare label " << this->mat_label << " with " + << map.getLabel() << std::endl; + // Check that the label is correct + if (this->mat_label != map.getLabel()) { + std::cerr << "Label mismatch: expected " << mat_label << ", got " + << map.getLabel() << std::endl; + return ""; + } + this->mat_tag = map.getTagName(); + std::cout << "Load iovs for tag " << this->mat_tag << std::endl; + // Now get the IOV. + IovSetDto iovset = + m_crestApi.selectIovs(this->mat_tag, 0, 10, 0, 10, 0, "id.since:ASC"); + if (iovset.getResources().size() < 1) { + std::cerr << "No IOV found for the tag: " << this->mat_tag << std::endl; + return ""; + } + // Get the IOV + IovDto iov = iovset.getResources()[0]; + std::cout << "Found IOV " << iov.toJson().dump(4) << std::endl; + // Get the payload using the iov hash + std::string hash = iov.getPayloadHash(); + std::string payload = m_crestApi.getPayload(hash); + // Dump the payload to a file + std::string file_path = m_file_path + file_name; + // Write the payload to the file + std::cout << "Dump payload to file " << file_path << std::endl; + dumpToFile(file_path, payload); + return file_path; + } + + void dumpToFile(const std::string &filePath, const std::string &content) { + // Open the file in write mode + std::ofstream outFile(filePath); + + // Check if the file was successfully opened + if (!outFile) { + std::cerr << "Error: Could not open file " << filePath << " for writing." + << std::endl; + return; + } + + // Write the content to the file + outFile << content; + + // Close the file + outFile.close(); + + std::cout << "Content successfully written to " << filePath << std::endl; + } +}; + +int main() { + std::string geoglobal_tag = "TEST-GEOMETRY-01"; + std::cout << "Global tag for geometry: " << geoglobal_tag << std::endl; + + try { + // Create an instance of CrestFileResolver + CrestFileResolver crestFileResolver(crest_server, "/tmp/crest_test/", + geoglobal_tag, "/Material/TrackingGeo"); + // Resolve the file path + std::string file_path = + crestFileResolver.resolveFilePath(geoglobal_tag, "test_file.json"); + // Print the parsed inner JSON + std::cout << "Dumped JSON from Geometry Global Tag for tracking material: " + << file_path << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error parsing JSON: " << e.what() << std::endl; + } + + return 0; +} -- GitLab From 3b2212d56ebbc9bfc6bb5a7f0461cb205e60fc91 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Wed, 2 Apr 2025 22:07:58 +0200 Subject: [PATCH 05/33] cherry pick from Mikhail --- CrestApi/CrestApiFs.h | 12 ++++++++++++ CrestApi/CrestBaseResponse.h | 1 + CrestApi/RespPage.h | 16 ++++++++++++++++ src/CrestApiFs.cxx | 31 ++++++++++++++++++++++++++++++- 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/CrestApi/CrestApiFs.h b/CrestApi/CrestApiFs.h index 0408d2a..715bf53 100644 --- a/CrestApi/CrestApiFs.h +++ b/CrestApi/CrestApiFs.h @@ -591,6 +591,18 @@ class CrestApiFs : public CrestApiBase { const std::string &compressionType, const std::string &version, const std::string &streamerInfo); + + /** + * This method retrieves monitoring information on payload as a list of + * payload tag information. + * @param tagname - tag name pattern, "%" can be used for any symbols, + * @param size - page size, + * @param page - page number, + * @param sort - sorting order (name:ASC or name:DESC), + * @return JSON payload tag info list as a PayloadTagInfoSetDto.<br> + */ + PayloadTagInfoSetDto listPayloadTagInfo(const std::string &tagname, int size, + int page, const std::string &sort); }; } // namespace Crest diff --git a/CrestApi/CrestBaseResponse.h b/CrestApi/CrestBaseResponse.h index 8cf2e76..281e872 100644 --- a/CrestApi/CrestBaseResponse.h +++ b/CrestApi/CrestBaseResponse.h @@ -31,6 +31,7 @@ class CrestBaseResponse { std::optional<RespPage> getRespPage() const { return page; } std::optional<GenericMap> getFilter() const { return filter; } + void setRespPage(const RespPage &page) { this->page = page; } void setDataType(const std::string &dtype) { datatype = dtype; } json toJson() const; diff --git a/CrestApi/RespPage.h b/CrestApi/RespPage.h index c27c131..c3daa49 100644 --- a/CrestApi/RespPage.h +++ b/CrestApi/RespPage.h @@ -23,11 +23,27 @@ class RespPage { int number; public: + RespPage() : size(0), totalElements(0), totalPages(0), number(0) {} + + RespPage(int size, int64_t totalElements, int totalPages, int number) + : size(size), + totalElements(totalElements), + totalPages(totalPages), + number(number) {} + int getSize() const { return size; } int64_t getTotalElements() const { return totalElements; } int getTotalPages() const { return totalPages; } int getNumber() const { return number; } + // Define setters + void setSize(int size) { this->size = size; } + void setTotalElements(int64_t totalElements) { + this->totalElements = totalElements; + } + void setTotalPages(int totalPages) { this->totalPages = totalPages; } + void setNumber(int number) { this->number = number; } + json toJson() const; static RespPage fromJson(const json &j); }; diff --git a/src/CrestApiFs.cxx b/src/CrestApiFs.cxx index 476505b..a414e22 100644 --- a/src/CrestApiFs.cxx +++ b/src/CrestApiFs.cxx @@ -160,6 +160,7 @@ GlobalTagSetDto CrestApiFs::listGlobalTags(const std::string &name, int size, "ERROR in CrestApiFs::listTags: wrong sort parameter." + sort); } + int totalItems = 0; // total global tag number try { std::vector<std::string> taglist = nameList(folder, ascending); std::vector<std::string> clearedTaglist; @@ -178,6 +179,8 @@ GlobalTagSetDto CrestApiFs::listGlobalTags(const std::string &name, int size, } } + totalItems = clearedTaglist.size(); // total global tag number + taglist = getVectorPage(clearedTaglist, size, page); for (const std::string &tag : taglist) { std::string file_name = folder + "/" + tag + "/" + s_FS_GLOBALTAG_FILE; @@ -190,6 +193,11 @@ GlobalTagSetDto CrestApiFs::listGlobalTags(const std::string &name, int size, "ERROR in CrestApiFs::listGlobalTags: cannot get the tag list."); } + // add RespPage: + int tPages = std::ceil((double)totalItems / size); + RespPage respp(tagSet.size(), totalItems, tPages, page); + tagSet.setRespPage(respp); + return tagSet; } @@ -268,6 +276,7 @@ TagSetDto CrestApiFs::listTags(const std::string &name, int size, int page, "ERROR in CrestApiFs::listTags: wrong sort parameter." + sort); } + int totalItems = 0; // total tag number try { std::vector<std::string> taglist = nameList(folder, ascending); std::vector<std::string> clearedTaglist; @@ -286,6 +295,8 @@ TagSetDto CrestApiFs::listTags(const std::string &name, int size, int page, } } + totalItems = clearedTaglist.size(); // total tag number + taglist = getVectorPage(clearedTaglist, size, page); for (const std::string &tag : taglist) { std::string file_name = folder + "/" + tag + "/" + s_FS_TAG_FILE; @@ -298,6 +309,11 @@ TagSetDto CrestApiFs::listTags(const std::string &name, int size, int page, "ERROR in CrestApiFs::listTags: cannot get the tag list."); } + // add RespPage: + int tPages = std::ceil((double)totalItems / size); + RespPage respp(tagSet.size(), totalItems, tPages, page); + tagSet.setRespPage(respp); + return tagSet; } @@ -477,6 +493,13 @@ GlobalTagMapSetDto CrestApiFs::findGlobalTagMap( GlobalTagMapSetDto dto; dto = GlobalTagMapSetDto::fromFsJson(js); + + // add RespPage: + int totalItems = dto.size(); + RespPage respp(totalItems, totalItems, 1, + 0); // full global tag map in one response + dto.setRespPage(respp); + return dto; } @@ -489,10 +512,11 @@ IovSetDto CrestApiFs::selectIovs( IovSetDto dto; nlohmann::json js = nlohmann::json::array(); + int niovs = 0; try { nlohmann::json iovList = findAllIovs(name); - int niovs = iovList.size(); + niovs = iovList.size(); for (int i = 0; i < niovs; i++) { if (iovList[i].find("since") != iovList[i].end()) { @@ -529,6 +553,11 @@ IovSetDto CrestApiFs::selectIovs( nlohmann::json ext = getPage(sorted, size, page); dto = IovSetDto::fromFsJson(ext); + // add RespPage: + int tPages = std::ceil((double)niovs / size); + RespPage respp(dto.size(), niovs, tPages, page); + dto.setRespPage(respp); + return dto; } -- GitLab From 8f85ea30f0731638ed73ac556814d7a629009dd5 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Wed, 2 Apr 2025 22:19:24 +0200 Subject: [PATCH 06/33] cherry pick (second part) --- CrestApi/CrestApiBase.h | 13 +++++++++++++ src/CrestApi.cxx | 19 +++++++++++++++++++ src/CrestApiFs.cxx | 7 +++++++ 3 files changed, 39 insertions(+) diff --git a/CrestApi/CrestApiBase.h b/CrestApi/CrestApiBase.h index 0734311..488980d 100644 --- a/CrestApi/CrestApiBase.h +++ b/CrestApi/CrestApiBase.h @@ -479,6 +479,19 @@ class CrestApiBase { * @return CrestApi library version. */ std::string getClientVersion(); + + /** + * This method retrieves monitoring information on payload as a list of + * payload tag information. + * @param tagname - tag name pattern, "%" can be used for any symbols, + * @param size - page size, + * @param page - page number, + * @param sort - sorting order (name:ASC or name:DESC), + * @return JSON payload tag info list as a PayloadTagInfoSetDto.<br> + */ + virtual PayloadTagInfoSetDto listPayloadTagInfo(const std::string &tagname, + int size, int page, + const std::string &sort) = 0; }; } // namespace Crest diff --git a/src/CrestApi.cxx b/src/CrestApi.cxx index a8df227..c39d93c 100644 --- a/src/CrestApi.cxx +++ b/src/CrestApi.cxx @@ -482,6 +482,25 @@ void CrestApi::removeGlobalTagMap(const std::string &name, nlohmann::json js = nullptr; retv = m_request.performRequest(current_path, Action::DELETE, js); } +std::string retv; +nlohmann::json js = nullptr; +retv = m_request.performRequest(current_path, Action::DELETE, js); + +nlohmann::json response = StringUtils::getJson(retv); +GlobalTagMapSetDto dto = GlobalTagMapSetDto::fromJson(response); +if (dto.size() != 0) { + GlobalTagMapDto gtmap = dto.getResources()[0]; + if (name != gtmap.getGlobalTagName() || tagname != gtmap.getTagName() || + label != gtmap.getLabel()) { + throw CrestException( + "ERROR in CrestApi::removeGlobalTagMap Cannot delete global tag map."); + } +} else { + throw CrestException( + "ERROR in CrestApi::removeGlobalTagMap Cannot delete global tag map."); +} + +} //============================================================================================================== // Iovs methods: diff --git a/src/CrestApiFs.cxx b/src/CrestApiFs.cxx index a414e22..3ff2d3c 100644 --- a/src/CrestApiFs.cxx +++ b/src/CrestApiFs.cxx @@ -1018,4 +1018,11 @@ std::string CrestApiFs::getCrestVersion() { "CREST server version for file storage."); } +PayloadTagInfoSetDto CrestApiFs::listPayloadTagInfo(const std::string &tagname, + int size, int page, + const std::string &sort) { + checkFsException("CrestApiFs::listPayloadTagInfo"); + return PayloadTagInfoSetDto(); +} + } // namespace Crest -- GitLab From 6f83209349a40686108cfca005645d48a8abab15 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Wed, 2 Apr 2025 22:23:25 +0200 Subject: [PATCH 07/33] cherry pick (second part) --- CrestApi/CrestApi.h | 3 ++- CrestApi/CrestApiFs.h | 3 ++- src/CrestApi.cxx | 26 +++++++++++--------------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/CrestApi/CrestApi.h b/CrestApi/CrestApi.h index 5ed9713..6446578 100644 --- a/CrestApi/CrestApi.h +++ b/CrestApi/CrestApi.h @@ -548,7 +548,8 @@ class CrestApi : public CrestApiBase { * @return JSON payload tag info list as a PayloadTagInfoSetDto.<br> */ PayloadTagInfoSetDto listPayloadTagInfo(const std::string &tagname, int size, - int page, const std::string &sort); + int page, + const std::string &sort) override; /** * This method retrieves run lumi information. diff --git a/CrestApi/CrestApiFs.h b/CrestApi/CrestApiFs.h index 715bf53..8890596 100644 --- a/CrestApi/CrestApiFs.h +++ b/CrestApi/CrestApiFs.h @@ -602,7 +602,8 @@ class CrestApiFs : public CrestApiBase { * @return JSON payload tag info list as a PayloadTagInfoSetDto.<br> */ PayloadTagInfoSetDto listPayloadTagInfo(const std::string &tagname, int size, - int page, const std::string &sort); + int page, + const std::string &sort) override; }; } // namespace Crest diff --git a/src/CrestApi.cxx b/src/CrestApi.cxx index c39d93c..89460a0 100644 --- a/src/CrestApi.cxx +++ b/src/CrestApi.cxx @@ -481,25 +481,21 @@ void CrestApi::removeGlobalTagMap(const std::string &name, std::string retv; nlohmann::json js = nullptr; retv = m_request.performRequest(current_path, Action::DELETE, js); -} -std::string retv; -nlohmann::json js = nullptr; -retv = m_request.performRequest(current_path, Action::DELETE, js); -nlohmann::json response = StringUtils::getJson(retv); -GlobalTagMapSetDto dto = GlobalTagMapSetDto::fromJson(response); -if (dto.size() != 0) { - GlobalTagMapDto gtmap = dto.getResources()[0]; - if (name != gtmap.getGlobalTagName() || tagname != gtmap.getTagName() || - label != gtmap.getLabel()) { + nlohmann::json response = StringUtils::getJson(retv); + GlobalTagMapSetDto dto = GlobalTagMapSetDto::fromJson(response); + if (dto.size() != 0) { + GlobalTagMapDto gtmap = dto.getResources()[0]; + if (name != gtmap.getGlobalTagName() || tagname != gtmap.getTagName() || + label != gtmap.getLabel()) { + throw CrestException( + "ERROR in CrestApi::removeGlobalTagMap Cannot delete global tag " + "map."); + } + } else { throw CrestException( "ERROR in CrestApi::removeGlobalTagMap Cannot delete global tag map."); } -} else { - throw CrestException( - "ERROR in CrestApi::removeGlobalTagMap Cannot delete global tag map."); -} - } //============================================================================================================== -- GitLab From c00538a86ca467b93807e15f6d6083545662fd30 Mon Sep 17 00:00:00 2001 From: Mikhail Mineev <Mikhail.Mineev@cern.ch> Date: Thu, 3 Apr 2025 15:04:42 +0200 Subject: [PATCH 08/33] CREST server url corrected --- CrestApi/RunLumiDto.h | 1 - src/StringUtils.cxx | 2 +- test/CrestApi_test.cxx | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CrestApi/RunLumiDto.h b/CrestApi/RunLumiDto.h index 10c49dc..5e78f72 100644 --- a/CrestApi/RunLumiDto.h +++ b/CrestApi/RunLumiDto.h @@ -6,7 +6,6 @@ #define CREST_RUN_LUMI_DTO_HPP #include <CrestApi/CrestException.h> -#include <_types/_uint64_t.h> #include <nlohmann/json.hpp> #include <optional> diff --git a/src/StringUtils.cxx b/src/StringUtils.cxx index bcf4196..f6f3e1d 100644 --- a/src/StringUtils.cxx +++ b/src/StringUtils.cxx @@ -63,7 +63,7 @@ ParsedUrl Crest::StringUtils::parseUrl(std::string_view url) { // The remaining part is the apipath if (!url.empty() && url[0] == '/') { - result.apipath = ""; + result.apipath = url; } return result; diff --git a/test/CrestApi_test.cxx b/test/CrestApi_test.cxx index 9e67512..fcb0812 100644 --- a/test/CrestApi_test.cxx +++ b/test/CrestApi_test.cxx @@ -20,7 +20,7 @@ using namespace Crest; BOOST_AUTO_TEST_SUITE(CrestApiTest) -const std::string crest_server = "https://atlaf-alma9-01.cern.ch/api-v6.0"; +const std::string crest_server = "http://atlaf-alma9-02.cern.ch:8080/api-v6.0"; const std::string tagname = "test_ctest_tag_01"; const std::string global_tag = "TEST_GLOBAL_TAG_01"; -- GitLab From 67fe7b2d76ca657b8e8703d78308203d53d35b5a Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Thu, 3 Apr 2025 22:05:31 +0200 Subject: [PATCH 09/33] add acts file resolver example --- CMakeLists.txt | 1 + acts/CMakeLists.txt | 54 ++++++++++++ acts/CrestFileResolver.cxx | 80 +++++++++++++++++ acts/CrestFileResolver.h | 68 ++++++++++++++ test/CMakeLists.txt | 4 +- test/CrestFileResolver.cxx | 151 -------------------------------- test/CrestFileResolver_test.cxx | 46 ++++++++++ 7 files changed, 252 insertions(+), 152 deletions(-) create mode 100644 acts/CMakeLists.txt create mode 100644 acts/CrestFileResolver.cxx create mode 100644 acts/CrestFileResolver.h delete mode 100644 test/CrestFileResolver.cxx create mode 100644 test/CrestFileResolver_test.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 78148b0..111d154 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,6 +154,7 @@ option(CRESTAPI_BUILD_EXAMPLES "Flag for building the examples" TRUE) if(CRESTAPI_BUILD_EXAMPLES) add_subdirectory(examples) + ## add_subdirectory(acts) endif() # Export the project. diff --git a/acts/CMakeLists.txt b/acts/CMakeLists.txt new file mode 100644 index 0000000..5f8152e --- /dev/null +++ b/acts/CMakeLists.txt @@ -0,0 +1,54 @@ +# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration +# Set the name of the package. +cmake_minimum_required(VERSION 3.12) +project(CrestResolver VERSION 6.1 LANGUAGES CXX) + +# Find the necessary externals. +find_package(CrestApi REQUIRED) + + +# Set the C++ standard to use. +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF CACHE BOOL + "Whether to enable compiler-specific C++ extensions") + +# Set the default library type to build. +option(BUILD_SHARED_LIBS + "Flag for building shared/static libraries" TRUE) + +# Use the standard GNU installation directories. +include(GNUInstallDirs) +set(CMAKE_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/CrestResolver") + +# Turn on all warnings with GCC and Clang. +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +endif() + +# Declare the header and source file names. +set(HEADERS + "./CrestFileResolver.h" +) + +set(SOURCES + "./CrestFileResolver.cxx" +) + +# Set up the build of the main library of the project. +add_library(CrestResolverLib ${SOURCES} ${HEADERS}) +target_link_libraries(CrestResolverLib + PUBLIC + CURL::libcurl + Boost::boost + nlohmann_json::nlohmann_json + CrestApi::CrestApiLib + PRIVATE + OpenSSL::SSL +) +target_include_directories(CrestResolverLib + PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> +) + diff --git a/acts/CrestFileResolver.cxx b/acts/CrestFileResolver.cxx new file mode 100644 index 0000000..a625d14 --- /dev/null +++ b/acts/CrestFileResolver.cxx @@ -0,0 +1,80 @@ + +/** + * @file CrestApi/test/CrestApi_test.cxx + * @brief Some tests for server methods. + */ + +#include "CrestFileResolver.h" + +using namespace Crest; + +std::string CrestFileResolver::resolveFilePath(const std::string &geometry_gtag, + const std::string &label, + const std::string &file_name) { + // Here you can implement the logic to resolve the file path. + // Use crestapi to resolve mappings and detect the tag. + std::cout << "Load mappings for " << geometry_gtag << std::endl; + GlobalTagMapSetDto mapset = + m_crestApi.findGlobalTagMap(geometry_gtag, "Trace"); + std::cout << "test: listTagsInGlobalTag (Trace) =" << std::endl; + std::cout << mapset.toJson().dump(4) << std::endl; + + if (mapset.getResources().size() < 1) { + std::cerr << "No mapping found for the global tag: " << geometry_gtag + << std::endl; + return ""; + } + // Get the mapping (geometry tag name) + GlobalTagMapDto map = mapset.getResources()[0]; + std::cout << "Found map " << map.toJson().dump(4) << std::endl; + std::cout << "Compare label " << this->mat_label << " with " << map.getLabel() + << std::endl; + // Check that the label is correct + if (this->mat_label != map.getLabel()) { + std::cerr << "Label mismatch: expected " << mat_label << ", got " + << map.getLabel() << std::endl; + return ""; + } + std::string mat_tag = map.getTagName(); + std::cout << "Load iovs for tag " << mat_tag << std::endl; + // Now get the IOV. + IovSetDto iovset = + m_crestApi.selectIovs(mat_tag, 0, 10, 0, 10, 0, "id.since:ASC"); + if (iovset.getResources().size() < 1) { + std::cerr << "No IOV found for the tag: " << mat_tag << std::endl; + return ""; + } + // Get the IOV + IovDto iov = iovset.getResources()[0]; + std::cout << "Found IOV " << iov.toJson().dump(4) << std::endl; + // Get the payload using the iov hash + std::string hash = iov.getPayloadHash(); + std::string payload = m_crestApi.getPayload(hash); + // Dump the payload to a file + std::string file_path = m_file_path + file_name; + // Write the payload to the file + std::cout << "Dump payload to file " << file_path << std::endl; + dumpToFile(file_path, payload); + return file_path; +} + +void CrestFileResolver::dumpToFile(const std::string &filePath, + const std::string &content) { + // Open the file in write mode + std::ofstream outFile(filePath); + + // Check if the file was successfully opened + if (!outFile) { + std::cerr << "Error: Could not open file " << filePath << " for writing." + << std::endl; + return; + } + + // Write the content to the file + outFile << content; + + // Close the file + outFile.close(); + + std::cout << "Content successfully written to " << filePath << std::endl; +} diff --git a/acts/CrestFileResolver.h b/acts/CrestFileResolver.h new file mode 100644 index 0000000..128bf0e --- /dev/null +++ b/acts/CrestFileResolver.h @@ -0,0 +1,68 @@ + +/** + * @file CrestApi/test/CrestApi_test.cxx + * @brief Some tests for server methods. + */ +#ifndef CRESTAPI_CRESTRESOLVER_H +#define CRESTAPI_CRESTRESOLVER_H + +#include <fstream> +#include <iostream> +#include <string> + +#include "CrestApi/CrestApi.h" +#include "CrestApi/GlobalTagMapDto.h" +#include "CrestApi/GlobalTagMapSetDto.h" +#include "CrestApi/IovSetDto.h" +#include "CrestApi/StringUtils.h" + +using namespace Crest; + +/** + * Class to resolve a geometry tracking file. + * These files are linked to a geometry global tag, and contain only one IOV + * (since=0). + */ +class CrestFileResolver { + private: + std::string m_crest_server; + CrestApi m_crestApi; + // This should be somehow linked to the type of geometry tag (ITK, ...) + std::string mat_label = "ITK"; + // Base directoy where the file will be dumped. + std::string m_file_path; + + public: + CrestFileResolver(const std::string &crest_server) + : m_crest_server(crest_server), + m_crestApi(crest_server), + m_file_path("/tmp/crest_test/") { + // Check CrestApi version. + if (m_crestApi.getCrestVersion() != "v6.0") { + std::cerr << "CrestApi version mismatch!" << std::endl; + } + } + + CrestFileResolver(const std::string &crest_server, + const std::string &file_path, const std::string &label) + : m_crest_server(crest_server), + m_crestApi(crest_server), + mat_label(label), + m_file_path(file_path) { + // Check CrestApi version + if (m_crestApi.getCrestVersion() != "v6.0") { + std::cerr << "CrestApi version mismatch!" << std::endl; + } + } + // Destructor + ~CrestFileResolver() = default; + + // Resolve the file path for a given geometry global tag. + std::string resolveFilePath(const std::string &geometry_gtag, + const std::string &label, + const std::string &file_name); + // Dump the payload to a file. + void dumpToFile(const std::string &filePath, const std::string &content); +}; + +#endif // CRESTAPI_CRESTRESOLVER_H \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a3aef12..68e1cb7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,6 +8,7 @@ function(crestapi_add_test TESTNAME) Boost::unit_test_framework nlohmann_json::nlohmann_json CrestApiLib + CrestResolverLib ) add_test( NAME ${TESTNAME} @@ -15,9 +16,10 @@ function(crestapi_add_test TESTNAME) ) endfunction() + # Declare the tests. crestapi_add_test(CrestApi_test) crestapi_add_test(CrestApiFs_test) crestapi_add_test(json_parse) crestapi_add_test(test-json) -crestapi_add_test(CrestFileResolver) +crestapi_add_test(CrestFileResolver_test) diff --git a/test/CrestFileResolver.cxx b/test/CrestFileResolver.cxx deleted file mode 100644 index f0da3d5..0000000 --- a/test/CrestFileResolver.cxx +++ /dev/null @@ -1,151 +0,0 @@ - -/** - * @file CrestApi/test/CrestApi_test.cxx - * @brief Some tests for server methods. - */ - -#include "CrestApi/GlobalTagMapDto.h" -#include "CrestApi/GlobalTagMapSetDto.h" -#include "CrestApi/IovSetDto.h" -#define BOOST_TEST_DYN_LINK -#define BOOST_TEST_MAIN -#define BOOST_TEST_MODULE TEST_CRESTAPI - -#include <fstream> -#include <iostream> -#include <string> - -#include "../CrestApi/CrestApi.h" -#include "../CrestApi/StringUtils.h" - -using namespace Crest; - -const std::string crest_server = "http://atlaf-alma9-02.cern.ch:8080/api-v6.0"; -const std::string global_tag = "TEST-GEOMETRY-01"; - -class CrestFileResolver { - private: - std::string m_crest_server; - CrestApi m_crestApi; - std::string geometry_global_tag; - std::string mat_tag = "none"; - std::string mat_label = "/Material/TrackingGeo"; - std::string m_file_path; - - public: - CrestFileResolver(const std::string &crest_server) - : m_crest_server(crest_server), - m_crestApi(crest_server), - geometry_global_tag("TEST-GEOMETRY-01"), - m_file_path("/tmp/crest_test/") { - // Check CrestApi version - if (m_crestApi.getCrestVersion() != "v6.0") { - std::cerr << "CrestApi version mismatch!" << std::endl; - } - } - - CrestFileResolver(const std::string &crest_server, - const std::string &file_path, - const std::string &geometry_gtag, const std::string &label) - : m_crest_server(crest_server), - m_crestApi(crest_server), - geometry_global_tag(geometry_gtag), - mat_tag("none"), - mat_label(label), - m_file_path(file_path) { - // Check CrestApi version - if (m_crestApi.getCrestVersion() != "v6.0") { - std::cerr << "CrestApi version mismatch!" << std::endl; - } - } - - std::string resolveFilePath(const std::string &geometry_gtag, - const std::string &file_name) { - // Here you can implement the logic to resolve the file path. - // Use crestapi to resolve mappings and detect the tag. - std::cout << "Load mappings for " << geometry_global_tag << std::endl; - GlobalTagMapSetDto mapset = - m_crestApi.findGlobalTagMap(geometry_global_tag, "Trace"); - std::cout << "test: listTagsInGlobalTag (Trace) =" << std::endl; - std::cout << mapset.toJson().dump(4) << std::endl; - - if (mapset.getResources().size() < 1) { - std::cerr << "No mapping found for the global tag: " - << geometry_global_tag << std::endl; - return ""; - } - // Get the mapping (geometry tag name) - GlobalTagMapDto map = mapset.getResources()[0]; - std::cout << "Found map " << map.toJson().dump(4) << std::endl; - std::cout << "Compare label " << this->mat_label << " with " - << map.getLabel() << std::endl; - // Check that the label is correct - if (this->mat_label != map.getLabel()) { - std::cerr << "Label mismatch: expected " << mat_label << ", got " - << map.getLabel() << std::endl; - return ""; - } - this->mat_tag = map.getTagName(); - std::cout << "Load iovs for tag " << this->mat_tag << std::endl; - // Now get the IOV. - IovSetDto iovset = - m_crestApi.selectIovs(this->mat_tag, 0, 10, 0, 10, 0, "id.since:ASC"); - if (iovset.getResources().size() < 1) { - std::cerr << "No IOV found for the tag: " << this->mat_tag << std::endl; - return ""; - } - // Get the IOV - IovDto iov = iovset.getResources()[0]; - std::cout << "Found IOV " << iov.toJson().dump(4) << std::endl; - // Get the payload using the iov hash - std::string hash = iov.getPayloadHash(); - std::string payload = m_crestApi.getPayload(hash); - // Dump the payload to a file - std::string file_path = m_file_path + file_name; - // Write the payload to the file - std::cout << "Dump payload to file " << file_path << std::endl; - dumpToFile(file_path, payload); - return file_path; - } - - void dumpToFile(const std::string &filePath, const std::string &content) { - // Open the file in write mode - std::ofstream outFile(filePath); - - // Check if the file was successfully opened - if (!outFile) { - std::cerr << "Error: Could not open file " << filePath << " for writing." - << std::endl; - return; - } - - // Write the content to the file - outFile << content; - - // Close the file - outFile.close(); - - std::cout << "Content successfully written to " << filePath << std::endl; - } -}; - -int main() { - std::string geoglobal_tag = "TEST-GEOMETRY-01"; - std::cout << "Global tag for geometry: " << geoglobal_tag << std::endl; - - try { - // Create an instance of CrestFileResolver - CrestFileResolver crestFileResolver(crest_server, "/tmp/crest_test/", - geoglobal_tag, "/Material/TrackingGeo"); - // Resolve the file path - std::string file_path = - crestFileResolver.resolveFilePath(geoglobal_tag, "test_file.json"); - // Print the parsed inner JSON - std::cout << "Dumped JSON from Geometry Global Tag for tracking material: " - << file_path << std::endl; - } catch (const std::exception &e) { - std::cerr << "Error parsing JSON: " << e.what() << std::endl; - } - - return 0; -} diff --git a/test/CrestFileResolver_test.cxx b/test/CrestFileResolver_test.cxx new file mode 100644 index 0000000..abb1b41 --- /dev/null +++ b/test/CrestFileResolver_test.cxx @@ -0,0 +1,46 @@ + +/** + * @file CrestApi/test/CrestApi_test.cxx + * @brief Some tests for server methods. + */ + +#include "CrestApi/GlobalTagMapDto.h" +#include "CrestApi/GlobalTagMapSetDto.h" +#include "CrestApi/IovSetDto.h" +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN +#define BOOST_TEST_MODULE TEST_CRESTAPI + +#include <fstream> +#include <iostream> +#include <string> + +#include "../CrestApi/CrestApi.h" +#include "../CrestApi/StringUtils.h" +#include "../acts/CrestFileResolver.h" + +using namespace Crest; + +const std::string crest_server = "http://atlaf-alma9-02.cern.ch:8080/api-v6.0"; +const std::string global_tag = "TEST-GEOMETRY-01"; + +int main() { + std::string geoglobal_tag = "TEST-GEOMETRY-01"; + std::cout << "Global tag for geometry: " << geoglobal_tag << std::endl; + + try { + // Create an instance of CrestFileResolver + CrestFileResolver crestFileResolver(crest_server, "/tmp/crest_test/", + "/Material/TrackingGeo"); + // Resolve the file path + std::string file_path = crestFileResolver.resolveFilePath( + geoglobal_tag, "/Material/TrackingGeo", "test_file.json"); + // Print the parsed inner JSON + std::cout << "Dumped JSON from Geometry Global Tag for tracking material: " + << file_path << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error parsing JSON: " << e.what() << std::endl; + } + + return 0; +} -- GitLab From bed2fbf27bf4043a9ef7d7be30cc996f9df39f8d Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Fri, 4 Apr 2025 15:50:24 +0200 Subject: [PATCH 10/33] remove CrestFileResolver from cmake and tests --- test/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 68e1cb7..37d9c7b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,7 +8,7 @@ function(crestapi_add_test TESTNAME) Boost::unit_test_framework nlohmann_json::nlohmann_json CrestApiLib - CrestResolverLib + # this is for ACTS: CrestResolverLib ) add_test( NAME ${TESTNAME} @@ -21,5 +21,5 @@ endfunction() crestapi_add_test(CrestApi_test) crestapi_add_test(CrestApiFs_test) crestapi_add_test(json_parse) -crestapi_add_test(test-json) -crestapi_add_test(CrestFileResolver_test) +## crestapi_add_test(test-json) +## crestapi_add_test(CrestFileResolver_test) -- GitLab From 042b5f5b21223ef0522d5cf7da2c3c42d8839f20 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Fri, 4 Apr 2025 16:47:07 +0200 Subject: [PATCH 11/33] fix CI --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f51a594..b85569c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - VERSION: "5.2" + VERSION: "6.2" IMAGE: "crestapi" # Overriding the variables from the template files. TESTS_DIR: test -- GitLab From 0717448571a78fe3d28dc8bd11f0169144945a7c Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Fri, 4 Apr 2025 16:52:44 +0200 Subject: [PATCH 12/33] fix CI --- .gitlab-ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b85569c..593a525 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -72,8 +72,6 @@ build_container_job: CONTEXT_DIR: "" PUSH_IMAGE: "true" DOCKER_FILE_NAME: "Dockerfile" - rules: - - if: '$CI_COMMIT_REF_NAME !~ /^auto-fix-.*/' apitest: stage: test @@ -107,6 +105,6 @@ apitest: crestcmd: # or any other appropriate stage stage: compile-crestcmd script: - - curl -X POST -F token=$CRESTCMD_TOKEN -F ref=release-5.1-CR2 https://gitlab.cern.ch/api/v4/projects/144796/trigger/pipeline + - curl -X POST -F token=$CRESTCMD_TOKEN -F ref=release-6.1-CR1 https://gitlab.cern.ch/api/v4/projects/144796/trigger/pipeline only: - release-5.1-CR2 # or whichever branch in Project 1 should trigger Project 2 \ No newline at end of file -- GitLab From bd8345e5eb9cb0620f64676d8acbcde074782050 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Fri, 4 Apr 2025 16:55:32 +0200 Subject: [PATCH 13/33] fix CI --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 593a525..2e74b11 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -63,8 +63,6 @@ auto-fix: - merge_requests # Optionally, run this job only on merge requests build_container_job: - rules: - - if: $DO_DOCKER_IMAGE == "yes" && $CI_PIPELINE_SOURCE != 'merge_request_event' stage: build_docker_image extends: .build_kaniko variables: @@ -72,6 +70,8 @@ build_container_job: CONTEXT_DIR: "" PUSH_IMAGE: "true" DOCKER_FILE_NAME: "Dockerfile" + rules: + - if: '$CI_COMMIT_REF_NAME !~ /^auto-fix-.*/' apitest: stage: test -- GitLab From 1ea504ec777712379a75cfbf356f0b63a8076039 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Fri, 4 Apr 2025 21:59:06 +0200 Subject: [PATCH 14/33] fix doc --- CRESTAPI_USAGE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CRESTAPI_USAGE.md b/CRESTAPI_USAGE.md index db8670c..731ca6b 100644 --- a/CRESTAPI_USAGE.md +++ b/CRESTAPI_USAGE.md @@ -38,7 +38,7 @@ Here a set of code snippets represting this workflow. {"name", tagname}, {"timeType", "time"}, {"description", "test"}, - {"synchronization", "none"}, + {"synchronization", "ALL"}, {"insertionTime", "2018-12-18T11:32:58.081+0000"}, {"modificationTime", "2018-12-18T11:32:57.952+0000"}, {"payloadSpec", "JSON"}, // For athena reading you need to be careful to this field [crest-json-single-iov | crest-json-multi-iov] -- GitLab From de6f1c27f9756be4324fa2ae675ea2d89545d577 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Fri, 4 Apr 2025 21:59:35 +0200 Subject: [PATCH 15/33] fix doc --- examples/crest_example_fs.cxx | 26 +++++++++++++------------- examples/crest_example_server.cxx | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/crest_example_fs.cxx b/examples/crest_example_fs.cxx index 63492cc..4a9d906 100644 --- a/examples/crest_example_fs.cxx +++ b/examples/crest_example_fs.cxx @@ -41,7 +41,7 @@ void testCreateGlobalTag_FS(const std::string &tagname) { std::cout << std::endl << "test file system: createGlobalTag" << std::endl; CrestApiFs myCrestApi = CrestApiFs(true, "/tmp/crest"); - /* + /* nlohmann::json js = { {"name", tagname}, @@ -107,14 +107,14 @@ void testCreateTag_FS(const std::string &tagname) { */ // the tag innitialization with the constructor: - + TagDto dto(tagname, "time", "test"); dto.setSynchronization("ALL"); dto.setObjectType("JSON"); dto.setStatus("UNLOCKED"); dto.setLastValidatedTime(0.0); dto.setEndOfValidity(0.0); - + try { myCrestApi.createTag(dto); std::cout << std::endl << "test: createTag FS (success)" << std::endl; @@ -272,16 +272,16 @@ void testStorePayloads_FS(const std::string &tagname) { TagDto dto = TagDto(); dto = dto.fromJson(tag_js); */ - + // the tag innitialization with the constructor: - + TagDto dto(tagname, "time", "test"); dto.setSynchronization("ALL"); dto.setObjectType("JSON"); dto.setStatus("UNLOCKED"); dto.setLastValidatedTime(0.0); dto.setEndOfValidity(0.0); - + try { myCrestApi.createTag(dto); std::cout << std::endl @@ -298,7 +298,7 @@ void testStorePayloads_FS(const std::string &tagname) { /* // OLD variant, StoreSetDto defined via JSON - + // Your JSON string std::string jsonString = R"( { @@ -325,7 +325,7 @@ void testStorePayloads_FS(const std::string &tagname) { */ // New version: - + StoreSetDto storeSetDto; StoreDto dto1(1000, "", "Sample data"); @@ -349,7 +349,7 @@ void testStorePayloads_FS(const std::string &tagname) { } /* - + // Your JSON string std::string jsonString2 = R"( { @@ -376,7 +376,7 @@ void testStorePayloads_FS(const std::string &tagname) { */ // New version: - + StoreSetDto storeSetDto2; StoreDto dto3(3000, "", "Sample data 3"); @@ -766,7 +766,7 @@ void createNTags_FS(const std::string &tagname, int n) { */ // the tag innitialization with the constructor: - + TagDto dto(tg, "time", "test"); dto.setSynchronization("ALL"); dto.setObjectType("JSON"); @@ -912,14 +912,14 @@ void createNGlobalTags_FS(const std::string &tagname, int n) { GlobalTagDto dto = GlobalTagDto(); dto = dto.fromJson(js); */ - + // global tag innitialization with the constructor: GlobalTagDto dto(tg, "test", "1", "M"); dto.setValidity(0); dto.setType("N"); dto.setScenario("test"); - + try { myCrestApi.createGlobalTag(dto); std::cout << std::endl << "global tag " << tg << " created" << std::endl; diff --git a/examples/crest_example_server.cxx b/examples/crest_example_server.cxx index 4e84887..0ac1067 100644 --- a/examples/crest_example_server.cxx +++ b/examples/crest_example_server.cxx @@ -371,7 +371,7 @@ void testStorePayloads(const std::string &tagname) { */ // New version: - + StoreSetDto storeSetDto; StoreDto dto1(1000, "", "Sample data"); -- GitLab From c11c3a173d12add12b51a5cf57eac6dad4a78546 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Mon, 7 Apr 2025 10:26:44 +0200 Subject: [PATCH 16/33] create separate library for CrestContainer. --- CMakeLists.txt | 3 +- tools/CMakeLists.txt | 90 +++++ tools/CrestContainer.cxx | 411 +++++++++++++--------- tools/CrestContainer.h | 109 +++--- tools/cmake/CrestContainerConfig.cmake.in | 29 ++ 5 files changed, 416 insertions(+), 226 deletions(-) create mode 100644 tools/CMakeLists.txt create mode 100644 tools/cmake/CrestContainerConfig.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 111d154..cd45973 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,7 +154,8 @@ option(CRESTAPI_BUILD_EXAMPLES "Flag for building the examples" TRUE) if(CRESTAPI_BUILD_EXAMPLES) add_subdirectory(examples) - ## add_subdirectory(acts) + add_subdirectory(acts) + add_subdirectory(tools) endif() # Export the project. diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..694bb88 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,90 @@ +# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration +# Set the name of the package. +cmake_minimum_required(VERSION 3.12) +project(CrestContainer VERSION 6.1 LANGUAGES CXX) + +# Find the necessary externals. +find_package(CrestApi REQUIRED) + + +# Set the C++ standard to use. +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF CACHE BOOL + "Whether to enable compiler-specific C++ extensions") + +# Set the default library type to build. +option(BUILD_SHARED_LIBS + "Flag for building shared/static libraries" TRUE) + +# Use the standard GNU installation directories. +include(GNUInstallDirs) +set(CMAKE_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/CrestResolver") + +# Turn on all warnings with GCC and Clang. +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +endif() + +# Declare the header and source file names. +set(HEADERS + "./CrestContainer.h" +) + +set(SOURCES + "./CrestContainer.cxx" +) + +# Set up the build of the main library of the project. +add_library(CrestContainerLib ${SOURCES} ${HEADERS}) +target_link_libraries(CrestContainerLib + PUBLIC + CURL::libcurl + Boost::boost + nlohmann_json::nlohmann_json + CrestApi::CrestApiLib + PRIVATE + OpenSSL::SSL +) +target_include_directories(CrestContainerLib + PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> +) + +set_target_properties(CrestContainerLib PROPERTIES + PUBLIC_HEADER "${HEADERS}" + VERSION "${PROJECT_VERSION}" +) + +# Install the library. +install(TARGETS CrestContainerLib + EXPORT CrestContainerTargets + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/CrestContainer" +) + +# Export the project. +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CrestContainerConfigVersion.cmake" + VERSION "${PROJECT_VERSION}" + COMPATIBILITY AnyNewerVersion +) +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CrestContainerConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CrestContainerConfig.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_CMAKEDIR}" + PATH_VARS CMAKE_INSTALL_INCLUDEDIR CMAKE_INSTALL_LIBDIR +) +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CrestContainerConfigVersion.cmake" + "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CrestContainerConfig.cmake" + DESTINATION "${CMAKE_INSTALL_CMAKEDIR}" +) +install(EXPORT CrestContainerTargets + NAMESPACE CrestApi:: + DESTINATION "${CMAKE_INSTALL_CMAKEDIR}" +) diff --git a/tools/CrestContainer.cxx b/tools/CrestContainer.cxx index 2e31ab0..a7de28d 100644 --- a/tools/CrestContainer.cxx +++ b/tools/CrestContainer.cxx @@ -17,29 +17,13 @@ using json = nlohmann::json; -Crest::CrestContainer::CrestContainer() : m_modeId(ModeId::Standard) { - initStringToType(); +Crest::CrestContainer::CrestContainer(Crest::ModeId mode) : m_modeId(mode) { + ; } Crest::CrestContainer::~CrestContainer() { flush(); } -void Crest::CrestContainer::initStringToType() { - // Loop over key value pairs in s_typeToString map and fill s_StringToType - for (const auto &[key, value] : s_typeToString) { - s_StringToType[value] = key; - } -} - -void Crest::CrestContainer::addStreamerInfoField(const std::string &key, - const std::string &value) { - m_streamer_info_data[key] = value; -} - -void Crest::CrestContainer::cleanStreamerInfo() { - m_streamer_info_data.clear(); -} - bool Crest::CrestContainer::isVectorPayload() { return m_isVectorPayload; } @@ -49,11 +33,21 @@ void Crest::CrestContainer::setVectorPayload(bool isVectorPayload) { Crest::TypeId Crest::CrestContainer::stringToTypeId( const std::string &type) const { - auto it = s_StringToType.find(type); - if (it == s_StringToType.end()) { - throw CommonCrestException("The type of parameter is not defined."); + for (auto &t : s_typeToString) { + if (t.second.compare(type) == 0) + return t.first; } - return it->second; + throw CommonCrestException("The type of parameter is not defined."); +} + +bool Crest::CrestContainer::compareStrTimestamp(std::string as, + std::string bs) { + auto a = std::stol(as); + auto b = std::stol(bs); + + // first parameter should be greater than + // second one (for decreasing order) + return a < b; } // Function to convert an integer to a TypeId @@ -64,8 +58,8 @@ Crest::TypeId Crest::CrestContainer::intToTypeId(int value) const { return static_cast<Crest::TypeId>(value); } -std::string Crest::CrestContainer::base64Encode(const uint8_t *data, - unsigned int len) { +std::string Crest::CrestContainer::base64_encode(const uint8_t *data, + unsigned int len) { using namespace boost::archive::iterators; typedef base64_from_binary<transform_width<const char *, 6, 8>> base64_encoder; @@ -81,7 +75,7 @@ std::string Crest::CrestContainer::base64Encode(const uint8_t *data, } // Decode base64 data to binary -std::vector<unsigned char> Crest::CrestContainer::base64Decode( +std::vector<unsigned char> Crest::CrestContainer::base64_decode( const std::string &encodedData) { using namespace boost::archive::iterators; typedef transform_width<binary_from_base64<std::string::const_iterator>, 8, 6> @@ -160,10 +154,8 @@ void Crest::CrestContainer::addRecord(const char *name, int number, ...) { case TypeId::String4k: case TypeId::String64k: case TypeId::String16M: - case TypeId::String128M: case TypeId::Blob64k: case TypeId::Blob16M: - case TypeId::Blob128M: m_row[name] = std::string(va_arg(ap, const char *)); break; default: @@ -179,22 +171,21 @@ void Crest::CrestContainer::addData(const char *channel_id) { void Crest::CrestContainer::addExternalData(const char *channel_id, const nlohmann::json &data) { - - validatePayloadSize(data); + // std::cerr<<"addExternalData channel_id="<<channel_id<<" + // json="<<data.dump()<<std::endl; nlohmann::json arr_data = m_isVectorPayload ? nlohmann::json::array() : nlohmann::json(); - - for (const auto &data_row : data) // m_vector_data) - { - auto row_arr_data = createRowArray(data_row); - - if (m_isVectorPayload) { + if (m_isVectorPayload) { + for (const auto &data_row : data) // m_vector_data) + { + auto row_arr_data = createRowArray(data_row); arr_data.push_back(row_arr_data); - } else { - arr_data = row_arr_data; } + m_payload[channel_id] = arr_data; + } else { + auto row_arr_data = createRowArray(data); + m_payload[channel_id] = row_arr_data; } - m_payload[channel_id] = arr_data; m_vector_data.clear(); m_row.clear(); } @@ -203,12 +194,12 @@ void Crest::CrestContainer::validatePayloadSize( const nlohmann::json &data) const { if (!data.is_array()) throw CommonCrestException( - "The format of data is wrong. It should be the " - "vector (size 1 for non vector payload)"); + "The format of data is wrong. It should be the vector (size 1 for non " + "vector payload)"); if (!m_isVectorPayload && m_vector_data.size() > 1) { throw CommonCrestException( - "The payload is not a vector, but the size " - "seems to be larger than 1...."); + "The payload is not a vector, but the size seems to be larger than " + "1...."); } } @@ -228,13 +219,28 @@ nlohmann::json Crest::CrestContainer::createRowArray( void Crest::CrestContainer::addIov(const uint64_t since) { m_iov_data["since"] = since; m_iov_data["data"] = m_payload; - m_full_data[std::to_string(since)] = m_payload; + m_full_data[since] = m_payload; m_payload.clear(); } void Crest::CrestContainer::selectIov(const uint64_t since) { + // std::cerr<<"Crest::CrestContainer::selectIov start"<<std::endl; m_iov_data["since"] = since; - m_iov_data["data"] = m_full_data[std::to_string(since)]; + if (m_full_data.contains(since)) { + m_iov_data["data"] = m_full_data[since]; + m_payload = m_full_data[since]; + } else { + m_iov_data["data"] = {}; + m_payload = {}; + } + // std::cerr<<"Crest::CrestContainer::selectIov end"<<std::endl; +} + +uint64_t Crest::CrestContainer::getFirstTime() { + if (m_full_data.size() == 0) { + throw CommonCrestException("No IOV in CrestContainer"); + } + return m_full_data.begin()->first; } std::vector<std::string> Crest::CrestContainer::channelIds() { @@ -242,6 +248,7 @@ std::vector<std::string> Crest::CrestContainer::channelIds() { for (auto &x : m_iov_data["data"].items()) { chs.push_back(x.key()); } + sort(chs.begin(), chs.end(), Crest::CrestContainer::compareStrTimestamp); return chs; } const std::vector<std::pair<std::string, Crest::TypeId>> & @@ -249,7 +256,7 @@ Crest::CrestContainer::getMPayloadSpec() { return m_payload_spec; } -const nlohmann::json &Crest::CrestContainer::getPayloadChannel( +const nlohmann::json Crest::CrestContainer::getPayloadChannel( const char *channel_id) { if (m_payload.empty()) { m_payload = m_iov_data["data"]; @@ -297,8 +304,7 @@ void Crest::CrestContainer::putRow2Vector() { const nlohmann::json &Crest::CrestContainer::getRow() { if (m_isVectorPayload) return m_vector_data; - m_vector_data.push_back(m_row); - return m_vector_data; + return m_row; } const nlohmann::json &Crest::CrestContainer::getPayload() { @@ -336,27 +342,6 @@ std::string Crest::CrestContainer::getJsonIovData() { return m_iov_data.dump(); } -nlohmann::json Crest::CrestContainer::getStoreSetDto() { - nlohmann::json j; - nlohmann::json resources = nlohmann::json::array(); - - nlohmann::json storeDto; - // Loop over the iov list container to add the data to the storeDto - // Iterating over the keys of m_full_data - for (auto &[key, data] : m_full_data.items()) { - storeDto["since"] = (uint64_t)std::stoull(key); - storeDto["data"] = data.dump(); - storeDto["streamerInfo"] = m_streamer_info_data.dump(); - resources.push_back(storeDto); - } - - j["resources"] = resources; - j["format"] = "StoreSetDto"; - j["datatype"] = "data"; - j["size"] = resources.size(); - return j; -} - json Crest::CrestContainer::getPayloadSpec() { json pspec_data = json::array(); for (auto &column : m_payload_spec) { @@ -523,17 +508,120 @@ void Crest::CrestContainer::parseOldFormat(std::string &colName, } default: { throw std::runtime_error("UNTREATED TYPE!"); - break; } } + } catch (json::exception &e) { std::cerr << e.what() << std::endl; throw std::runtime_error(e.what()); } } - -void Crest::CrestContainer::fromJson(const uint64_t since, - const nlohmann::json &j_in) { +void Crest::CrestContainer::parseData(const nlohmann::json &values) { + if (values.is_array() && values.size() == m_payload_spec.size()) { + for (size_t i = 0; i < values.size(); ++i) { + const auto &spec = m_payload_spec[i]; + std::string colName = spec.first; + TypeId colType = spec.second; + if (values[i].is_string() && + (colType == TypeId::UChar || colType == TypeId::Bool || + colType == TypeId::Int16 || colType == TypeId::UInt16 || + colType == TypeId::Int32 || colType == TypeId::UInt32 || + colType == TypeId::Int64 || colType == TypeId::UInt63 || + colType == TypeId::Float || colType == TypeId::Double)) { + parseOldFormat(colName, colType, values[i]); + continue; + } + if (values[i].is_null()) { + m_row[colName] = nullptr; + continue; + } + switch (colType) { + case TypeId::Bool: + m_row[colName] = values[i].get<bool>(); + break; + case TypeId::UChar: + m_row[colName] = values[i].get<unsigned char>(); + break; + case TypeId::Int16: + case TypeId::Int32: + m_row[colName] = values[i].get<int>(); + break; + case TypeId::UInt16: + case TypeId::UInt32: + m_row[colName] = values[i].get<unsigned int>(); + break; + case TypeId::UInt63: + m_row[colName] = values[i].get<uint64_t>(); + break; + case TypeId::Int64: + m_row[colName] = values[i].get<int64_t>(); + break; + case TypeId::Float: + case TypeId::Double: + m_row[colName] = values[i].get<double>(); + break; + case TypeId::String255: + case TypeId::String4k: + case TypeId::String64k: + case TypeId::String16M: + case TypeId::String128M: + m_row[colName] = values[i].get<std::string>(); + break; + case TypeId::Blob128M: + case TypeId::Blob64k: + case TypeId::Blob16M: + m_row[colName] = values[i].get<std::string>(); + break; + default: + throw CommonCrestException( + "CrestContainer::parseData: Unsupported column type."); + } + } + } else { + if (!values.is_array()) + std::cerr << "CrestContainer::parseData values is not array" << std::endl; + else + std::cerr << "CrestContainer::parseData values number=" << values.size() + << " m_payload_spec.size()=" << m_payload_spec.size() + << std::endl; + throw CommonCrestException( + "CrestContainer::parseData: Mismatch in number of values."); + } +} +std::vector<uint64_t> Crest::CrestContainer::fromJson( + uint64_t since, const nlohmann::json &j_in) { + if (m_modeId == Crest::ModeId::Standard) { + std::cerr << "CrestContainer::fromJson mode=Standard" << std::endl; + nlohmann::json j = j_in; + if (j.is_string()) { + std::istringstream ss(to_string(j)); + std::string st; + ss >> std::quoted(st); + j = json::parse(st); + } + // Accessing the "data" object + if (j.contains("data") && j["data"].is_object()) { + readCommonType(since, j_in); + } else if (j.is_object()) { + readCommonType(since, j); + } else { + std::cerr << "CrestContainer::fromJson json:" << j << std::endl; + throw CommonCrestException( + "CrestContainer::fromJson: JSON is not a JSON object."); + } + std::vector<uint64_t> ret; + ret.push_back(since); + return ret; + } else if (m_modeId == Crest::ModeId::DCS_FULL) { + std::cerr << "CrestContainer::fromJson mode=DCS_FULL" << std::endl; + return readDcsFullType(j_in); + } else { + throw CommonCrestException( + "CrestContainer::fromJson: Unsupported type of payload."); + } +} +std::vector<uint64_t> Crest::CrestContainer::readDcsFullType( + const nlohmann::json &j_in) { nlohmann::json j = j_in; if (j.is_string()) { std::istringstream ss(to_string(j)); @@ -541,96 +629,101 @@ void Crest::CrestContainer::fromJson(const uint64_t since, ss >> std::quoted(st); j = json::parse(st); } - if (m_modeId != ModeId::Standard) - throw CommonCrestException("Unsupported type of payload."); - // Accessing the "data" object + // std::cerr<<"readDcsFullType: "<<j<<std::endl; + nlohmann::json dcs_data; if (j.contains("data") && j["data"].is_object()) { - const auto &data = j["data"]; - - // Loop over each channel in the data - for (const auto &channel : data.items()) { - std::string channelKey = channel.key(); - const auto &data_ch = channel.value(); - nlohmann::json vecJson(json::value_t::array); - if (m_isVectorPayload) - vecJson = data_ch; + // std::cerr<<"readDcsFullType: "<<j<<std::endl; + dcs_data = j["data"]; + } else + dcs_data = j; + std::vector<uint64_t> ret; + if (j.is_object()) { + for (const auto &[key, value] : dcs_data.items()) { + uint64_t since = std::stoull(key); + // std::cerr<<"read since="<<since<<std::endl; + readCommonType(since, value); + ret.push_back(since); + /*if(data.contains("since")) + since=data["since"].template get<std::int64_t>(); else - vecJson.push_back(data_ch); - - for (const auto &values : vecJson) { - // TODO: this will not work for vector payloads - // Check if the number of values in the array matches the expected - // number of columns - if (values.is_array() && values.size() == m_payload_spec.size()) { - for (size_t i = 0; i < values.size(); ++i) { - const auto &spec = m_payload_spec[i]; - std::string colName = spec.first; - TypeId colType = spec.second; - if (values[i].is_string() && - (colType == TypeId::Bool || colType == TypeId::Int16 || - colType == TypeId::UInt16 || colType == TypeId::Int32 || - colType == TypeId::UInt32 || colType == TypeId::Int64 || - colType == TypeId::UInt63 || colType == TypeId::Float || - colType == TypeId::Double)) { - parseOldFormat(colName, colType, values[i]); - continue; - } - switch (colType) { - case TypeId::Bool: - m_row[colName] = values[i].get<bool>(); - break; - case TypeId::UChar: - m_row[colName] = values[i].get<unsigned char>(); - break; - case TypeId::Int16: - case TypeId::Int32: - m_row[colName] = values[i].get<int>(); - break; - case TypeId::UInt16: - case TypeId::UInt32: - m_row[colName] = values[i].get<unsigned int>(); - break; - case TypeId::UInt63: - m_row[colName] = values[i].get<uint64_t>(); - break; - case TypeId::Int64: - m_row[colName] = values[i].get<int64_t>(); - break; - case TypeId::Float: - case TypeId::Double: - m_row[colName] = values[i].get<double>(); - break; - case TypeId::String255: - case TypeId::String4k: - case TypeId::String64k: - case TypeId::String16M: - case TypeId::String128M: - m_row[colName] = values[i].get<std::string>(); - break; - case TypeId::Blob128M: - case TypeId::Blob64k: - case TypeId::Blob16M: - m_row[colName] = values[i].get<std::string>(); - break; - default: - throw CommonCrestException("Unsupported column type."); - } - } - } else { - std::cerr << "CrestContainer::fromJson: Mismatch in number of values " - "for channel " - << channelKey << std::endl; - } - if (m_isVectorPayload) - putRow2Vector(); - } - addData(channelKey.c_str()); + throw CommonCrestException("CrestContainer::readDcsFullType: Wrong + payload format."); readCommonType(since,data);*/ } - } else { - std::cerr << "CrestContainer::fromJson: JSON does not contain a 'data' " - "object or it is not a JSON object." - << std::endl; - std::cerr << "CrestContainer::fromJson json:" << j << std::endl; + } + return ret; +} +void Crest::CrestContainer::readCommonType(uint64_t since, + const nlohmann::json &j_in) { + // std::cerr<<"readCommonType j_in="<<j_in.dump()<<std::endl; + // Loop over each channel in the data + for (const auto &channel : j_in.items()) { + std::string channelKey = channel.key(); + const auto &data_ch = channel.value(); + nlohmann::json vecJson(json::value_t::array); + if (m_isVectorPayload) + vecJson = data_ch; + else + vecJson.push_back(data_ch); + + for (const auto &values : vecJson) { + parseData(values); + if (m_isVectorPayload) + putRow2Vector(); + } + addData(channelKey.c_str()); } addIov(since); + // std::cout << " m_iov_data: " << m_iov_data.dump(2) << std::endl; + // std::cout << "CondContainer: from json method has filled internal + // attributes" << std::endl; +} + +nlohmann::json Crest::CrestContainer::getStoreSetDto(uint64_t period) { + nlohmann::json j; + nlohmann::json resources = nlohmann::json::array(); + if (m_modeId == Crest::ModeId::Standard) { + nlohmann::json storeDto; + // Loop over the iov list container to add the data to the storeDto + for (const auto &[key, value] : m_full_data) { + storeDto["since"] = key; + storeDto["data"] = value.dump(); + storeDto["streamerInfo"] = ""; // m_streamer_info_data.dump(); + resources.push_back(storeDto); + } + } else if (m_modeId == Crest::ModeId::DCS_FULL) { + uint64_t since = getFirstTime(); + // std::cerr<<"Number of IOV="<<m_full_data.size()<<std::endl; + nlohmann::json storeDto; + nlohmann::json dcs_payload; + nlohmann::json curPayload = m_full_data[since]; + // std::cerr<<"Number of chs="<<curPayload.size()<<std::endl; + // for (std::pair<uint64_t, uint64_t> pair : m_iovs) { + for (const auto &[key, value] : m_full_data) { + curPayload.update(value); + dcs_payload[std::to_string(key)] = curPayload; + if (period > 0 && (key - since) > period) { + storeDto["since"] = since; + storeDto["data"] = dcs_payload.dump(); + storeDto["streamerInfo"] = ""; + resources.push_back(storeDto); + since = key; + dcs_payload = {}; + } + } + // nlohmann::json obj={}; + // obj["iovs"]=iovs; + // obj["obj"]=dcs_payload; + if (dcs_payload.size() > 0) { + storeDto["since"] = since; + storeDto["data"] = dcs_payload.dump(); + storeDto["streamerInfo"] = ""; + resources.push_back(storeDto); + } + } + j["resources"] = resources; + j["format"] = "StoreSetDto"; + j["datatype"] = "data"; + j["size"] = resources.size(); + + return j; } diff --git a/tools/CrestContainer.h b/tools/CrestContainer.h index f5315e3..793b326 100644 --- a/tools/CrestContainer.h +++ b/tools/CrestContainer.h @@ -68,50 +68,22 @@ const static std::map<TypeId, std::string> s_typeToString = { enum class ModeId { Standard, DCS, DCS_FULL }; class CrestContainer { private: - std::map<std::string, TypeId> s_StringToType; std::vector<std::pair<std::string, TypeId>> m_payload_spec; nlohmann::json m_payload = {}; nlohmann::json m_row = {}; nlohmann::json m_iov_data = {}; - nlohmann::json m_streamer_info_data = {}; nlohmann::json m_vector_data = nlohmann::json::array(); - // nlohmann::json m_iov_list=nlohmann::json::array(); - - nlohmann::json m_full_data = {}; + std::map<uint64_t, nlohmann::json> m_full_data; ModeId m_modeId; bool m_isVectorPayload = false; - void initStringToType(); - void validatePayloadSize(const nlohmann::json &data) const; - nlohmann::json createRowArray(const nlohmann::json &data_row) const; - const nlohmann::json &getRow(); + void validatePayloadSize(const nlohmann::json& data) const; + nlohmann::json createRowArray(const nlohmann::json& data_row) const; + const nlohmann::json& getRow(); public: - /** - * Constructor and destructor. - * The container is used to store the payload and the iov data, helping in the - * definition of the payload structure. The payload is a json object that is - * added to the iov data. It contains a set of channels, each one with its own - * data. Once the container has been filled, it can be converted to a json - * object. When consumed, it is necessary to flush it in order to reuse it. - */ - CrestContainer(); + CrestContainer(ModeId mode = ModeId::Standard); ~CrestContainer(); - - /** - * Utility function to add a field to the streamer info. - * The streamer info is a json object that is added to the payload. - * It is used to store metadata information about the payload. - * @param key The key of the field. - * @param value The value of the field. - */ - void addStreamerInfoField(const std::string &key, const std::string &value); - - /** - * Utility function to clean the streamer info. - */ - void cleanStreamerInfo(); - /** * @brief It adds a column to the payload specification. * @param name The name of the column. @@ -121,9 +93,9 @@ class CrestContainer { * m_payload_spec. The methods with a different signature are just overloads * to make it easier to use. */ - void addColumn(const std::string &name, TypeId type); - void addColumn(const std::string &name, const char *type); - void addColumn(const std::string &name, uint32_t type); + void addColumn(const std::string& name, TypeId type); + void addColumn(const std::string& name, const char* type); + void addColumn(const std::string& name, uint32_t type); /** * @brief It sets the mode of the container. @@ -131,7 +103,7 @@ class CrestContainer { bool isVectorPayload(); void setVectorPayload(bool isVectorPayload); - Crest::TypeId stringToTypeId(const std::string &type) const; + Crest::TypeId stringToTypeId(const std::string& type) const; Crest::TypeId intToTypeId(int value) const; /** @@ -148,7 +120,7 @@ class CrestContainer { * * This method adds a null to the payload. It fills the json object m_row. */ - void addNullRecord(const char *name); + void addNullRecord(const char* name); /** * @brief It adds a record to the payload. @@ -158,7 +130,7 @@ class CrestContainer { * * This method adds a record to the payload. It fills the json object m_row. */ - void addRecord(const char *name, int number, ...); + void addRecord(const char* name, int number, ...); /** * @brief It associate the payload row to a channel_id. * @param channel_id The channel_id to associate the payload row. @@ -166,7 +138,7 @@ class CrestContainer { * The method adds the current payload row to the json object m_payload. The * row is associated with the channel_id. */ - void addData(const char *channel_id); + void addData(const char* channel_id); /** * @brief It associate the payload row to a channel_id. * @param channel_id The channel_id to associate the payload row. @@ -176,7 +148,7 @@ class CrestContainer { * associated with the channel_id. The data is the data to be associated with * the channel_id, and they belong to a previously filled payload row. */ - void addExternalData(const char *channel_id, const nlohmann::json &data); + void addExternalData(const char* channel_id, const nlohmann::json& data); /** * @brief It adds an IOV to the json object m_iov_data. * @param since The since value of the IOV. @@ -186,15 +158,15 @@ class CrestContainer { */ void addIov(const uint64_t since); - const std::vector<std::pair<std::string, TypeId>> &getMPayloadSpec(); + const std::vector<std::pair<std::string, TypeId>>& getMPayloadSpec(); template <typename T> - T getRecord(const std::string &name) { - for (const auto &column : m_payload_spec) { + T getRecord(const std::string& name) { + for (const auto& column : m_payload_spec) { if (column.first == name) { try { return m_row.at(name).get<T>(); - } catch (nlohmann::json::exception &e) { + } catch (nlohmann::json::exception& e) { throw std::runtime_error("JSON exception for key: " + name); } } @@ -202,33 +174,33 @@ class CrestContainer { throw std::runtime_error("Column name not found or type mismatch."); } - const nlohmann::json &getPayload(); - const nlohmann::json &getIovData(); + const nlohmann::json& getPayload(); + const nlohmann::json& getIovData(); - const nlohmann::json &getPayloadChannel(const char *channel_id); + const nlohmann::json getPayloadChannel(const char* channel_id); - void setIovData(const nlohmann::json &j); - void setPayload(const nlohmann::json &j); + void setIovData(const nlohmann::json& j); + void setPayload(const nlohmann::json& j); // return the payload spec as a copy nlohmann::json getPayloadSpec(); - void setPayloadSpec(const nlohmann::json &j); + void setPayloadSpec(const nlohmann::json& j); /** * @brief It encodes the input string to base64. */ - static std::string base64Encode(const uint8_t *bytes_to_encode, - unsigned int in_len); + static std::string base64_encode(const uint8_t* bytes_to_encode, + unsigned int in_len); /** * @brief It decodes the input string from base64. */ - static std::vector<unsigned char> base64Decode( - const std::string &encodedData); + static std::vector<unsigned char> base64_decode( + const std::string& encodedData); /** * @brief It returns the index of the column with the given name. * @param name The name of the column. * It checks the payload spec array to get the index back. */ - int getColumnIndex(const std::string &name); + int getColumnIndex(const std::string& name); /** * @brief It reinitializes the containers. */ @@ -244,26 +216,31 @@ class CrestContainer { * @brief It returns the json representation of the container. */ std::string getJsonIovData(); - /** - * @brief It returns the StoreSetDto json representation of the container. - */ - nlohmann::json getStoreSetDto(); /** * @brief It creates a file with the json representation of the container. */ - void dumpJsonToFile(const nlohmann::json &j, const std::string &filename); + void dumpJsonToFile(const nlohmann::json& j, const std::string& filename); /** * @brief It reads a json file and returns the json object. */ - nlohmann::json readJsonFromFile(const std::string &filename, - const std::string &spec_filename); + nlohmann::json readJsonFromFile(const std::string& filename, + const std::string& spec_filename); /** * @brief It reads a json object to fill the containers. */ - void fromJson(const uint64_t since, const nlohmann::json &j); + std::vector<uint64_t> fromJson(uint64_t since, const nlohmann::json& j); + + void parseOldFormat(std::string& colName, TypeId& typespec, + const nlohmann::json& j); + + nlohmann::json getStoreSetDto(uint64_t period = -1); + + uint64_t getFirstTime(); - void parseOldFormat(std::string &colName, TypeId &typespec, - const nlohmann::json &j); + void parseData(const nlohmann::json& values); + std::vector<uint64_t> readDcsFullType(const nlohmann::json& j_in); + void readCommonType(uint64_t since, const nlohmann::json& j_in); + static bool compareStrTimestamp(std::string as, std::string bs); }; } // namespace Crest #endif diff --git a/tools/cmake/CrestContainerConfig.cmake.in b/tools/cmake/CrestContainerConfig.cmake.in new file mode 100644 index 0000000..9a06914 --- /dev/null +++ b/tools/cmake/CrestContainerConfig.cmake.in @@ -0,0 +1,29 @@ +# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration + +# Avoid hard-coding absolute paths +# This calculates ${PACKAGE_PREFIX_DIR} at run-time based on the +# relative locations of the CMAKE_INSTALL_PREFIX and this config file +# after installation +@PACKAGE_INIT@ + +# Set up useful variables. +set_and_check(CrestContainer_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@") +set_and_check(CrestContainer_LIBRARY_DIR "@PACKAGE_CMAKE_INSTALL_LIBDIR@") + +# Find dependencies +include(CMakeFindDependencyMacro) +find_dependency(CURL) +find_dependency(OpenSSL) +find_dependency(nlohmann_json 3.2.0) +find_dependency(Boost) +find_dependency(CrestApi) + +# Use the cmake generated target +include("${CMAKE_CURRENT_LIST_DIR}/CrestContainerTargets.cmake") +set(CrestContainer_LIBRARIES CrestApi::CrestContainerLib) + +# Print a standard information message about the package being found. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CrestContainer + REQUIRED_VARS CMAKE_CURRENT_LIST_FILE + VERSION_VAR CrestContainer_VERSION ) -- GitLab From 65fe92d6c2da272fc074f570823d4b732d988d4a Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Mon, 7 Apr 2025 10:46:33 +0200 Subject: [PATCH 17/33] remove additional projects from default cmake --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd45973..eaa3a4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,8 +154,8 @@ option(CRESTAPI_BUILD_EXAMPLES "Flag for building the examples" TRUE) if(CRESTAPI_BUILD_EXAMPLES) add_subdirectory(examples) - add_subdirectory(acts) - add_subdirectory(tools) +## add_subdirectory(acts) +## add_subdirectory(tools) endif() # Export the project. -- GitLab From eaeee35ce6464445828c8fd0910106e59daf87af Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Mon, 7 Apr 2025 11:38:25 +0200 Subject: [PATCH 18/33] remove additional projects from default cmake --- CMakeLists.txt | 2 +- tools/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eaa3a4a..c5a5e1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,7 +155,7 @@ option(CRESTAPI_BUILD_EXAMPLES if(CRESTAPI_BUILD_EXAMPLES) add_subdirectory(examples) ## add_subdirectory(acts) -## add_subdirectory(tools) + add_subdirectory(tools) endif() # Export the project. diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 694bb88..560ec53 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -19,7 +19,7 @@ option(BUILD_SHARED_LIBS # Use the standard GNU installation directories. include(GNUInstallDirs) -set(CMAKE_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/CrestResolver") +set(CMAKE_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/CrestContainer") # Turn on all warnings with GCC and Clang. if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") -- GitLab From 5a939019dd5ab5933f824e92f882a4055c8d5356 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Mon, 7 Apr 2025 13:28:40 +0200 Subject: [PATCH 19/33] deal with include dirs --- tools/CMakeLists.txt | 8 +++++++- tools/cmake/CrestContainerConfig.cmake.in | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 560ec53..640e66d 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.12) project(CrestContainer VERSION 6.1 LANGUAGES CXX) # Find the necessary externals. -find_package(CrestApi REQUIRED) +find_package(CrestApi 6.1 REQUIRED) # Set the C++ standard to use. @@ -34,6 +34,12 @@ set(HEADERS set(SOURCES "./CrestContainer.cxx" ) +# Use RPATH for build tree +set(CMAKE_SKIP_BUILD_RPATH FALSE) +# Don't use RPATH for install tree when building +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +# Add the automatically determined parts of the RPATH +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) # Set up the build of the main library of the project. add_library(CrestContainerLib ${SOURCES} ${HEADERS}) diff --git a/tools/cmake/CrestContainerConfig.cmake.in b/tools/cmake/CrestContainerConfig.cmake.in index 9a06914..c1153fe 100644 --- a/tools/cmake/CrestContainerConfig.cmake.in +++ b/tools/cmake/CrestContainerConfig.cmake.in @@ -10,6 +10,10 @@ set_and_check(CrestContainer_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@") set_and_check(CrestContainer_LIBRARY_DIR "@PACKAGE_CMAKE_INSTALL_LIBDIR@") +# Set the include directories that dependent projects will need +set(CrestContainerLib_INCLUDE_DIRS "${CrestContainer_INCLUDE_DIR}") +set(CrestContainer_INCLUDE_DIRS "${CrestContainer_INCLUDE_DIR}") # For backward compatibility + # Find dependencies include(CMakeFindDependencyMacro) find_dependency(CURL) @@ -25,5 +29,5 @@ set(CrestContainer_LIBRARIES CrestApi::CrestContainerLib) # Print a standard information message about the package being found. include(FindPackageHandleStandardArgs) find_package_handle_standard_args(CrestContainer - REQUIRED_VARS CMAKE_CURRENT_LIST_FILE + REQUIRED_VARS CMAKE_CURRENT_LIST_FILE CrestContainer_INCLUDE_DIR VERSION_VAR CrestContainer_VERSION ) -- GitLab From 047565482995fc441728294b2a0a18443a8deb13 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Mon, 7 Apr 2025 13:40:36 +0200 Subject: [PATCH 20/33] add channel method --- tools/CrestContainer.cxx | 18 ++++++++++++++++++ tools/CrestContainer.h | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/tools/CrestContainer.cxx b/tools/CrestContainer.cxx index a7de28d..56d8c7d 100644 --- a/tools/CrestContainer.cxx +++ b/tools/CrestContainer.cxx @@ -727,3 +727,21 @@ nlohmann::json Crest::CrestContainer::getStoreSetDto(uint64_t period) { return j; } + +void Crest::CrestContainer::addChannel(const std::string &channelId, + const std::string &channelName) { + // Check if the channel is already in the map + if (m_chanMap.find(channelId) == m_chanMap.end()) { + if (channelName.c_str() == nullptr) { + m_chanMap[channelId] = ""; + } else { + m_chanMap[channelId] = channelName; + } + std::cout << "CrestContainer: added to m_chanMap " << channelId << " = " + << m_chanMap[channelId] << std::endl; + return; + } + std::cout << "CrestContainer: ChannelID " << channelId + << " found in map ... don't do anything - size is " + << m_chanMap.size() << std::endl; +} diff --git a/tools/CrestContainer.h b/tools/CrestContainer.h index 793b326..5a8cc30 100644 --- a/tools/CrestContainer.h +++ b/tools/CrestContainer.h @@ -74,6 +74,10 @@ class CrestContainer { nlohmann::json m_iov_data = {}; nlohmann::json m_vector_data = nlohmann::json::array(); std::map<uint64_t, nlohmann::json> m_full_data; + // Utility map to store the channel_id and the index of the channel + std::map<std::string, std::string> m_chanMap; + std::map<std::string, std::string> m_chanIdxMap; + ModeId m_modeId; bool m_isVectorPayload = false; @@ -122,6 +126,11 @@ class CrestContainer { */ void addNullRecord(const char* name); + /** + * @brief It adds a channel to the channels map. + */ + void addChannel(const std::string& channelId, const std::string& channelName); + /** * @brief It adds a record to the payload. * @param name The name of the column. -- GitLab From 272c06a063a5cdbdc006a7193f200bf930aa8ecf Mon Sep 17 00:00:00 2001 From: Mikhail Mineev <Mikhail.Mineev@cern.ch> Date: Tue, 8 Apr 2025 16:10:41 +0200 Subject: [PATCH 21/33] errors in CrestContainer corrected --- tools/CMakeLists.txt | 4 ++-- tools/CrestContainer.cxx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 640e66d..4dcd648 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.12) project(CrestContainer VERSION 6.1 LANGUAGES CXX) # Find the necessary externals. -find_package(CrestApi 6.1 REQUIRED) +#find_package(CrestApi 6.1 REQUIRED) # Set the C++ standard to use. @@ -48,7 +48,7 @@ target_link_libraries(CrestContainerLib CURL::libcurl Boost::boost nlohmann_json::nlohmann_json - CrestApi::CrestApiLib + CrestApiLib PRIVATE OpenSSL::SSL ) diff --git a/tools/CrestContainer.cxx b/tools/CrestContainer.cxx index 56d8c7d..2db77c3 100644 --- a/tools/CrestContainer.cxx +++ b/tools/CrestContainer.cxx @@ -13,7 +13,7 @@ #include <fstream> #include <iomanip> -#include "CrestApi/CrestCondException.h" +#include <CrestApi/CrestCondException.h> using json = nlohmann::json; -- GitLab From 0f4c4d1023d2d8e421662ed63bd602cd490388a3 Mon Sep 17 00:00:00 2001 From: GitLab CI <noreply@cern.ch> Date: Wed, 9 Apr 2025 08:02:33 +0000 Subject: [PATCH 22/33] Auto-fix: Apply formatting corrections --- tools/CrestContainer.cxx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/CrestContainer.cxx b/tools/CrestContainer.cxx index 2db77c3..747c29d 100644 --- a/tools/CrestContainer.cxx +++ b/tools/CrestContainer.cxx @@ -4,6 +4,7 @@ #include "CrestContainer.h" +#include <CrestApi/CrestCondException.h> #include <stdarg.h> #include <boost/algorithm/string.hpp> @@ -13,8 +14,6 @@ #include <fstream> #include <iomanip> -#include <CrestApi/CrestCondException.h> - using json = nlohmann::json; Crest::CrestContainer::CrestContainer(Crest::ModeId mode) : m_modeId(mode) { -- GitLab From 5eb8da19d1a17bb3d0812dfce7f8207373e6f39a Mon Sep 17 00:00:00 2001 From: Mikhail Mineev <Mikhail.Mineev@cern.ch> Date: Wed, 7 May 2025 13:09:59 +0200 Subject: [PATCH 23/33] methods for streamer info added --- CrestApi/CrestApi.h | 7 +++++ CrestApi/CrestApiBase.h | 7 +++++ CrestApi/CrestApiFs.h | 15 +++++++++ examples/crest_example_fs.cxx | 29 +++++++++++++++++ examples/crest_example_server.cxx | 4 ++- src/CrestApi.cxx | 12 +++++++ src/CrestApiFs.cxx | 52 ++++++++++++++++++++++++++++++- 7 files changed, 124 insertions(+), 2 deletions(-) diff --git a/CrestApi/CrestApi.h b/CrestApi/CrestApi.h index 6446578..fd3eb9b 100644 --- a/CrestApi/CrestApi.h +++ b/CrestApi/CrestApi.h @@ -532,6 +532,13 @@ class CrestApi : public CrestApiBase { */ PayloadDto getPayloadMeta(const std::string &hash) override; + /** + * This method finds a streamer info for the hash. + * @param hash - hash. + * @return streamer info. + */ + std::string getStreamerInfo(const std::string &hash); + /** * This method returns the full CREST Server version. * @return CREST server version. diff --git a/CrestApi/CrestApiBase.h b/CrestApi/CrestApiBase.h index 488980d..935b4aa 100644 --- a/CrestApi/CrestApiBase.h +++ b/CrestApi/CrestApiBase.h @@ -468,6 +468,13 @@ class CrestApiBase { */ virtual PayloadDto getPayloadMeta(const std::string &hash) = 0; + /** + * This method finds a streamer info for the hash. + * @param hash - hash. + * @return streamer info. + */ + virtual std::string getStreamerInfo(const std::string &hash) = 0; + /** * This method returns the full CREST Server version. * @return CREST server version. diff --git a/CrestApi/CrestApiFs.h b/CrestApi/CrestApiFs.h index 8890596..50c65a7 100644 --- a/CrestApi/CrestApiFs.h +++ b/CrestApi/CrestApiFs.h @@ -97,6 +97,14 @@ class CrestApiFs : public CrestApiBase { */ void flush(); + /** + * Auxiliary method to store the streamer info on the file storage. + * @param hash - the payload hash. + * @param streamerInfo - the payload streamer info. + * + */ + void createStreamerInfo(const std::string &hash, const std::string streamerInfo); + public: /** * CrestApiFs constructor. @@ -502,6 +510,13 @@ class CrestApiFs : public CrestApiBase { */ PayloadDto getPayloadMeta(const std::string &hash) override; + /** + * This method finds a streamer info for the hash. + * @param hash - hash. + * @return streamer info. + */ + std::string getStreamerInfo(const std::string &hash); + /** * This method returns the full CREST Server version. * @return CREST server version. diff --git a/examples/crest_example_fs.cxx b/examples/crest_example_fs.cxx index 4a9d906..c37fe8c 100644 --- a/examples/crest_example_fs.cxx +++ b/examples/crest_example_fs.cxx @@ -550,6 +550,34 @@ void testGetPayload_FS(const std::string &hash) { } } +void testGetPayload_FS_2(const std::string &tagname) { + std::cout << std::endl << "test: getPayload 2" << std::endl; + CrestApiFs myCrestApi = CrestApiFs(true, "/tmp/crest"); + + try { + IovSetDto dto = + myCrestApi.selectIovs(tagname, 0, -1, 0, 1000, 0, "id.since:ASC"); + std::cout << std::endl + << "test: listIovs (result) = " << std::endl + << dto.toJson().dump(4) << std::endl; + std::vector<IovDto> iovs = dto.getResources(); + for (auto iov : iovs) { + std::cout << "iov.since = " << iov.getSince() << std::endl; + std::cout << "iov.payloadHash = " << iov.getPayloadHash() << std::endl; + std::string payload = myCrestApi.getPayload(iov.getPayloadHash()); + std::cout << "payload = " << payload.substr(0, 100) << std::endl; + PayloadDto payloadDto = myCrestApi.getPayloadMeta(iov.getPayloadHash()); + std::cout << "payloadDto = " << payloadDto.toJson().dump(4) << std::endl; + std::cout << " size = " << payloadDto.size() << std::endl; + std::string streamer = myCrestApi.getStreamerInfo(iov.getPayloadHash()); + std::cout << "streamerInfo = " << streamer << std::endl; + } + } catch (const std::exception &e) { + std::cout << std::endl << "test: getPayload 2 (failed)" << std::endl; + std::cout << e.what() << std::endl; + } +} + void testGetPayloadMeta_FS(const std::string &hash) { std::cout << std::endl << "test: getPayloadMeta FS" << std::endl; @@ -1111,6 +1139,7 @@ int main() { testFindTag_FS(tagname); testStorePayloads_FS(tagname); testGetSize_FS(tagname); + testGetPayload_FS_2(tagname); */ /* diff --git a/examples/crest_example_server.cxx b/examples/crest_example_server.cxx index 0ac1067..f0446b2 100644 --- a/examples/crest_example_server.cxx +++ b/examples/crest_example_server.cxx @@ -471,6 +471,8 @@ void testGetPayload(const std::string &tagname) { PayloadDto payloadDto = myCrestApi.getPayloadMeta(iov.getPayloadHash()); std::cout << "payloadDto = " << payloadDto.toJson().dump(4) << std::endl; std::cout << " size = " << payloadDto.size() << std::endl; + std::string streamer = myCrestApi.getStreamerInfo(iov.getPayloadHash()); + std::cout << "streamerInfo = " << streamer << std::endl; } } catch (const std::exception &e) { std::cout << std::endl << "test: listIovs (failed)" << std::endl; @@ -855,7 +857,7 @@ int main(int argc, char *argv[]) { testGetSize(tagname); testRemoveTag(tagname); - testFindExistingTagMeta("SCTDAQConfigChipSlim-HEAD"); + // testFindExistingTagMeta("SCTDAQConfigChipSlim-HEAD"); // testGetVersion(); diff --git a/src/CrestApi.cxx b/src/CrestApi.cxx index 89460a0..2768c8e 100644 --- a/src/CrestApi.cxx +++ b/src/CrestApi.cxx @@ -776,6 +776,18 @@ void CrestApi::checkHash(const std::string &hash, const std::string &str, return; } +std::string CrestApi::getStreamerInfo(const std::string &hash) { + std::string current_path = m_PATH; + current_path += urlPaths[UrlPathIndex::PAYLOADS]; + current_path += urlPaths[UrlPathIndex::DATA]; + current_path += "?format=STREAMER&hash="; + current_path += hash; + std::string retv; + nlohmann::json js = nullptr; + retv = m_request.performRequest(current_path, Action::GET, js); + return retv; +} + RunLumiSetDto CrestApi::listRunInfo(const std::string &since, const std::string &until, const std::string format, diff --git a/src/CrestApiFs.cxx b/src/CrestApiFs.cxx index 3ff2d3c..85c8640 100644 --- a/src/CrestApiFs.cxx +++ b/src/CrestApiFs.cxx @@ -737,7 +737,6 @@ void CrestApiFs::storePayloadDump(const std::string &tag, uint64_t since, {"objectType", objectType}, {"version", version}, {"size", js.size()}, - {"streamerInfo", streamerInfo}, {"compressionType", compressionType}, {"insertionTime", getDateAndTime()}}; @@ -748,6 +747,9 @@ void CrestApiFs::storePayloadDump(const std::string &tag, uint64_t since, outFile << jsn.dump(); outFile.close(); + // streamer info: + createStreamerInfo(hashCode,streamerInfo); + // check if data exists if (m_data.find(tag) == m_data.end()) { @@ -844,6 +846,54 @@ PayloadDto CrestApiFs::getPayloadMeta(const std::string &hash) { return dto; } +std::string CrestApiFs::getStreamerInfo(const std::string &hash) { + std::string workDir = m_data_folder; + workDir += '/'; + workDir += getFirstLetters(hash); + workDir += '/'; + workDir += hash; + std::string filePath = workDir + "/streamer.json"; + std::string res = ""; + + try { + if (std::filesystem::exists(filePath)) { + res = getFileString(filePath); + } else { + throw CrestException("streamer info with hash " + hash + " does not exist."); + } + } catch (const std::exception &e) { + std::string message = e.what(); + throw CrestException( + "ERROR in CrestApiFs::getStreamerInfo cannot get the " + "streamer info form file storage, " + + message); + } + + return res; +} + +void CrestApiFs::createStreamerInfo(const std::string &hash, const std::string streamerInfo) { + std::string workDir = m_data_folder; + workDir += '/'; + workDir += getFirstLetters(hash); + workDir += '/'; + workDir += hash; + std::string filePath = workDir + "/streamer.json"; + + if (m_isRewrite) { + if (std::filesystem::exists(std::filesystem::path(filePath))) { + std::filesystem::remove(std::filesystem::path(filePath)); + } + + std::ofstream outFile; + + outFile.open(filePath.c_str()); + outFile << streamerInfo; + outFile.close(); + } +} + // + nlohmann::json CrestApiFs::getPage(nlohmann::json data, int size, int page) { nlohmann::json js = nlohmann::json::array(); int dataSize = data.size(); -- GitLab From dc41e02005c744c35767cc910f7e0d26dc58ce8e Mon Sep 17 00:00:00 2001 From: mavogel <mavogel@cern.ch> Date: Mon, 12 May 2025 15:45:14 +0200 Subject: [PATCH 24/33] Refactored into new CrestAuth class --- CMakeLists.txt | 2 + CrestApi/CrestAuth.h | 46 ++++++++++ CrestApi/CrestRequest.h | 3 + src/CrestAuth.cxx | 186 ++++++++++++++++++++++++++++++++++++++++ src/CrestRequest.cxx | 13 +++ 5 files changed, 250 insertions(+) create mode 100644 CrestApi/CrestAuth.h create mode 100644 src/CrestAuth.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index c5a5e1d..8473ff0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ set(HEADERS "CrestApi/CrestApiFs.h" "CrestApi/CrestLogger.h" "CrestApi/CrestRequest.h" + "CrestApi/CrestAuth.h" "CrestApi/CrestApiExt.h" "CrestApi/CrestException.h" "CrestApi/CrestCondException.h" @@ -78,6 +79,7 @@ set(HEADERS set(SOURCES "src/CrestApi.cxx" "src/CrestRequest.cxx" + "src/CrestAuth.cxx" "src/CrestLogger.cxx" "src/StringUtils.cxx" "src/CrestApiFs.cxx" diff --git a/CrestApi/CrestAuth.h b/CrestApi/CrestAuth.h new file mode 100644 index 0000000..ef40c13 --- /dev/null +++ b/CrestApi/CrestAuth.h @@ -0,0 +1,46 @@ +// CrestAuth.h +#ifndef CRESTAPI_AUTH_H +#define CRESTAPI_AUTH_H + +#include <string> +#include <ctime> +#include <nlohmann/json.hpp> +#include <curl/curl.h> + +namespace Crest { + +class CrestAuth { + private: + std::string m_tokenFilePath; + std::string m_tokenEndpoint; + std::string m_clientId; + std::string m_audience; + + std::string m_exchangeToken; + std::string m_refreshToken; + std::string m_finalAccessToken; + + std::time_t m_exchangeTokenExpiresAt; + std::time_t m_finalAccessExpiresAt; + + bool saveTokens() const; + std::string postRequest(const std::string &url, const std::string &postFields) const; + + static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp); + + public: + CrestAuth(); + + bool loadTokens(); + bool refreshExchangeToken(); + bool exchangeTokenForAccess(); + + bool isExchangeTokenExpired() const; + bool hasValidAccessToken() const; + std::string getAccessToken(); +}; + +} // namespace Crest + +#endif // CRESTAPI_AUTH_H + diff --git a/CrestApi/CrestRequest.h b/CrestApi/CrestRequest.h index a6cbd67..2e77d19 100644 --- a/CrestApi/CrestRequest.h +++ b/CrestApi/CrestRequest.h @@ -13,6 +13,7 @@ #include <nlohmann/json.hpp> #include <source_location> #include <string> +#include <CrestApi/CrestAuth.h> namespace Crest { @@ -49,6 +50,8 @@ class CrestRequest { std::string m_port; std::string m_url; + CrestAuth m_auth; + char *m_CREST_PROXY = NULL; const char *m_CREST_PROXY_VAR = "SOCKS_PROXY"; diff --git a/src/CrestAuth.cxx b/src/CrestAuth.cxx new file mode 100644 index 0000000..abbb90c --- /dev/null +++ b/src/CrestAuth.cxx @@ -0,0 +1,186 @@ +// CrestAuth.cxx +#include <CrestApi/CrestAuth.h> + +#include <fstream> +#include <iostream> +#include <sstream> + +namespace Crest { + +CrestAuth::CrestAuth() + : m_tokenFilePath(std::getenv("HOME") + std::string("/.client-exchange-token.json")), + m_tokenEndpoint("https://auth.cern.ch/auth/realms/cern/protocol/openid-connect/token"), + m_clientId("crest-client"), + m_audience("crest-server"), + m_exchangeTokenExpiresAt(0), + m_finalAccessExpiresAt(0) {} + +bool CrestAuth::loadTokens() { + std::ifstream file(m_tokenFilePath); + if (!file.is_open()) { + std::cerr << "[CrestAuth] Failed to open token file: " << m_tokenFilePath << "\n"; + return false; + } + + std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); + if (content.empty()) { + std::cerr << "[CrestAuth] Token file is empty\n"; + return false; + } + + file.clear(); + file.seekg(0); + + nlohmann::json js; + try { + file >> js; + } catch (const std::exception& e) { + std::cerr << "[CrestAuth] Failed to parse token JSON: " << e.what() << "\n"; + return false; + } + + m_exchangeToken = js.value("access_token", ""); + m_refreshToken = js.value("refresh_token", ""); + int expires_in = js.value("expires_in", 300); + m_exchangeTokenExpiresAt = std::time(nullptr) + expires_in; + + std::cerr << "[CrestAuth] Loaded exchange and refresh tokens from file.\n"; + return true; +} + +bool CrestAuth::saveTokens() const { + nlohmann::json js; + js["access_token"] = m_exchangeToken; + js["refresh_token"] = m_refreshToken; + js["expires_in"] = static_cast<int>(m_exchangeTokenExpiresAt - std::time(nullptr)); + + std::ofstream file(m_tokenFilePath); + if (!file.is_open()) { + std::cerr << "[CrestAuth] Failed to open token file for writing: " << m_tokenFilePath << "\n"; + return false; + } + + file << js.dump(2); + std::cerr << "[CrestAuth] Saved tokens to file.\n"; + return true; +} + +bool CrestAuth::isExchangeTokenExpired() const { + return std::time(nullptr) >= m_exchangeTokenExpiresAt; +} + +bool CrestAuth::hasValidAccessToken() const { + return !m_finalAccessToken.empty() && std::time(nullptr) < m_finalAccessExpiresAt; +} + +std::string CrestAuth::getAccessToken() { + if (hasValidAccessToken()) { + std::cerr << "[CrestAuth] Returning in-memory access token.\n"; + return m_finalAccessToken; + } + + std::cerr << "[CrestAuth] In-memory access token is missing or expired. Attempting exchange.\n"; + if (exchangeTokenForAccess()) { + return m_finalAccessToken; + } + + std::cerr << "[CrestAuth] Failed to obtain access token.\n"; + return ""; +} + +bool CrestAuth::refreshExchangeToken() { + std::ostringstream oss; + oss << "grant_type=refresh_token" + << "&refresh_token=" << m_refreshToken + << "&client_id=" << m_clientId; + + std::string response = postRequest(m_tokenEndpoint, oss.str()); + if (response.empty()) return false; + + auto js = nlohmann::json::parse(response, nullptr, false); + if (js.is_discarded() || js.contains("error")) { + std::cerr << "[CrestAuth] Failed to refresh exchange token: " << js.dump(2) << "\n"; + return false; + } + + m_exchangeToken = js["access_token"]; + m_refreshToken = js.value("refresh_token", m_refreshToken); + m_exchangeTokenExpiresAt = std::time(nullptr) + js.value("expires_in", 300); + + std::cerr << "[CrestAuth] Successfully refreshed exchange token.\n"; + return saveTokens(); +} + +bool CrestAuth::exchangeTokenForAccess() { + CURL* curl = curl_easy_init(); + if (!curl) { + std::cerr << "[CrestAuth] Failed to initialize CURL.\n"; + return false; + } + + if (m_exchangeToken.empty()) { + std::cerr << "[CrestAuth] Exchange token is empty.\n"; + curl_easy_cleanup(curl); + return false; + } + + std::ostringstream oss; + oss << "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" + << "&subject_token=" << m_exchangeToken + << "&subject_token_type=urn:ietf:params:oauth:token-type:access_token" + << "&client_id=" << m_clientId + << "&audience=" << m_audience; + + std::string response = postRequest(m_tokenEndpoint, oss.str()); + curl_easy_cleanup(curl); + + if (response.empty()) return false; + + auto js = nlohmann::json::parse(response, nullptr, false); + if (js.is_discarded() || !js.contains("access_token")) { + std::cerr << "[CrestAuth] Token exchange failed: " << js.dump(2) << "\n"; + return false; + } + + m_finalAccessToken = js["access_token"]; + m_finalAccessExpiresAt = std::time(nullptr) + js.value("expires_in", 300); + + std::cerr << "[CrestAuth] Exchanged token successfully. Access token expires in " + << (m_finalAccessExpiresAt - std::time(nullptr)) << " seconds.\n"; + return true; +} + +std::string CrestAuth::postRequest(const std::string &url, const std::string &postFields) const { + CURL* curl = curl_easy_init(); + if (!curl) return ""; + + std::string response; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postFields.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + + struct curl_slist* headers = nullptr; + headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + CURLcode res = curl_easy_perform(curl); + if (res != CURLE_OK) { + std::cerr << "[CrestAuth] curl_easy_perform failed: " << curl_easy_strerror(res) << "\n"; + } + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + return response; +} + +size_t CrestAuth::WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) { + size_t totalSize = size * nmemb; + std::string* out = static_cast<std::string*>(userp); + out->append(static_cast<char*>(contents), totalSize); + return totalSize; +} + +} // namespace Crest + diff --git a/src/CrestRequest.cxx b/src/CrestRequest.cxx index 9d8e9ce..e3981c8 100644 --- a/src/CrestRequest.cxx +++ b/src/CrestRequest.cxx @@ -42,6 +42,11 @@ std::ostream &operator<<(std::ostream &os, Action action) { CrestRequest::CrestRequest() { curl_global_init(CURL_GLOBAL_ALL); m_CREST_PROXY = std::getenv(m_CREST_PROXY_VAR); + if (!m_auth.loadTokens() || m_auth.isExchangeTokenExpired()) { + if (!m_auth.refreshExchangeToken()) { + throw std::runtime_error("[CrestRequest] Failed to refresh tokens on init."); + } + } } CrestRequest::~CrestRequest() { @@ -125,6 +130,14 @@ std::string CrestRequest::performRequest(const std::string ¤t_path, curl = curl_easy_init(); std::string stt; struct curl_slist *headers = NULL; + std::string token = m_auth.getAccessToken(); + if (token.empty()) { + throw std::runtime_error("[CrestRequest] Access token is empty."); + } + + char authHeader[512]; + snprintf(authHeader, sizeof(authHeader), "Authorization: Bearer %s", token.c_str()); + headers = curl_slist_append(headers, authHeader); if (curl) { std::string url = Crest::StringUtils::appendEndpoint(m_url, current_path); std::string s; -- GitLab From 15c716fc097345cbce68e223f39390aa8df3e759 Mon Sep 17 00:00:00 2001 From: mavogel <mavogel@cern.ch> Date: Tue, 13 May 2025 22:59:09 +0200 Subject: [PATCH 25/33] Added authorization layer --- CMakeLists.txt | 2 ++ CrestApi/CrestAuth.h | 6 ++-- CrestApi/JwtUtils.h | 19 ++++++++++++ src/CrestAuth.cxx | 61 +++++++++++++++------------------------ src/CrestRequest.cxx | 33 ++++++++++++++++----- src/JwtUtils.cxx | 69 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 142 insertions(+), 48 deletions(-) create mode 100644 CrestApi/JwtUtils.h create mode 100644 src/JwtUtils.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 8473ff0..cde5800 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ set(HEADERS "CrestApi/CrestLogger.h" "CrestApi/CrestRequest.h" "CrestApi/CrestAuth.h" + "CrestApi/JwtUtils.h" "CrestApi/CrestApiExt.h" "CrestApi/CrestException.h" "CrestApi/CrestCondException.h" @@ -80,6 +81,7 @@ set(SOURCES "src/CrestApi.cxx" "src/CrestRequest.cxx" "src/CrestAuth.cxx" + "src/JwtUtils.cxx" "src/CrestLogger.cxx" "src/StringUtils.cxx" "src/CrestApiFs.cxx" diff --git a/CrestApi/CrestAuth.h b/CrestApi/CrestAuth.h index ef40c13..0b52006 100644 --- a/CrestApi/CrestAuth.h +++ b/CrestApi/CrestAuth.h @@ -21,9 +21,8 @@ class CrestAuth { std::string m_finalAccessToken; std::time_t m_exchangeTokenExpiresAt; - std::time_t m_finalAccessExpiresAt; - bool saveTokens() const; + bool saveTokens(const nlohmann::json& tokenJson) const; std::string postRequest(const std::string &url, const std::string &postFields) const; static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp); @@ -36,8 +35,7 @@ class CrestAuth { bool exchangeTokenForAccess(); bool isExchangeTokenExpired() const; - bool hasValidAccessToken() const; - std::string getAccessToken(); + std::string getAccessToken() const; }; } // namespace Crest diff --git a/CrestApi/JwtUtils.h b/CrestApi/JwtUtils.h new file mode 100644 index 0000000..a49ce9e --- /dev/null +++ b/CrestApi/JwtUtils.h @@ -0,0 +1,19 @@ +#ifndef JWT_UTILS_H +#define JWT_UTILS_H + +#include <string> +#include <ctime> + +namespace Crest { +class JwtUtils { +public: + // Extracts the `exp` (expiration timestamp) from a JWT token + static std::time_t extractExp(const std::string& jwt, int graceSeconds = 0); + +private: + static std::string base64urlDecode(const std::string& input); +}; +} // namespace Crest + +#endif // JWT_UTILS_H + diff --git a/src/CrestAuth.cxx b/src/CrestAuth.cxx index abbb90c..804865c 100644 --- a/src/CrestAuth.cxx +++ b/src/CrestAuth.cxx @@ -1,6 +1,6 @@ // CrestAuth.cxx #include <CrestApi/CrestAuth.h> - +#include <CrestApi/JwtUtils.h> #include <fstream> #include <iostream> #include <sstream> @@ -12,8 +12,7 @@ CrestAuth::CrestAuth() m_tokenEndpoint("https://auth.cern.ch/auth/realms/cern/protocol/openid-connect/token"), m_clientId("crest-client"), m_audience("crest-server"), - m_exchangeTokenExpiresAt(0), - m_finalAccessExpiresAt(0) {} + m_exchangeTokenExpiresAt(0) {} bool CrestAuth::loadTokens() { std::ifstream file(m_tokenFilePath); @@ -41,27 +40,26 @@ bool CrestAuth::loadTokens() { m_exchangeToken = js.value("access_token", ""); m_refreshToken = js.value("refresh_token", ""); - int expires_in = js.value("expires_in", 300); - m_exchangeTokenExpiresAt = std::time(nullptr) + expires_in; + try { + m_exchangeTokenExpiresAt = JwtUtils::extractExp(m_exchangeToken, 30); + } catch (const std::exception& e) { + std::cerr << "[CrestAuth] Failed to extract 'exp' from JWT: " << e.what() << "\n"; + m_exchangeTokenExpiresAt = std::time(nullptr) + 300; // fallback + } std::cerr << "[CrestAuth] Loaded exchange and refresh tokens from file.\n"; return true; } -bool CrestAuth::saveTokens() const { - nlohmann::json js; - js["access_token"] = m_exchangeToken; - js["refresh_token"] = m_refreshToken; - js["expires_in"] = static_cast<int>(m_exchangeTokenExpiresAt - std::time(nullptr)); - +bool CrestAuth::saveTokens(const nlohmann::json& tokenJson) const { std::ofstream file(m_tokenFilePath); if (!file.is_open()) { std::cerr << "[CrestAuth] Failed to open token file for writing: " << m_tokenFilePath << "\n"; return false; } - file << js.dump(2); - std::cerr << "[CrestAuth] Saved tokens to file.\n"; + file << tokenJson.dump(); + std::cerr << "[CrestAuth] Saved exchanged token to file.\n"; return true; } @@ -69,23 +67,8 @@ bool CrestAuth::isExchangeTokenExpired() const { return std::time(nullptr) >= m_exchangeTokenExpiresAt; } -bool CrestAuth::hasValidAccessToken() const { - return !m_finalAccessToken.empty() && std::time(nullptr) < m_finalAccessExpiresAt; -} - -std::string CrestAuth::getAccessToken() { - if (hasValidAccessToken()) { - std::cerr << "[CrestAuth] Returning in-memory access token.\n"; - return m_finalAccessToken; - } - - std::cerr << "[CrestAuth] In-memory access token is missing or expired. Attempting exchange.\n"; - if (exchangeTokenForAccess()) { - return m_finalAccessToken; - } - - std::cerr << "[CrestAuth] Failed to obtain access token.\n"; - return ""; +std::string CrestAuth::getAccessToken() const { + return m_finalAccessToken; } bool CrestAuth::refreshExchangeToken() { @@ -98,6 +81,7 @@ bool CrestAuth::refreshExchangeToken() { if (response.empty()) return false; auto js = nlohmann::json::parse(response, nullptr, false); + //std::cerr << "[CrestAuth] Response to refresh exchange token: " << js.dump() << "\n"; if (js.is_discarded() || js.contains("error")) { std::cerr << "[CrestAuth] Failed to refresh exchange token: " << js.dump(2) << "\n"; return false; @@ -105,10 +89,14 @@ bool CrestAuth::refreshExchangeToken() { m_exchangeToken = js["access_token"]; m_refreshToken = js.value("refresh_token", m_refreshToken); - m_exchangeTokenExpiresAt = std::time(nullptr) + js.value("expires_in", 300); - + try { + m_exchangeTokenExpiresAt = JwtUtils::extractExp(m_exchangeToken, 30); + } catch (const std::exception& e) { + std::cerr << "[CrestAuth] Failed to extract 'exp' from JWT: " << e.what() << "\n"; + m_exchangeTokenExpiresAt = std::time(nullptr) + 300; // fallback + } std::cerr << "[CrestAuth] Successfully refreshed exchange token.\n"; - return saveTokens(); + return saveTokens(js); } bool CrestAuth::exchangeTokenForAccess() { @@ -143,10 +131,9 @@ bool CrestAuth::exchangeTokenForAccess() { } m_finalAccessToken = js["access_token"]; - m_finalAccessExpiresAt = std::time(nullptr) + js.value("expires_in", 300); - std::cerr << "[CrestAuth] Exchanged token successfully. Access token expires in " - << (m_finalAccessExpiresAt - std::time(nullptr)) << " seconds.\n"; + std::cerr << "[CrestAuth] Token exchanged successfully.\n"; + return true; } @@ -163,7 +150,7 @@ std::string CrestAuth::postRequest(const std::string &url, const std::string &po struct curl_slist* headers = nullptr; headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { diff --git a/src/CrestRequest.cxx b/src/CrestRequest.cxx index e3981c8..84d2d6a 100644 --- a/src/CrestRequest.cxx +++ b/src/CrestRequest.cxx @@ -42,11 +42,31 @@ std::ostream &operator<<(std::ostream &os, Action action) { CrestRequest::CrestRequest() { curl_global_init(CURL_GLOBAL_ALL); m_CREST_PROXY = std::getenv(m_CREST_PROXY_VAR); - if (!m_auth.loadTokens() || m_auth.isExchangeTokenExpired()) { + + std::cerr << "[DEBUG] Setting up authorization...\n"; + //std::cerr << "[DEBUG] Calling loadTokens()...\n"; + bool tokensLoaded = m_auth.loadTokens(); + //std::cerr << "[DEBUG] loadTokens() returned: " << tokensLoaded << "\n"; + + if (!tokensLoaded) { + throw std::runtime_error( + "[CrestRequest] No valid token file found.\n" + "Please generate a new exchange token using the authentication utility " + "(auth-get-user-token -c crest-client -o ~/.client-exchange-token.json) and try again."); + } + + std::cerr << "[DEBUG] Checking if exchange token is expired...\n"; + if (m_auth.isExchangeTokenExpired()) { + std::cerr << "[DEBUG] Token is expired. Attempting to refresh it...\n"; if (!m_auth.refreshExchangeToken()) { - throw std::runtime_error("[CrestRequest] Failed to refresh tokens on init."); + throw std::runtime_error( + "[CrestRequest] Exchange token is expired and refresh failed.\n" + "Please generate a new token manually."); } } + if (!m_auth.exchangeTokenForAccess()) { + throw std::runtime_error("[CrestRequest] Failed to obtain access token from exchange."); + } } CrestRequest::~CrestRequest() { @@ -131,13 +151,12 @@ std::string CrestRequest::performRequest(const std::string ¤t_path, std::string stt; struct curl_slist *headers = NULL; std::string token = m_auth.getAccessToken(); + if (token.empty()) { throw std::runtime_error("[CrestRequest] Access token is empty."); } - - char authHeader[512]; - snprintf(authHeader, sizeof(authHeader), "Authorization: Bearer %s", token.c_str()); - headers = curl_slist_append(headers, authHeader); + std::string authHeader = "Authorization: Bearer " + token; + headers = curl_slist_append(headers, authHeader.c_str()); if (curl) { std::string url = Crest::StringUtils::appendEndpoint(m_url, current_path); std::string s; @@ -160,7 +179,7 @@ std::string CrestRequest::performRequest(const std::string ¤t_path, // -k analogue: curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); - + //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); if (js.is_null()) { if (action == Action::DELETE) curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); diff --git a/src/JwtUtils.cxx b/src/JwtUtils.cxx new file mode 100644 index 0000000..3f6b27d --- /dev/null +++ b/src/JwtUtils.cxx @@ -0,0 +1,69 @@ +#include <CrestApi/JwtUtils.h> +#include <nlohmann/json.hpp> +#include <stdexcept> +#include <sstream> +#include <algorithm> + +namespace Crest { + +std::string JwtUtils::base64urlDecode(const std::string& input) { + std::string base64 = input; + std::replace(base64.begin(), base64.end(), '-', '+'); + std::replace(base64.begin(), base64.end(), '_', '/'); + while (base64.length() % 4 != 0) base64 += '='; + + static constexpr unsigned char kDecodeTable[256] = { + 64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64, + 64,64,64,64,64,64,64,64,64,64,64,62,64,64,64,63,52,53,54,55,56,57,58,59,60,61,64,64,64, 0,64,64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,64,64,64,64,64, + 64,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,64,64,64,64,64, + 64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64, + 64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64, + 64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64, + 64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64 + }; + + std::string output; + size_t i = 0; + while (i < base64.length()) { + uint32_t val = 0; + int valb = -8; + for (int j = 0; j < 4 && i < base64.length(); ++j) { + unsigned char c = base64[i++]; + if (kDecodeTable[c] == 64) throw std::runtime_error("Invalid base64url character"); + val = (val << 6) + kDecodeTable[c]; + valb += 6; + } + while (valb >= 0) { + output.push_back(static_cast<char>((val >> valb) & 0xFF)); + valb -= 8; + } + } + return output; +} + +std::time_t JwtUtils::extractExp(const std::string& jwt, int graceSeconds) { + size_t dot1 = jwt.find('.'); + size_t dot2 = jwt.find('.', dot1 + 1); + if (dot1 == std::string::npos || dot2 == std::string::npos) { + throw std::runtime_error("Invalid JWT format"); + } + + std::string payloadEncoded = jwt.substr(dot1 + 1, dot2 - dot1 - 1); + std::string payloadJson = base64urlDecode(payloadEncoded); + + auto js = nlohmann::json::parse(payloadJson, nullptr, false); + if (js.is_discarded()) { + throw std::runtime_error("Failed to parse JWT payload"); + } + + if (!js.contains("exp") || !js["exp"].is_number()) { + throw std::runtime_error("JWT payload does not contain valid 'exp'"); + } + + std::time_t rawExp = js["exp"]; + return rawExp - graceSeconds; +} + +} // namespace Crest + -- GitLab From a4ade5a9e6df3f80493f0dd3668813a4e8a7b660 Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Wed, 14 May 2025 11:23:33 +0200 Subject: [PATCH 26/33] clean up --- CrestApi/CrestApi.h | 2 +- CrestApi/CrestApiFs.h | 11 +++-- CrestApi/CrestAuth.h | 12 +++-- CrestApi/CrestRequest.h | 2 +- CrestApi/JwtUtils.h | 13 +++-- src/CrestApiFs.cxx | 12 +++-- src/CrestAuth.cxx | 67 +++++++++++++++---------- src/CrestRequest.cxx | 12 +++-- src/JwtUtils.cxx | 106 +++++++++++++++++++++------------------- 9 files changed, 133 insertions(+), 104 deletions(-) diff --git a/CrestApi/CrestApi.h b/CrestApi/CrestApi.h index fd3eb9b..4ec654d 100644 --- a/CrestApi/CrestApi.h +++ b/CrestApi/CrestApi.h @@ -537,7 +537,7 @@ class CrestApi : public CrestApiBase { * @param hash - hash. * @return streamer info. */ - std::string getStreamerInfo(const std::string &hash); + std::string getStreamerInfo(const std::string &hash) override; /** * This method returns the full CREST Server version. diff --git a/CrestApi/CrestApiFs.h b/CrestApi/CrestApiFs.h index 50c65a7..62c7e9c 100644 --- a/CrestApi/CrestApiFs.h +++ b/CrestApi/CrestApiFs.h @@ -97,14 +97,15 @@ class CrestApiFs : public CrestApiBase { */ void flush(); - /** + /** * Auxiliary method to store the streamer info on the file storage. * @param hash - the payload hash. * @param streamerInfo - the payload streamer info. * - */ - void createStreamerInfo(const std::string &hash, const std::string streamerInfo); - + */ + void createStreamerInfo(const std::string &hash, + const std::string streamerInfo); + public: /** * CrestApiFs constructor. @@ -515,7 +516,7 @@ class CrestApiFs : public CrestApiBase { * @param hash - hash. * @return streamer info. */ - std::string getStreamerInfo(const std::string &hash); + std::string getStreamerInfo(const std::string &hash) override; /** * This method returns the full CREST Server version. diff --git a/CrestApi/CrestAuth.h b/CrestApi/CrestAuth.h index 0b52006..b03c3c6 100644 --- a/CrestApi/CrestAuth.h +++ b/CrestApi/CrestAuth.h @@ -2,10 +2,11 @@ #ifndef CRESTAPI_AUTH_H #define CRESTAPI_AUTH_H -#include <string> +#include <curl/curl.h> + #include <ctime> #include <nlohmann/json.hpp> -#include <curl/curl.h> +#include <string> namespace Crest { @@ -23,9 +24,11 @@ class CrestAuth { std::time_t m_exchangeTokenExpiresAt; bool saveTokens(const nlohmann::json& tokenJson) const; - std::string postRequest(const std::string &url, const std::string &postFields) const; + std::string postRequest(const std::string& url, + const std::string& postFields) const; - static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp); + static size_t WriteCallback(void* contents, size_t size, size_t nmemb, + void* userp); public: CrestAuth(); @@ -41,4 +44,3 @@ class CrestAuth { } // namespace Crest #endif // CRESTAPI_AUTH_H - diff --git a/CrestApi/CrestRequest.h b/CrestApi/CrestRequest.h index 2e77d19..e2c32c3 100644 --- a/CrestApi/CrestRequest.h +++ b/CrestApi/CrestRequest.h @@ -5,6 +5,7 @@ #ifndef CRESTAPI_REQUEST_H #define CRESTAPI_REQUEST_H +#include <CrestApi/CrestAuth.h> #include <curl/curl.h> #include <cstdint> @@ -13,7 +14,6 @@ #include <nlohmann/json.hpp> #include <source_location> #include <string> -#include <CrestApi/CrestAuth.h> namespace Crest { diff --git a/CrestApi/JwtUtils.h b/CrestApi/JwtUtils.h index a49ce9e..ccd2d5d 100644 --- a/CrestApi/JwtUtils.h +++ b/CrestApi/JwtUtils.h @@ -1,19 +1,18 @@ #ifndef JWT_UTILS_H #define JWT_UTILS_H -#include <string> #include <ctime> +#include <string> namespace Crest { class JwtUtils { -public: - // Extracts the `exp` (expiration timestamp) from a JWT token - static std::time_t extractExp(const std::string& jwt, int graceSeconds = 0); + public: + // Extracts the `exp` (expiration timestamp) from a JWT token + static std::time_t extractExp(const std::string& jwt, int graceSeconds = 0); -private: - static std::string base64urlDecode(const std::string& input); + private: + static std::string base64urlDecode(const std::string& input); }; } // namespace Crest #endif // JWT_UTILS_H - diff --git a/src/CrestApiFs.cxx b/src/CrestApiFs.cxx index 85c8640..ba0922c 100644 --- a/src/CrestApiFs.cxx +++ b/src/CrestApiFs.cxx @@ -748,7 +748,7 @@ void CrestApiFs::storePayloadDump(const std::string &tag, uint64_t since, outFile.close(); // streamer info: - createStreamerInfo(hashCode,streamerInfo); + createStreamerInfo(hashCode, streamerInfo); // check if data exists @@ -859,7 +859,8 @@ std::string CrestApiFs::getStreamerInfo(const std::string &hash) { if (std::filesystem::exists(filePath)) { res = getFileString(filePath); } else { - throw CrestException("streamer info with hash " + hash + " does not exist."); + throw CrestException("streamer info with hash " + hash + + " does not exist."); } } catch (const std::exception &e) { std::string message = e.what(); @@ -871,8 +872,9 @@ std::string CrestApiFs::getStreamerInfo(const std::string &hash) { return res; } - -void CrestApiFs::createStreamerInfo(const std::string &hash, const std::string streamerInfo) { + +void CrestApiFs::createStreamerInfo(const std::string &hash, + const std::string streamerInfo) { std::string workDir = m_data_folder; workDir += '/'; workDir += getFirstLetters(hash); @@ -892,7 +894,7 @@ void CrestApiFs::createStreamerInfo(const std::string &hash, const std::string s outFile.close(); } } - // +// nlohmann::json CrestApiFs::getPage(nlohmann::json data, int size, int page) { nlohmann::json js = nlohmann::json::array(); diff --git a/src/CrestAuth.cxx b/src/CrestAuth.cxx index 804865c..4cbb60d 100644 --- a/src/CrestAuth.cxx +++ b/src/CrestAuth.cxx @@ -1,6 +1,7 @@ // CrestAuth.cxx #include <CrestApi/CrestAuth.h> #include <CrestApi/JwtUtils.h> + #include <fstream> #include <iostream> #include <sstream> @@ -8,8 +9,11 @@ namespace Crest { CrestAuth::CrestAuth() - : m_tokenFilePath(std::getenv("HOME") + std::string("/.client-exchange-token.json")), - m_tokenEndpoint("https://auth.cern.ch/auth/realms/cern/protocol/openid-connect/token"), + : m_tokenFilePath(std::getenv("HOME") + + std::string("/.client-exchange-token.json")), + m_tokenEndpoint( + "https://auth.cern.ch/auth/realms/cern/protocol/openid-connect/" + "token"), m_clientId("crest-client"), m_audience("crest-server"), m_exchangeTokenExpiresAt(0) {} @@ -17,11 +21,13 @@ CrestAuth::CrestAuth() bool CrestAuth::loadTokens() { std::ifstream file(m_tokenFilePath); if (!file.is_open()) { - std::cerr << "[CrestAuth] Failed to open token file: " << m_tokenFilePath << "\n"; + std::cerr << "[CrestAuth] Failed to open token file: " << m_tokenFilePath + << "\n"; return false; } - std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); + std::string content((std::istreambuf_iterator<char>(file)), + std::istreambuf_iterator<char>()); if (content.empty()) { std::cerr << "[CrestAuth] Token file is empty\n"; return false; @@ -41,10 +47,11 @@ bool CrestAuth::loadTokens() { m_exchangeToken = js.value("access_token", ""); m_refreshToken = js.value("refresh_token", ""); try { - m_exchangeTokenExpiresAt = JwtUtils::extractExp(m_exchangeToken, 30); + m_exchangeTokenExpiresAt = JwtUtils::extractExp(m_exchangeToken, 30); } catch (const std::exception& e) { - std::cerr << "[CrestAuth] Failed to extract 'exp' from JWT: " << e.what() << "\n"; - m_exchangeTokenExpiresAt = std::time(nullptr) + 300; // fallback + std::cerr << "[CrestAuth] Failed to extract 'exp' from JWT: " << e.what() + << "\n"; + m_exchangeTokenExpiresAt = std::time(nullptr) + 300; // fallback } std::cerr << "[CrestAuth] Loaded exchange and refresh tokens from file.\n"; @@ -54,7 +61,8 @@ bool CrestAuth::loadTokens() { bool CrestAuth::saveTokens(const nlohmann::json& tokenJson) const { std::ofstream file(m_tokenFilePath); if (!file.is_open()) { - std::cerr << "[CrestAuth] Failed to open token file for writing: " << m_tokenFilePath << "\n"; + std::cerr << "[CrestAuth] Failed to open token file for writing: " + << m_tokenFilePath << "\n"; return false; } @@ -74,26 +82,29 @@ std::string CrestAuth::getAccessToken() const { bool CrestAuth::refreshExchangeToken() { std::ostringstream oss; oss << "grant_type=refresh_token" - << "&refresh_token=" << m_refreshToken - << "&client_id=" << m_clientId; + << "&refresh_token=" << m_refreshToken << "&client_id=" << m_clientId; std::string response = postRequest(m_tokenEndpoint, oss.str()); - if (response.empty()) return false; + if (response.empty()) + return false; auto js = nlohmann::json::parse(response, nullptr, false); - //std::cerr << "[CrestAuth] Response to refresh exchange token: " << js.dump() << "\n"; + // std::cerr << "[CrestAuth] Response to refresh exchange token: " << + // js.dump() << "\n"; if (js.is_discarded() || js.contains("error")) { - std::cerr << "[CrestAuth] Failed to refresh exchange token: " << js.dump(2) << "\n"; + std::cerr << "[CrestAuth] Failed to refresh exchange token: " << js.dump(2) + << "\n"; return false; } m_exchangeToken = js["access_token"]; m_refreshToken = js.value("refresh_token", m_refreshToken); try { - m_exchangeTokenExpiresAt = JwtUtils::extractExp(m_exchangeToken, 30); + m_exchangeTokenExpiresAt = JwtUtils::extractExp(m_exchangeToken, 30); } catch (const std::exception& e) { - std::cerr << "[CrestAuth] Failed to extract 'exp' from JWT: " << e.what() << "\n"; - m_exchangeTokenExpiresAt = std::time(nullptr) + 300; // fallback + std::cerr << "[CrestAuth] Failed to extract 'exp' from JWT: " << e.what() + << "\n"; + m_exchangeTokenExpiresAt = std::time(nullptr) + 300; // fallback } std::cerr << "[CrestAuth] Successfully refreshed exchange token.\n"; return saveTokens(js); @@ -116,13 +127,13 @@ bool CrestAuth::exchangeTokenForAccess() { oss << "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" << "&subject_token=" << m_exchangeToken << "&subject_token_type=urn:ietf:params:oauth:token-type:access_token" - << "&client_id=" << m_clientId - << "&audience=" << m_audience; + << "&client_id=" << m_clientId << "&audience=" << m_audience; std::string response = postRequest(m_tokenEndpoint, oss.str()); curl_easy_cleanup(curl); - if (response.empty()) return false; + if (response.empty()) + return false; auto js = nlohmann::json::parse(response, nullptr, false); if (js.is_discarded() || !js.contains("access_token")) { @@ -137,9 +148,11 @@ bool CrestAuth::exchangeTokenForAccess() { return true; } -std::string CrestAuth::postRequest(const std::string &url, const std::string &postFields) const { +std::string CrestAuth::postRequest(const std::string& url, + const std::string& postFields) const { CURL* curl = curl_easy_init(); - if (!curl) return ""; + if (!curl) + return ""; std::string response; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); @@ -148,13 +161,15 @@ std::string CrestAuth::postRequest(const std::string &url, const std::string &po curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); struct curl_slist* headers = nullptr; - headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded"); + headers = curl_slist_append( + headers, "Content-Type: application/x-www-form-urlencoded"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { - std::cerr << "[CrestAuth] curl_easy_perform failed: " << curl_easy_strerror(res) << "\n"; + std::cerr << "[CrestAuth] curl_easy_perform failed: " + << curl_easy_strerror(res) << "\n"; } curl_slist_free_all(headers); @@ -162,7 +177,8 @@ std::string CrestAuth::postRequest(const std::string &url, const std::string &po return response; } -size_t CrestAuth::WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) { +size_t CrestAuth::WriteCallback(void* contents, size_t size, size_t nmemb, + void* userp) { size_t totalSize = size * nmemb; std::string* out = static_cast<std::string*>(userp); out->append(static_cast<char*>(contents), totalSize); @@ -170,4 +186,3 @@ size_t CrestAuth::WriteCallback(void* contents, size_t size, size_t nmemb, void* } } // namespace Crest - diff --git a/src/CrestRequest.cxx b/src/CrestRequest.cxx index 84d2d6a..369e0a9 100644 --- a/src/CrestRequest.cxx +++ b/src/CrestRequest.cxx @@ -44,15 +44,16 @@ CrestRequest::CrestRequest() { m_CREST_PROXY = std::getenv(m_CREST_PROXY_VAR); std::cerr << "[DEBUG] Setting up authorization...\n"; - //std::cerr << "[DEBUG] Calling loadTokens()...\n"; + // std::cerr << "[DEBUG] Calling loadTokens()...\n"; bool tokensLoaded = m_auth.loadTokens(); - //std::cerr << "[DEBUG] loadTokens() returned: " << tokensLoaded << "\n"; + // std::cerr << "[DEBUG] loadTokens() returned: " << tokensLoaded << "\n"; if (!tokensLoaded) { throw std::runtime_error( "[CrestRequest] No valid token file found.\n" "Please generate a new exchange token using the authentication utility " - "(auth-get-user-token -c crest-client -o ~/.client-exchange-token.json) and try again."); + "(auth-get-user-token -c crest-client -o " + "~/.client-exchange-token.json) and try again."); } std::cerr << "[DEBUG] Checking if exchange token is expired...\n"; @@ -65,7 +66,8 @@ CrestRequest::CrestRequest() { } } if (!m_auth.exchangeTokenForAccess()) { - throw std::runtime_error("[CrestRequest] Failed to obtain access token from exchange."); + throw std::runtime_error( + "[CrestRequest] Failed to obtain access token from exchange."); } } @@ -179,7 +181,7 @@ std::string CrestRequest::performRequest(const std::string ¤t_path, // -k analogue: curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); - //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); if (js.is_null()) { if (action == Action::DELETE) curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); diff --git a/src/JwtUtils.cxx b/src/JwtUtils.cxx index 3f6b27d..f8e2f05 100644 --- a/src/JwtUtils.cxx +++ b/src/JwtUtils.cxx @@ -1,69 +1,77 @@ #include <CrestApi/JwtUtils.h> + +#include <algorithm> #include <nlohmann/json.hpp> -#include <stdexcept> #include <sstream> -#include <algorithm> +#include <stdexcept> namespace Crest { std::string JwtUtils::base64urlDecode(const std::string& input) { - std::string base64 = input; - std::replace(base64.begin(), base64.end(), '-', '+'); - std::replace(base64.begin(), base64.end(), '_', '/'); - while (base64.length() % 4 != 0) base64 += '='; + std::string base64 = input; + std::replace(base64.begin(), base64.end(), '-', '+'); + std::replace(base64.begin(), base64.end(), '_', '/'); + while (base64.length() % 4 != 0) + base64 += '='; - static constexpr unsigned char kDecodeTable[256] = { - 64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64, - 64,64,64,64,64,64,64,64,64,64,64,62,64,64,64,63,52,53,54,55,56,57,58,59,60,61,64,64,64, 0,64,64, - 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,64,64,64,64,64, - 64,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,64,64,64,64,64, - 64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64, - 64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64, - 64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64, - 64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64 - }; + static constexpr unsigned char kDecodeTable[256] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 64, 64, 64, 0, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64}; - std::string output; - size_t i = 0; - while (i < base64.length()) { - uint32_t val = 0; - int valb = -8; - for (int j = 0; j < 4 && i < base64.length(); ++j) { - unsigned char c = base64[i++]; - if (kDecodeTable[c] == 64) throw std::runtime_error("Invalid base64url character"); - val = (val << 6) + kDecodeTable[c]; - valb += 6; - } - while (valb >= 0) { - output.push_back(static_cast<char>((val >> valb) & 0xFF)); - valb -= 8; - } + std::string output; + size_t i = 0; + while (i < base64.length()) { + uint32_t val = 0; + int valb = -8; + for (int j = 0; j < 4 && i < base64.length(); ++j) { + unsigned char c = base64[i++]; + if (kDecodeTable[c] == 64) + throw std::runtime_error("Invalid base64url character"); + val = (val << 6) + kDecodeTable[c]; + valb += 6; } - return output; + while (valb >= 0) { + output.push_back(static_cast<char>((val >> valb) & 0xFF)); + valb -= 8; + } + } + return output; } std::time_t JwtUtils::extractExp(const std::string& jwt, int graceSeconds) { - size_t dot1 = jwt.find('.'); - size_t dot2 = jwt.find('.', dot1 + 1); - if (dot1 == std::string::npos || dot2 == std::string::npos) { - throw std::runtime_error("Invalid JWT format"); - } + size_t dot1 = jwt.find('.'); + size_t dot2 = jwt.find('.', dot1 + 1); + if (dot1 == std::string::npos || dot2 == std::string::npos) { + throw std::runtime_error("Invalid JWT format"); + } - std::string payloadEncoded = jwt.substr(dot1 + 1, dot2 - dot1 - 1); - std::string payloadJson = base64urlDecode(payloadEncoded); + std::string payloadEncoded = jwt.substr(dot1 + 1, dot2 - dot1 - 1); + std::string payloadJson = base64urlDecode(payloadEncoded); - auto js = nlohmann::json::parse(payloadJson, nullptr, false); - if (js.is_discarded()) { - throw std::runtime_error("Failed to parse JWT payload"); - } + auto js = nlohmann::json::parse(payloadJson, nullptr, false); + if (js.is_discarded()) { + throw std::runtime_error("Failed to parse JWT payload"); + } - if (!js.contains("exp") || !js["exp"].is_number()) { - throw std::runtime_error("JWT payload does not contain valid 'exp'"); - } + if (!js.contains("exp") || !js["exp"].is_number()) { + throw std::runtime_error("JWT payload does not contain valid 'exp'"); + } - std::time_t rawExp = js["exp"]; - return rawExp - graceSeconds; + std::time_t rawExp = js["exp"]; + return rawExp - graceSeconds; } } // namespace Crest - -- GitLab From 2eeac3424e2890a777001bd2024c830f3ab88792 Mon Sep 17 00:00:00 2001 From: mavogel <mavogel@cern.ch> Date: Wed, 14 May 2025 19:36:23 +0200 Subject: [PATCH 27/33] Client authorization via env: CREST_AUTH_MODE --- CrestApi/CrestAuth.h | 2 ++ CrestApi/CrestRequest.h | 3 +++ src/CrestApi.cxx | 2 ++ src/CrestAuth.cxx | 28 ++++++++++++++++++++++ src/CrestRequest.cxx | 53 ++++++++++++++++++----------------------- 5 files changed, 58 insertions(+), 30 deletions(-) diff --git a/CrestApi/CrestAuth.h b/CrestApi/CrestAuth.h index 0b52006..dac693c 100644 --- a/CrestApi/CrestAuth.h +++ b/CrestApi/CrestAuth.h @@ -36,6 +36,8 @@ class CrestAuth { bool isExchangeTokenExpired() const; std::string getAccessToken() const; + + bool setupExchangeCredentials(); }; } // namespace Crest diff --git a/CrestApi/CrestRequest.h b/CrestApi/CrestRequest.h index 2e77d19..d38d7f6 100644 --- a/CrestApi/CrestRequest.h +++ b/CrestApi/CrestRequest.h @@ -82,6 +82,9 @@ class CrestRequest { // Getters std::string getUrl(); + // Setup authentication + void initAuth(const char* mode); + /** * General auxillary method to make request to the CREST Server. This method * is used by other methods realizing the requests with the concrete kinds of diff --git a/src/CrestApi.cxx b/src/CrestApi.cxx index 2768c8e..b403d6e 100644 --- a/src/CrestApi.cxx +++ b/src/CrestApi.cxx @@ -36,6 +36,7 @@ CrestApi::CrestApi(const std::string &host, const std::string &port, m_request.setHost(host); m_request.setPort(port); m_request.setPrefix(protocol); + m_request.initAuth(std::getenv("CREST_AUTH_MODE")); } /** @@ -60,6 +61,7 @@ CrestApi::CrestApi(std::string_view url, std::string_view apipath, m_request.setHost(parsedUrl.host.data()); m_request.setPort(std::to_string(*parsedUrl.port)); m_request.setPrefix(parsedUrl.protocol.data()); + m_request.initAuth(std::getenv("CREST_AUTH_MODE")); if (parsedUrl.apipath == "") { m_PATH = urlPaths[UrlPathIndex::CREST_ROOT]; diff --git a/src/CrestAuth.cxx b/src/CrestAuth.cxx index 804865c..ee54875 100644 --- a/src/CrestAuth.cxx +++ b/src/CrestAuth.cxx @@ -169,5 +169,33 @@ size_t CrestAuth::WriteCallback(void* contents, size_t size, size_t nmemb, void* return totalSize; } +bool CrestAuth::setupExchangeCredentials() { + std::cerr << "[CrestAuth] Setting up authorization - requesting access token...\n"; + + bool tokensLoaded = loadTokens(); + if (!tokensLoaded) { + throw std::runtime_error( + "[CrestAuth] No valid token file found.\n" + "Please generate a new exchange token using the authentication utility:\n" + "auth-get-user-token -c crest-client -o ~/.client-exchange-token.json"); + } + + std::cerr << "[CrestAuth] Checking if exchange token is expired...\n"; + if (isExchangeTokenExpired()) { + std::cerr << "[CrestAuth] Exchange token is expired. Attempting refresh...\n"; + if (!refreshExchangeToken()) { + throw std::runtime_error( + "[CrestAuth] Exchange token is expired and refresh failed.\n" + "Please generate a new token manually."); + } + } + + if (!exchangeTokenForAccess()) { + throw std::runtime_error("[CrestAuth] Failed to obtain access token from exchange."); + } + + std::cerr << "[CrestAuth] Setup complete — access token obtained.\n"; + return true; +} } // namespace Crest diff --git a/src/CrestRequest.cxx b/src/CrestRequest.cxx index 84d2d6a..aa37b06 100644 --- a/src/CrestRequest.cxx +++ b/src/CrestRequest.cxx @@ -42,31 +42,6 @@ std::ostream &operator<<(std::ostream &os, Action action) { CrestRequest::CrestRequest() { curl_global_init(CURL_GLOBAL_ALL); m_CREST_PROXY = std::getenv(m_CREST_PROXY_VAR); - - std::cerr << "[DEBUG] Setting up authorization...\n"; - //std::cerr << "[DEBUG] Calling loadTokens()...\n"; - bool tokensLoaded = m_auth.loadTokens(); - //std::cerr << "[DEBUG] loadTokens() returned: " << tokensLoaded << "\n"; - - if (!tokensLoaded) { - throw std::runtime_error( - "[CrestRequest] No valid token file found.\n" - "Please generate a new exchange token using the authentication utility " - "(auth-get-user-token -c crest-client -o ~/.client-exchange-token.json) and try again."); - } - - std::cerr << "[DEBUG] Checking if exchange token is expired...\n"; - if (m_auth.isExchangeTokenExpired()) { - std::cerr << "[DEBUG] Token is expired. Attempting to refresh it...\n"; - if (!m_auth.refreshExchangeToken()) { - throw std::runtime_error( - "[CrestRequest] Exchange token is expired and refresh failed.\n" - "Please generate a new token manually."); - } - } - if (!m_auth.exchangeTokenForAccess()) { - throw std::runtime_error("[CrestRequest] Failed to obtain access token from exchange."); - } } CrestRequest::~CrestRequest() { @@ -102,6 +77,26 @@ std::string CrestRequest::getUrl() { return m_url; } +void CrestRequest::initAuth(const char* mode) { + if (!mode) { + std::cerr << "[CrestRequest] CREST_AUTH_MODE is unset — skipping client authorization.\n"; + return; + } + + std::string modeStr = mode; + std::cerr << "[CrestRequest] CREST_AUTH_MODE is set to: " << modeStr << "\n"; + + if (modeStr == "JWT") { + std::cerr << "[CrestRequest] Authorizing via JWT.\n"; + m_auth.setupExchangeCredentials(); + } else if (modeStr == "CLIENT_CREDENTIALS") { + std::cerr << "[CrestRequest] Authorizing via client id and secret.\n"; + // m_auth.setupClientCredentials(); + } else { + std::cerr << "[CrestRequest] Valid values of CREST_AUTH_MODE: JWT, CLIENT_CREDENTIALS — skipping client authorization.\n"; + } +} + size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::vector<char> *output) { size_t total_size = size * nmemb; @@ -151,12 +146,10 @@ std::string CrestRequest::performRequest(const std::string ¤t_path, std::string stt; struct curl_slist *headers = NULL; std::string token = m_auth.getAccessToken(); - - if (token.empty()) { - throw std::runtime_error("[CrestRequest] Access token is empty."); + if (!token.empty()) { + std::string authHeader = "Authorization: Bearer " + token; + headers = curl_slist_append(headers, authHeader.c_str()); } - std::string authHeader = "Authorization: Bearer " + token; - headers = curl_slist_append(headers, authHeader.c_str()); if (curl) { std::string url = Crest::StringUtils::appendEndpoint(m_url, current_path); std::string s; -- GitLab From e7d26a964ccec016d8bf4b9a8e6d917e30280dc2 Mon Sep 17 00:00:00 2001 From: mavogel <mavogel@cern.ch> Date: Wed, 14 May 2025 23:25:41 +0200 Subject: [PATCH 28/33] Added documentation and stardard logging --- CrestApi/CrestAuth.h | 63 +++++++++++++++++++++++++++++++++++++++++ CrestApi/CrestRequest.h | 17 ++++++++++- src/CrestAuth.cxx | 55 ++++++++++++++++++----------------- src/CrestRequest.cxx | 10 +++---- 4 files changed, 111 insertions(+), 34 deletions(-) diff --git a/CrestApi/CrestAuth.h b/CrestApi/CrestAuth.h index dac693c..b1140c6 100644 --- a/CrestApi/CrestAuth.h +++ b/CrestApi/CrestAuth.h @@ -9,6 +9,10 @@ namespace Crest { +/** + * Class handling authentication for CREST requests. + * It loads, refreshes, and exchanges tokens used to authorize access. + */ class CrestAuth { private: std::string m_tokenFilePath; @@ -22,21 +26,80 @@ class CrestAuth { std::time_t m_exchangeTokenExpiresAt; + /** + * Save the full token JSON response to the configured token file. + * @param tokenJson Full JSON response from the authentication service. + * @return true if saving was successful, false otherwise. + */ bool saveTokens(const nlohmann::json& tokenJson) const; + + + /** + * Sends a form-encoded POST request using libcurl to the given URL. + * @param url Target endpoint URL. + * @param postFields POST body formatted as application/x-www-form-urlencoded. + * @return The response body as a string, or empty string on failure. + */ std::string postRequest(const std::string &url, const std::string &postFields) const; + /** + * libcurl write callback to accumulate received data into a string. + * @param contents Raw data pointer. + * @param size Size of each data chunk. + * @param nmemb Number of data chunks. + * @param userp Pointer to std::string used to collect output. + * @return Total bytes handled (size * nmemb). + */ static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp); public: + /** + * Default constructor. Initializes internal variables from default + * environment-based settings or hardcoded defaults. + */ CrestAuth(); + /** + * Loads the exchange and refresh tokens from a file on disk. + * Sets internal state based on the saved token data. + * @return true if loading and parsing succeeded; false if file missing or malformed. + */ bool loadTokens(); + + /** + * Uses the current refresh token to request a new exchange token from the authentication service. + * Stores the entire response to the token file and updates internal state. + * @return true if the refresh was successful, false otherwise. + */ bool refreshExchangeToken(); + + /** + * Exchanges the current exchange token for a final access token used for authorization. + * This does not persist the access token to file. + * @return true if access token was successfully acquired, false otherwise. + */ bool exchangeTokenForAccess(); + /** + * Checks whether the current exchange token is expired based on the 'exp' claim. + * @return true if the exchange token is expired or invalid. + */ bool isExchangeTokenExpired() const; + + /** + * Returns the currently cached access token. This method does not initiate a new exchange. + * @return Access token string if previously acquired; otherwise an empty string. + */ std::string getAccessToken() const; + + /** + * Initializes exchange credentials: + * - Loads exchange and refresh tokens from file + * - Refreshes the exchange token if expired + * - Performs token exchange to obtain an access token + * @return true if all steps succeed; throws std::runtime_error on fatal errors. + */ bool setupExchangeCredentials(); }; diff --git a/CrestApi/CrestRequest.h b/CrestApi/CrestRequest.h index d38d7f6..481e3a0 100644 --- a/CrestApi/CrestRequest.h +++ b/CrestApi/CrestRequest.h @@ -82,7 +82,22 @@ class CrestRequest { // Getters std::string getUrl(); - // Setup authentication +/** + * Initializes authentication and authorization for CREST requests. + * + * This method reads the specified execution mode (e.g., "JWT", "CLIENT_CREDENTIALS") + * and sets up access credentials accordingly. It: + * - Requests an access token via an exchange token if mode="JWT" + * - Requests an access token via client id and secret if mode="CLIENT_CREDENTIALS" + * + * If the mode is nullptr (i.e. environment variable not set), no special behavior is applied. + * + * @param mode - A C-style string indicating the execution mode (e.g., from the CREST_AUTH_MODE + * environment variable). If nullptr, the default behavior is applied. + * + * @throws std::runtime_error if authentication fails critically (e.g., missing token file or + * failed token exchange). + */ void initAuth(const char* mode); /** diff --git a/src/CrestAuth.cxx b/src/CrestAuth.cxx index ee54875..3354597 100644 --- a/src/CrestAuth.cxx +++ b/src/CrestAuth.cxx @@ -1,6 +1,7 @@ // CrestAuth.cxx #include <CrestApi/CrestAuth.h> #include <CrestApi/JwtUtils.h> +#include <CrestApi/CrestLogger.h> #include <fstream> #include <iostream> #include <sstream> @@ -17,13 +18,13 @@ CrestAuth::CrestAuth() bool CrestAuth::loadTokens() { std::ifstream file(m_tokenFilePath); if (!file.is_open()) { - std::cerr << "[CrestAuth] Failed to open token file: " << m_tokenFilePath << "\n"; + Logger::log(LogLevel::ERROR, "Failed to open token file: " + m_tokenFilePath); return false; } std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); if (content.empty()) { - std::cerr << "[CrestAuth] Token file is empty\n"; + Logger::log(LogLevel::ERROR, "Token file is empty"); return false; } @@ -34,7 +35,7 @@ bool CrestAuth::loadTokens() { try { file >> js; } catch (const std::exception& e) { - std::cerr << "[CrestAuth] Failed to parse token JSON: " << e.what() << "\n"; + Logger::log(LogLevel::ERROR, "Failed to parse token JSON: " + (std::string)e.what()); return false; } @@ -43,23 +44,23 @@ bool CrestAuth::loadTokens() { try { m_exchangeTokenExpiresAt = JwtUtils::extractExp(m_exchangeToken, 30); } catch (const std::exception& e) { - std::cerr << "[CrestAuth] Failed to extract 'exp' from JWT: " << e.what() << "\n"; + Logger::log(LogLevel::ERROR, "Failed to extract 'exp' from JWT: " + (std::string)e.what()); m_exchangeTokenExpiresAt = std::time(nullptr) + 300; // fallback } - std::cerr << "[CrestAuth] Loaded exchange and refresh tokens from file.\n"; + Logger::log(LogLevel::INFO, "Loaded exchange and refresh tokens from file"); return true; } bool CrestAuth::saveTokens(const nlohmann::json& tokenJson) const { std::ofstream file(m_tokenFilePath); if (!file.is_open()) { - std::cerr << "[CrestAuth] Failed to open token file for writing: " << m_tokenFilePath << "\n"; + Logger::log(LogLevel::ERROR, "Failed to open token file for writing: " + m_tokenFilePath); return false; } file << tokenJson.dump(); - std::cerr << "[CrestAuth] Saved exchanged token to file.\n"; + Logger::log(LogLevel::INFO, "Saved exchanged token to file"); return true; } @@ -81,9 +82,8 @@ bool CrestAuth::refreshExchangeToken() { if (response.empty()) return false; auto js = nlohmann::json::parse(response, nullptr, false); - //std::cerr << "[CrestAuth] Response to refresh exchange token: " << js.dump() << "\n"; if (js.is_discarded() || js.contains("error")) { - std::cerr << "[CrestAuth] Failed to refresh exchange token: " << js.dump(2) << "\n"; + Logger::log(LogLevel::ERROR, "Failed to refresh exchange token: " + js.dump(2)); return false; } @@ -92,22 +92,22 @@ bool CrestAuth::refreshExchangeToken() { try { m_exchangeTokenExpiresAt = JwtUtils::extractExp(m_exchangeToken, 30); } catch (const std::exception& e) { - std::cerr << "[CrestAuth] Failed to extract 'exp' from JWT: " << e.what() << "\n"; + Logger::log(LogLevel::ERROR, "Failed to extract 'exp' from JWT: " + (std::string)e.what()); m_exchangeTokenExpiresAt = std::time(nullptr) + 300; // fallback } - std::cerr << "[CrestAuth] Successfully refreshed exchange token.\n"; + Logger::log(LogLevel::INFO, "Successfully refreshed exchange token"); return saveTokens(js); } bool CrestAuth::exchangeTokenForAccess() { CURL* curl = curl_easy_init(); if (!curl) { - std::cerr << "[CrestAuth] Failed to initialize CURL.\n"; + Logger::log(LogLevel::ERROR, "Failed to initialize CURL"); return false; } if (m_exchangeToken.empty()) { - std::cerr << "[CrestAuth] Exchange token is empty.\n"; + Logger::log(LogLevel::ERROR, "Exchange token is empty"); curl_easy_cleanup(curl); return false; } @@ -126,13 +126,13 @@ bool CrestAuth::exchangeTokenForAccess() { auto js = nlohmann::json::parse(response, nullptr, false); if (js.is_discarded() || !js.contains("access_token")) { - std::cerr << "[CrestAuth] Token exchange failed: " << js.dump(2) << "\n"; + Logger::log(LogLevel::ERROR, "Token exchange failed: " + js.dump(2)); return false; } m_finalAccessToken = js["access_token"]; - std::cerr << "[CrestAuth] Token exchanged successfully.\n"; + Logger::log(LogLevel::INFO, "Token exchanged successfully"); return true; } @@ -154,7 +154,8 @@ std::string CrestAuth::postRequest(const std::string &url, const std::string &po CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { - std::cerr << "[CrestAuth] curl_easy_perform failed: " << curl_easy_strerror(res) << "\n"; + std::string errMsg = curl_easy_strerror(res); + Logger::log(LogLevel::ERROR, "curl_easy_perform failed: " + errMsg); } curl_slist_free_all(headers); @@ -170,31 +171,29 @@ size_t CrestAuth::WriteCallback(void* contents, size_t size, size_t nmemb, void* } bool CrestAuth::setupExchangeCredentials() { - std::cerr << "[CrestAuth] Setting up authorization - requesting access token...\n"; + Logger::log(LogLevel::INFO, "Setting up authorization - requesting access token..."); bool tokensLoaded = loadTokens(); if (!tokensLoaded) { - throw std::runtime_error( - "[CrestAuth] No valid token file found.\n" - "Please generate a new exchange token using the authentication utility:\n" - "auth-get-user-token -c crest-client -o ~/.client-exchange-token.json"); + Logger::log(LogLevel::ERROR, "No valid token file found.\nExecute: auth-get-user-token -c crest-client -o ~/.client-exchange-token.json"); + return false; } - std::cerr << "[CrestAuth] Checking if exchange token is expired...\n"; + Logger::log(LogLevel::INFO, "Checking if exchange token is expired..."); if (isExchangeTokenExpired()) { - std::cerr << "[CrestAuth] Exchange token is expired. Attempting refresh...\n"; + Logger::log(LogLevel::INFO, "Exchange token is expired. Attempting refresh..."); if (!refreshExchangeToken()) { - throw std::runtime_error( - "[CrestAuth] Exchange token is expired and refresh failed.\n" - "Please generate a new token manually."); + Logger::log(LogLevel::ERROR, "Exchange token is expired and refresh failed.\nExecute: auth-get-user-token -c crest-client -o ~/.client-exchange-token.json"); + return false; } } if (!exchangeTokenForAccess()) { - throw std::runtime_error("[CrestAuth] Failed to obtain access token from exchange."); + Logger::log(LogLevel::ERROR, "Failed to obtain access token from exchange"); + return false; } - std::cerr << "[CrestAuth] Setup complete — access token obtained.\n"; + Logger::log(LogLevel::INFO, "Setup complete — access token obtained"); return true; } } // namespace Crest diff --git a/src/CrestRequest.cxx b/src/CrestRequest.cxx index aa37b06..b919c53 100644 --- a/src/CrestRequest.cxx +++ b/src/CrestRequest.cxx @@ -79,21 +79,21 @@ std::string CrestRequest::getUrl() { void CrestRequest::initAuth(const char* mode) { if (!mode) { - std::cerr << "[CrestRequest] CREST_AUTH_MODE is unset — skipping client authorization.\n"; + Logger::log(LogLevel::ERROR, "CREST_AUTH_MODE is unset — skipping client authorization"); return; } std::string modeStr = mode; - std::cerr << "[CrestRequest] CREST_AUTH_MODE is set to: " << modeStr << "\n"; + Logger::log(LogLevel::DEBUG, "CREST_AUTH_MODE is set to: " + modeStr); if (modeStr == "JWT") { - std::cerr << "[CrestRequest] Authorizing via JWT.\n"; + Logger::log(LogLevel::INFO, "Authorizing via JWT"); m_auth.setupExchangeCredentials(); } else if (modeStr == "CLIENT_CREDENTIALS") { - std::cerr << "[CrestRequest] Authorizing via client id and secret.\n"; + Logger::log(LogLevel::INFO, "Authorizing via client id and secret"); // m_auth.setupClientCredentials(); } else { - std::cerr << "[CrestRequest] Valid values of CREST_AUTH_MODE: JWT, CLIENT_CREDENTIALS — skipping client authorization.\n"; + Logger::log(LogLevel::INFO, "Valid values of CREST_AUTH_MODE: JWT, CLIENT_CREDENTIALS — skipping client authorization"); } } -- GitLab From ddb0a736a84afd400ee910430e7dfe58190aeaec Mon Sep 17 00:00:00 2001 From: mavogel <mavogel@cern.ch> Date: Thu, 15 May 2025 23:43:40 +0200 Subject: [PATCH 29/33] Added client id and secret authorization --- CrestApi/CrestAuth.h | 14 ++++++++++++++ CrestApi/JwtUtils.h | 20 +++++++++++++++++++- src/CrestAuth.cxx | 41 ++++++++++++++++++++++++++++++++++++++++- src/CrestRequest.cxx | 12 ++++++++---- 4 files changed, 81 insertions(+), 6 deletions(-) diff --git a/CrestApi/CrestAuth.h b/CrestApi/CrestAuth.h index 1336f81..e9b1ff8 100644 --- a/CrestApi/CrestAuth.h +++ b/CrestApi/CrestAuth.h @@ -18,6 +18,7 @@ class CrestAuth { private: std::string m_tokenFilePath; std::string m_tokenEndpoint; + std::string m_apiEndpoint; std::string m_clientId; std::string m_audience; @@ -108,6 +109,19 @@ class CrestAuth { * errors. */ bool setupExchangeCredentials(); + + /** + * Requests an access token using the OAuth2 client credentials flow. + * Reads client ID and client secret from environment variables. + * Stores the resulting access token in memory. + * + * Environment variables: + * - CREST_CLIENT_ID + * - CREST_CLIENT_SECRET + * + * @return true if the token was successfully obtained and stored, false otherwise. + */ + bool setupClientCredentials(); }; } // namespace Crest diff --git a/CrestApi/JwtUtils.h b/CrestApi/JwtUtils.h index ccd2d5d..89a1f34 100644 --- a/CrestApi/JwtUtils.h +++ b/CrestApi/JwtUtils.h @@ -5,12 +5,30 @@ #include <string> namespace Crest { + +/** + * Utility class for decoding and inspecting JWT (JSON Web Token) claims. + */ class JwtUtils { public: - // Extracts the `exp` (expiration timestamp) from a JWT token + /** + * Extracts the `exp` (expiration) claim from a JWT access token. + * Optionally subtracts a grace period to account for clock skew. + * + * @param jwt A JWT string (in the format header.payload.signature). + * @param graceSeconds Optional number of seconds to subtract from the extracted expiration time. + * @return The expiration timestamp as time_t (Unix time). + * @throws std::runtime_error if the token is malformed or the exp claim is missing. + */ static std::time_t extractExp(const std::string& jwt, int graceSeconds = 0); private: + /** + * Decodes a Base64URL-encoded string (used in JWT header and payload). + * + * @param input The Base64URL-encoded string. + * @return The decoded string. + */ static std::string base64urlDecode(const std::string& input); }; } // namespace Crest diff --git a/src/CrestAuth.cxx b/src/CrestAuth.cxx index 1994bc2..dc339b9 100644 --- a/src/CrestAuth.cxx +++ b/src/CrestAuth.cxx @@ -15,6 +15,8 @@ CrestAuth::CrestAuth() m_tokenEndpoint( "https://auth.cern.ch/auth/realms/cern/protocol/openid-connect/" "token"), + m_apiEndpoint( + "https://auth.cern.ch/auth/realms/cern/api-access/token"), m_clientId("crest-client"), m_audience("crest-server"), m_exchangeTokenExpiresAt(0) {} @@ -164,7 +166,7 @@ std::string CrestAuth::postRequest(const std::string& url, headers = curl_slist_append( headers, "Content-Type: application/x-www-form-urlencoded"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { @@ -218,4 +220,41 @@ bool CrestAuth::setupExchangeCredentials() { Logger::log(LogLevel::INFO, "Setup complete — access token obtained"); return true; } + +bool CrestAuth::setupClientCredentials() { + const char* clientId = std::getenv("CREST_CLIENT_ID"); + const char* clientSecret = std::getenv("CREST_CLIENT_SECRET"); + + if (!clientId || !clientSecret) { + Logger::log(LogLevel::ERROR, "Missing CREST_CLIENT_ID or CREST_CLIENT_SECRET environment variable."); + return false; + } + + std::ostringstream oss; + oss << "grant_type=client_credentials" + << "&client_id=" << clientId + << "&client_secret=" << clientSecret + << "&subject_token_type=urn:ietf:params:oauth:token-type:access_token" + << "scope=openid" + << "&audience=" << m_audience; + + std::string response = postRequest(m_apiEndpoint, oss.str()); + if (response.empty()) { + Logger::log(LogLevel::ERROR, "No response received from client credentials request."); + return false; + } + + auto js = nlohmann::json::parse(response, nullptr, false); + //std::cerr << " client id and secret token: " << js.dump() << "\n"; + if (js.is_discarded() || !js.contains("access_token")) { + Logger::log(LogLevel::ERROR, "Failed to obtain access token using client credentials: " + js.dump(2)); + return false; + } + + m_finalAccessToken = js["access_token"]; + + Logger::log(LogLevel::INFO, "Access token acquired via client credentials flow"); + + return true; +} } // namespace Crest diff --git a/src/CrestRequest.cxx b/src/CrestRequest.cxx index 40f3a99..e64882b 100644 --- a/src/CrestRequest.cxx +++ b/src/CrestRequest.cxx @@ -79,7 +79,7 @@ std::string CrestRequest::getUrl() { void CrestRequest::initAuth(const char *mode) { if (!mode) { - Logger::log(LogLevel::ERROR, + Logger::log(LogLevel::WARNING, "CREST_AUTH_MODE is unset — skipping client authorization"); return; } @@ -92,7 +92,7 @@ void CrestRequest::initAuth(const char *mode) { m_auth.setupExchangeCredentials(); } else if (modeStr == "CLIENT_CREDENTIALS") { Logger::log(LogLevel::INFO, "Authorizing via client id and secret"); - // m_auth.setupClientCredentials(); + m_auth.setupClientCredentials(); } else { Logger::log(LogLevel::INFO, "Valid values of CREST_AUTH_MODE: JWT, CLIENT_CREDENTIALS — " @@ -175,7 +175,7 @@ std::string CrestRequest::performRequest(const std::string ¤t_path, // -k analogue: curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); - // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); if (js.is_null()) { if (action == Action::DELETE) curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); @@ -304,7 +304,11 @@ std::string CrestRequest::uploadPayload( curl = curl_easy_init(); struct curl_slist *headers = NULL; - + std::string token = m_auth.getAccessToken(); + if (!token.empty()) { + std::string authHeader = "Authorization: Bearer " + token; + headers = curl_slist_append(headers, authHeader.c_str()); + } if (curl) { std::string url = Crest::StringUtils::appendEndpoint(m_url, current_path); std::string response; -- GitLab From 520c6b440ba30f079d978b436e3e28260fcccb58 Mon Sep 17 00:00:00 2001 From: mavogel <mavogel@cern.ch> Date: Thu, 15 May 2025 23:58:23 +0200 Subject: [PATCH 30/33] Cleaning up --- src/CrestAuth.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CrestAuth.cxx b/src/CrestAuth.cxx index dc339b9..ea0866c 100644 --- a/src/CrestAuth.cxx +++ b/src/CrestAuth.cxx @@ -245,7 +245,6 @@ bool CrestAuth::setupClientCredentials() { } auto js = nlohmann::json::parse(response, nullptr, false); - //std::cerr << " client id and secret token: " << js.dump() << "\n"; if (js.is_discarded() || !js.contains("access_token")) { Logger::log(LogLevel::ERROR, "Failed to obtain access token using client credentials: " + js.dump(2)); return false; -- GitLab From 522eb575a64762c0490e58984b88966a91014b27 Mon Sep 17 00:00:00 2001 From: mavogel <mavogel@cern.ch> Date: Fri, 16 May 2025 16:56:15 +0200 Subject: [PATCH 31/33] Added authorization section to README, and a helper function to append authentication header --- CrestApi/CrestRequest.h | 6 ++++++ README.md | 18 +++++++++++++++++- src/CrestAuth.cxx | 2 +- src/CrestRequest.cxx | 32 +++++++++++++++++++++----------- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/CrestApi/CrestRequest.h b/CrestApi/CrestRequest.h index 9825e7b..3e64d05 100644 --- a/CrestApi/CrestRequest.h +++ b/CrestApi/CrestRequest.h @@ -103,6 +103,12 @@ class CrestRequest { */ void initAuth(const char *mode); + /** + * Creates an Authorization header using the current access token from CrestAuth. + * @return A curl_slist pointer containing the Authorization header, or nullptr if no token is available. + */ + struct curl_slist* createAuthHeader() const; + /** * General auxillary method to make request to the CREST Server. This method * is used by other methods realizing the requests with the concrete kinds of diff --git a/README.md b/README.md index c7eb90c..e846005 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,22 @@ cmake -DCMAKE_INSTALL_PREFIX=$HOME/local_lib .. make make install ``` + +## Authorization +Currently, the API supports two types of authorization workflows: +* `Client credentials`: the client is authenticated using a client id and secret. +```shell +export CREST_CLIENT_ID=<client id> +export CREST_CLIENT_SECRET=<secret> +export CREST_AUTH_MODE=CLIENT_CREDENTIALS +``` +* `Token exchange`: the client is authenticated using an exchange token. To obtain the token excute `auth-get-user-token`. +```shell +auth-get-user-token -c crest-client -o ~/.client-exchange-token.json +export CREST_AUTH_MODE=JWT +``` +Make sure to use `https` in `export CREST_API_URL=https://...` + ## Testing The simple examples are in the `doc` and `test` directory. The executables will be created in the `build` directory. @@ -64,4 +80,4 @@ The simple examples are in the `doc` and `test` directory. The executables will You can validate your installation for example using: ```shell ./crest_example_server http://crest-j23.cern.ch:8080/api-v5.0 -``` \ No newline at end of file +``` diff --git a/src/CrestAuth.cxx b/src/CrestAuth.cxx index ea0866c..efd2d86 100644 --- a/src/CrestAuth.cxx +++ b/src/CrestAuth.cxx @@ -95,7 +95,7 @@ bool CrestAuth::refreshExchangeToken() { auto js = nlohmann::json::parse(response, nullptr, false); if (js.is_discarded() || js.contains("error")) { Logger::log(LogLevel::ERROR, - "Failed to refresh exchange token: " + js.dump(2)); + "Failed to refresh exchange token: " + js.dump()); return false; } diff --git a/src/CrestRequest.cxx b/src/CrestRequest.cxx index e64882b..e09304f 100644 --- a/src/CrestRequest.cxx +++ b/src/CrestRequest.cxx @@ -100,6 +100,16 @@ void CrestRequest::initAuth(const char *mode) { } } +struct curl_slist* CrestRequest::createAuthHeader() const { + struct curl_slist* headers = nullptr; + std::string token = m_auth.getAccessToken(); + if (!token.empty()) { + std::string authHeader = "Authorization: Bearer " + token; + headers = curl_slist_append(headers, authHeader.c_str()); + } + return headers; +} + size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::vector<char> *output) { size_t total_size = size * nmemb; @@ -147,11 +157,9 @@ std::string CrestRequest::performRequest(const std::string ¤t_path, /* get a curl handle */ curl = curl_easy_init(); std::string stt; - struct curl_slist *headers = NULL; - std::string token = m_auth.getAccessToken(); - if (!token.empty()) { - std::string authHeader = "Authorization: Bearer " + token; - headers = curl_slist_append(headers, authHeader.c_str()); + struct curl_slist* headers = createAuthHeader(); + if (headers) { + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); } if (curl) { std::string url = Crest::StringUtils::appendEndpoint(m_url, current_path); @@ -238,7 +246,10 @@ std::vector<char> CrestRequest::getPayloadRequest( curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); - + struct curl_slist* headers = createAuthHeader(); + if (headers) { + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + } if (curl) { std::string url = Crest::StringUtils::appendEndpoint(m_url, current_path); std::string response; @@ -280,6 +291,7 @@ std::vector<char> CrestRequest::getPayloadRequest( long response_code; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + curl_slist_free_all(headers); curl_easy_cleanup(curl); // error checking in the server response: @@ -303,11 +315,9 @@ std::string CrestRequest::uploadPayload( curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); - struct curl_slist *headers = NULL; - std::string token = m_auth.getAccessToken(); - if (!token.empty()) { - std::string authHeader = "Authorization: Bearer " + token; - headers = curl_slist_append(headers, authHeader.c_str()); + struct curl_slist* headers = createAuthHeader(); + if (headers) { + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); } if (curl) { std::string url = Crest::StringUtils::appendEndpoint(m_url, current_path); -- GitLab From 4eb83113d2b3f95940661f52172d3a0b1bea7313 Mon Sep 17 00:00:00 2001 From: mavogel <mavogel@cern.ch> Date: Wed, 28 May 2025 00:48:36 +0200 Subject: [PATCH 32/33] Decreased verbosity in authentication logic --- src/CrestAuth.cxx | 18 +++++++++--------- src/CrestRequest.cxx | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/CrestAuth.cxx b/src/CrestAuth.cxx index efd2d86..2a48a3a 100644 --- a/src/CrestAuth.cxx +++ b/src/CrestAuth.cxx @@ -58,7 +58,7 @@ bool CrestAuth::loadTokens() { m_exchangeTokenExpiresAt = std::time(nullptr) + 300; // fallback } - Logger::log(LogLevel::INFO, "Loaded exchange and refresh tokens from file"); + Logger::log(LogLevel::DEBUG, "Loaded exchange and refresh tokens from file"); return true; } @@ -71,7 +71,7 @@ bool CrestAuth::saveTokens(const nlohmann::json& tokenJson) const { } file << tokenJson.dump(); - Logger::log(LogLevel::INFO, "Saved exchanged token to file"); + Logger::log(LogLevel::DEBUG, "Saved exchanged token to file"); return true; } @@ -108,7 +108,7 @@ bool CrestAuth::refreshExchangeToken() { "Failed to extract 'exp' from JWT: " + (std::string)e.what()); m_exchangeTokenExpiresAt = std::time(nullptr) + 300; // fallback } - Logger::log(LogLevel::INFO, "Successfully refreshed exchange token"); + Logger::log(LogLevel::DEBUG, "Successfully refreshed exchange token"); return saveTokens(js); } @@ -145,7 +145,7 @@ bool CrestAuth::exchangeTokenForAccess() { m_finalAccessToken = js["access_token"]; - Logger::log(LogLevel::INFO, "Token exchanged successfully"); + Logger::log(LogLevel::DEBUG, "Token exchanged successfully"); return true; } @@ -188,7 +188,7 @@ size_t CrestAuth::WriteCallback(void* contents, size_t size, size_t nmemb, } bool CrestAuth::setupExchangeCredentials() { - Logger::log(LogLevel::INFO, + Logger::log(LogLevel::DEBUG, "Setting up authorization - requesting access token..."); bool tokensLoaded = loadTokens(); @@ -199,9 +199,9 @@ bool CrestAuth::setupExchangeCredentials() { return false; } - Logger::log(LogLevel::INFO, "Checking if exchange token is expired..."); + Logger::log(LogLevel::DEBUG, "Checking if exchange token is expired..."); if (isExchangeTokenExpired()) { - Logger::log(LogLevel::INFO, + Logger::log(LogLevel::DEBUG, "Exchange token is expired. Attempting refresh..."); if (!refreshExchangeToken()) { Logger::log(LogLevel::ERROR, @@ -217,7 +217,7 @@ bool CrestAuth::setupExchangeCredentials() { return false; } - Logger::log(LogLevel::INFO, "Setup complete — access token obtained"); + Logger::log(LogLevel::DEBUG, "Setup complete — access token obtained"); return true; } @@ -252,7 +252,7 @@ bool CrestAuth::setupClientCredentials() { m_finalAccessToken = js["access_token"]; - Logger::log(LogLevel::INFO, "Access token acquired via client credentials flow"); + Logger::log(LogLevel::DEBUG, "Access token acquired via client credentials flow"); return true; } diff --git a/src/CrestRequest.cxx b/src/CrestRequest.cxx index e09304f..24b48ea 100644 --- a/src/CrestRequest.cxx +++ b/src/CrestRequest.cxx @@ -88,13 +88,13 @@ void CrestRequest::initAuth(const char *mode) { Logger::log(LogLevel::DEBUG, "CREST_AUTH_MODE is set to: " + modeStr); if (modeStr == "JWT") { - Logger::log(LogLevel::INFO, "Authorizing via JWT"); + Logger::log(LogLevel::DEBUG, "Authorizing via JWT"); m_auth.setupExchangeCredentials(); } else if (modeStr == "CLIENT_CREDENTIALS") { - Logger::log(LogLevel::INFO, "Authorizing via client id and secret"); + Logger::log(LogLevel::DEBUG, "Authorizing via client id and secret"); m_auth.setupClientCredentials(); } else { - Logger::log(LogLevel::INFO, + Logger::log(LogLevel::DEBUG, "Valid values of CREST_AUTH_MODE: JWT, CLIENT_CREDENTIALS — " "skipping client authorization"); } -- GitLab From cfd3013a75d6ffbe0ef7e40b5256dc3dc846f61b Mon Sep 17 00:00:00 2001 From: andrea formica <andrea.formica@cern.ch> Date: Sat, 7 Jun 2025 07:58:43 +0200 Subject: [PATCH 33/33] manual run of pre-commit for formatting --- CrestApi/CrestAuth.h | 3 ++- CrestApi/CrestRequest.h | 8 +++++--- CrestApi/JwtUtils.h | 6 ++++-- src/CrestAuth.cxx | 22 +++++++++++++--------- src/CrestRequest.cxx | 18 +++++++++--------- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/CrestApi/CrestAuth.h b/CrestApi/CrestAuth.h index e9b1ff8..83bf766 100644 --- a/CrestApi/CrestAuth.h +++ b/CrestApi/CrestAuth.h @@ -119,7 +119,8 @@ class CrestAuth { * - CREST_CLIENT_ID * - CREST_CLIENT_SECRET * - * @return true if the token was successfully obtained and stored, false otherwise. + * @return true if the token was successfully obtained and stored, false + * otherwise. */ bool setupClientCredentials(); }; diff --git a/CrestApi/CrestRequest.h b/CrestApi/CrestRequest.h index 3e64d05..401b605 100644 --- a/CrestApi/CrestRequest.h +++ b/CrestApi/CrestRequest.h @@ -104,10 +104,12 @@ class CrestRequest { void initAuth(const char *mode); /** - * Creates an Authorization header using the current access token from CrestAuth. - * @return A curl_slist pointer containing the Authorization header, or nullptr if no token is available. + * Creates an Authorization header using the current access token from + * CrestAuth. + * @return A curl_slist pointer containing the Authorization header, or + * nullptr if no token is available. */ - struct curl_slist* createAuthHeader() const; + struct curl_slist *createAuthHeader() const; /** * General auxillary method to make request to the CREST Server. This method diff --git a/CrestApi/JwtUtils.h b/CrestApi/JwtUtils.h index 89a1f34..2a15a64 100644 --- a/CrestApi/JwtUtils.h +++ b/CrestApi/JwtUtils.h @@ -16,9 +16,11 @@ class JwtUtils { * Optionally subtracts a grace period to account for clock skew. * * @param jwt A JWT string (in the format header.payload.signature). - * @param graceSeconds Optional number of seconds to subtract from the extracted expiration time. + * @param graceSeconds Optional number of seconds to subtract from the + * extracted expiration time. * @return The expiration timestamp as time_t (Unix time). - * @throws std::runtime_error if the token is malformed or the exp claim is missing. + * @throws std::runtime_error if the token is malformed or the exp claim is + * missing. */ static std::time_t extractExp(const std::string& jwt, int graceSeconds = 0); diff --git a/src/CrestAuth.cxx b/src/CrestAuth.cxx index 2a48a3a..98900ec 100644 --- a/src/CrestAuth.cxx +++ b/src/CrestAuth.cxx @@ -15,8 +15,7 @@ CrestAuth::CrestAuth() m_tokenEndpoint( "https://auth.cern.ch/auth/realms/cern/protocol/openid-connect/" "token"), - m_apiEndpoint( - "https://auth.cern.ch/auth/realms/cern/api-access/token"), + m_apiEndpoint("https://auth.cern.ch/auth/realms/cern/api-access/token"), m_clientId("crest-client"), m_audience("crest-server"), m_exchangeTokenExpiresAt(0) {} @@ -166,7 +165,7 @@ std::string CrestAuth::postRequest(const std::string& url, headers = curl_slist_append( headers, "Content-Type: application/x-www-form-urlencoded"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { @@ -226,33 +225,38 @@ bool CrestAuth::setupClientCredentials() { const char* clientSecret = std::getenv("CREST_CLIENT_SECRET"); if (!clientId || !clientSecret) { - Logger::log(LogLevel::ERROR, "Missing CREST_CLIENT_ID or CREST_CLIENT_SECRET environment variable."); + Logger::log( + LogLevel::ERROR, + "Missing CREST_CLIENT_ID or CREST_CLIENT_SECRET environment variable."); return false; } std::ostringstream oss; oss << "grant_type=client_credentials" - << "&client_id=" << clientId - << "&client_secret=" << clientSecret + << "&client_id=" << clientId << "&client_secret=" << clientSecret << "&subject_token_type=urn:ietf:params:oauth:token-type:access_token" << "scope=openid" << "&audience=" << m_audience; std::string response = postRequest(m_apiEndpoint, oss.str()); if (response.empty()) { - Logger::log(LogLevel::ERROR, "No response received from client credentials request."); + Logger::log(LogLevel::ERROR, + "No response received from client credentials request."); return false; } auto js = nlohmann::json::parse(response, nullptr, false); if (js.is_discarded() || !js.contains("access_token")) { - Logger::log(LogLevel::ERROR, "Failed to obtain access token using client credentials: " + js.dump(2)); + Logger::log(LogLevel::ERROR, + "Failed to obtain access token using client credentials: " + + js.dump(2)); return false; } m_finalAccessToken = js["access_token"]; - Logger::log(LogLevel::DEBUG, "Access token acquired via client credentials flow"); + Logger::log(LogLevel::DEBUG, + "Access token acquired via client credentials flow"); return true; } diff --git a/src/CrestRequest.cxx b/src/CrestRequest.cxx index 24b48ea..cbae7d4 100644 --- a/src/CrestRequest.cxx +++ b/src/CrestRequest.cxx @@ -100,8 +100,8 @@ void CrestRequest::initAuth(const char *mode) { } } -struct curl_slist* CrestRequest::createAuthHeader() const { - struct curl_slist* headers = nullptr; +struct curl_slist *CrestRequest::createAuthHeader() const { + struct curl_slist *headers = nullptr; std::string token = m_auth.getAccessToken(); if (!token.empty()) { std::string authHeader = "Authorization: Bearer " + token; @@ -157,9 +157,9 @@ std::string CrestRequest::performRequest(const std::string ¤t_path, /* get a curl handle */ curl = curl_easy_init(); std::string stt; - struct curl_slist* headers = createAuthHeader(); + struct curl_slist *headers = createAuthHeader(); if (headers) { - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); } if (curl) { std::string url = Crest::StringUtils::appendEndpoint(m_url, current_path); @@ -183,7 +183,7 @@ std::string CrestRequest::performRequest(const std::string ¤t_path, // -k analogue: curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); - //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); if (js.is_null()) { if (action == Action::DELETE) curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); @@ -246,9 +246,9 @@ std::vector<char> CrestRequest::getPayloadRequest( curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); - struct curl_slist* headers = createAuthHeader(); + struct curl_slist *headers = createAuthHeader(); if (headers) { - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); } if (curl) { std::string url = Crest::StringUtils::appendEndpoint(m_url, current_path); @@ -315,9 +315,9 @@ std::string CrestRequest::uploadPayload( curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); - struct curl_slist* headers = createAuthHeader(); + struct curl_slist *headers = createAuthHeader(); if (headers) { - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); } if (curl) { std::string url = Crest::StringUtils::appendEndpoint(m_url, current_path); -- GitLab