Project
Loading...
Searching...
No Matches
CcdbApi.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
16
17#include "CCDB/CcdbApi.h"
18#include "CCDB/CCDBQuery.h"
19
25#include <chrono>
26#include <memory>
27#include <ranges>
28#include <sstream>
29#include <TFile.h>
30#include <TGrid.h>
31#include <TSystem.h>
32#include <TStreamerInfo.h>
33#include <TMemFile.h>
34#include <TH1F.h>
35#include <TTree.h>
36#include <fairlogger/Logger.h>
37#include <TError.h>
38#include <TClass.h>
40#include <algorithm>
41#include <filesystem>
42#include <boost/algorithm/string.hpp>
43#include <iostream>
44#include <mutex>
45#include <boost/interprocess/sync/named_semaphore.hpp>
46#include <regex>
47#include <cstdio>
48#include <string>
49#include <string_view>
50#include <utility>
51#include <TAlienUserAgent.h>
52#include <unordered_set>
53#include "rapidjson/document.h"
54#include "rapidjson/writer.h"
55#include "rapidjson/stringbuffer.h"
56
57namespace o2::ccdb
58{
59
60using namespace std;
61
62std::mutex gIOMutex; // to protect TMemFile IO operations
63unique_ptr<TJAlienCredentials> CcdbApi::mJAlienCredentials = nullptr;
64
65namespace
66{
71std::string_view trimHeaderValue(std::string_view value)
72{
73 constexpr std::string_view whitespace = " \t\r\n";
74 const auto first = value.find_first_not_of(whitespace);
75 return first == std::string_view::npos
76 ? std::string_view{}
77 : value.substr(first, value.find_last_not_of(whitespace) - first + 1);
78}
79
86const std::vector<std::pair<std::string, std::string>>& gateTokenTable()
87{
88 static const auto table = []() {
89 std::vector<std::pair<std::string, std::string>> entries;
90 const char* spec = getenv("ALICEO2_CCDB_AUTH_TOKENS");
91 std::string_view rest = spec ? spec : "";
92 while (!rest.empty()) {
93 const auto sep = rest.find(';');
94 const auto entry = trimHeaderValue(rest.substr(0, sep));
95 rest = (sep == std::string_view::npos) ? std::string_view{} : rest.substr(sep + 1);
96 const auto eq = entry.find('=');
97 if (eq == std::string_view::npos) {
98 continue;
99 }
100 auto url = trimHeaderValue(entry.substr(0, eq));
101 // Trimmed: a stray newline in a token makes the request malformed, which
102 // a strict broker rejects with an opaque 400 rather than an auth error.
103 const auto token = trimHeaderValue(entry.substr(eq + 1));
104 while (url.size() > 1 && url.back() == '/') { // normalise, so the boundary test below is exact
105 url.remove_suffix(1);
106 }
107 if (!url.empty() && !token.empty()) {
108 entries.emplace_back(std::string(url), std::string("Authorization: Bearer ").append(token));
109 }
110 }
111 std::sort(entries.begin(), entries.end(),
112 [](const auto& a, const auto& b) { return a.first.size() > b.first.size(); });
113 return entries;
114 }();
115 return table;
116}
117
126curl_slist* appendGateToken(curl_slist* list, std::string_view url)
127{
128 for (const auto& [prefix, header] : gateTokenTable()) {
129 if (url.substr(0, prefix.size()) == prefix &&
130 (url.size() == prefix.size() || url[prefix.size()] == '/')) {
131 return curl_slist_append(list, header.c_str());
132 }
133 }
134 return list;
135}
136} // namespace
137
145{
146 public:
147 CCDBSemaphore(std::string const& cachepath, std::string const& path);
149
150 private:
151 boost::interprocess::named_semaphore* mSem = nullptr;
152 std::string mSemName{}; // name under which semaphore is kept by the OS kernel
153};
154
155// Small registry class with the purpose that a static object
156// ensures cleanup of registered semaphores even when programs
157// "crash".
159{
160 public:
161 SemaphoreRegistry() = default;
163 void add(CCDBSemaphore const* ptr);
164 void remove(CCDBSemaphore const* ptr);
165
166 private:
167 std::unordered_set<CCDBSemaphore const*> mStore;
168};
169static SemaphoreRegistry gSemaRegistry;
170
172{
173 using namespace o2::framework;
174 setUniqueAgentID();
175
177 mIsCCDBDownloaderPreferred = 0;
178 if (deploymentMode == DeploymentMode::OnlineDDS && deploymentMode == DeploymentMode::OnlineECS && deploymentMode == DeploymentMode::OnlineAUX && deploymentMode == DeploymentMode::FST) {
179 mIsCCDBDownloaderPreferred = 1;
180 }
181 if (getenv("ALICEO2_ENABLE_MULTIHANDLE_CCDBAPI")) { // todo rename ALICEO2_ENABLE_MULTIHANDLE_CCDBAPI to ALICEO2_PREFER_MULTIHANDLE_CCDBAPI
182 mIsCCDBDownloaderPreferred = atoi(getenv("ALICEO2_ENABLE_MULTIHANDLE_CCDBAPI"));
183 }
184 mDownloader = new CCDBDownloader();
185}
186
188{
189 curl_global_cleanup();
190 delete mDownloader;
191}
192
193void CcdbApi::setUniqueAgentID()
194{
195 mUniqueAgentID = TAlienUserAgent::BasedOnEnvironment().ToString();
196}
197
199{
200#ifdef __APPLE__
201 LOG(debug) << "On macOS we simply rely on TGrid::Connect(\"alien\").";
202 return true;
203#endif
204 if (getenv("ALICEO2_CCDB_NOTOKENCHECK") && atoi(getenv("ALICEO2_CCDB_NOTOKENCHECK"))) {
205 return true;
206 }
207 if (getenv("JALIEN_TOKEN_CERT")) {
208 return true;
209 }
210 auto returncode = system("LD_PRELOAD= alien-token-info &> /dev/null");
211 if (returncode == -1) {
212 LOG(error) << "...";
213 }
214 return returncode == 0;
215}
216
217void CcdbApi::curlInit()
218{
219 // todo : are there other things to initialize globally for curl ?
220 curl_global_init(CURL_GLOBAL_DEFAULT);
221 CcdbApi::mJAlienCredentials = std::make_unique<TJAlienCredentials>();
222 CcdbApi::mJAlienCredentials->loadCredentials();
223 CcdbApi::mJAlienCredentials->selectPreferedCredentials();
224
225 // allow to configure the socket timeout of CCDBDownloader (for some tuning studies)
226 if (getenv("ALICEO2_CCDB_SOCKET_TIMEOUT")) {
227 auto timeoutMS = atoi(getenv("ALICEO2_CCDB_SOCKET_TIMEOUT"));
228 if (timeoutMS >= 0) {
229 LOG(info) << "Setting socket timeout to " << timeoutMS << " milliseconds";
230 mDownloader->setKeepaliveTimeoutTime(timeoutMS);
231 }
232 }
233}
234
235void CcdbApi::init(std::string const& host)
236{
237 if (host.empty()) {
238 throw std::invalid_argument("Empty url passed CcdbApi, cannot initialize. Aborting.");
239 }
240
241 // if host is prefixed with "file://" this is a local snapshot
242 // in this case we init the API in snapshot (readonly) mode
243 constexpr const char* SNAPSHOTPREFIX = "file://";
244 mUrl = host;
245
246 if (host.substr(0, 7).compare(SNAPSHOTPREFIX) == 0) {
247 auto path = host.substr(7);
248 initInSnapshotMode(path);
249 } else {
250 initHostsPool(host);
251 curlInit();
252 }
253 // The environment option ALICEO2_CCDB_LOCALCACHE allows
254 // to reduce the number of queries to the server, by collecting the objects in a local
255 // cache folder, and serving from this folder for repeated queries.
256 // This is useful for instance for MC GRID productions in which we spawn
257 // many isolated processes, all querying the CCDB (for potentially the same objects and same timestamp).
258 // In addition, we can monitor exactly which objects are fetched and what is their content.
259 // One can also distribute so obtained caches to sites without network access.
260 //
261 // THE INFORMATION BELOW IS TEMPORARILY WRONG: the functionality of checking the validity if IGNORE_VALIDITYCHECK_OF_CCDB_LOCALCACHE
262 // is NOT set is broken. At the moment the code is modified to behave as if the IGNORE_VALIDITYCHECK_OF_CCDB_LOCALCACHE is always set
263 // whenever the ALICEO2_CCDB_LOCALCACHE is defined.
264 //
265 // When used with the DPL CCDB fetcher (i.e. loadFileToMemory is called), in order to prefer the available snapshot w/o its validity
266 // check an extra variable IGNORE_VALIDITYCHECK_OF_CCDB_LOCALCACHE must be defined, otherwhise the object will be fetched from the
267 // server after the validity check and new snapshot will be created if needed
268
269 std::string snapshotReport{};
270 const char* cachedir = getenv("ALICEO2_CCDB_LOCALCACHE");
271 namespace fs = std::filesystem;
272 if (cachedir) {
273 if (cachedir[0] == 0) {
274 mSnapshotCachePath = fs::weakly_canonical(fs::absolute("."));
275 } else {
276 mSnapshotCachePath = fs::weakly_canonical(fs::absolute(cachedir));
277 }
278 snapshotReport = fmt::format("(cache snapshots to dir={}", mSnapshotCachePath);
279 }
280 if (cachedir) { // || getenv("IGNORE_VALIDITYCHECK_OF_CCDB_LOCALCACHE")) {
281 mPreferSnapshotCache = true;
282 if (mSnapshotCachePath.empty()) {
283 LOGP(fatal, "IGNORE_VALIDITYCHECK_OF_CCDB_LOCALCACHE is defined but the ALICEO2_CCDB_LOCALCACHE is not");
284 }
285 snapshotReport += ", prefer if available";
286 }
287 if (!snapshotReport.empty()) {
288 snapshotReport += ')';
289 }
290
291 mNeedAlienToken = (host.find("https://") != std::string::npos) || (host.find("alice-ccdb.cern.ch") != std::string::npos) || (host.find("ccdb-test.cern.ch") != std::string::npos);
292 // Set the curl timeout. It can be forced with an env var or it has different defaults based on the deployment mode.
293 if (getenv("ALICEO2_CCDB_CURL_TIMEOUT_DOWNLOAD")) {
294 auto timeout = atoi(getenv("ALICEO2_CCDB_CURL_TIMEOUT_DOWNLOAD"));
295 if (timeout >= 0) { // if valid int
296 mCurlTimeoutDownload = timeout;
297 }
298 } else { // set a default depending on the deployment mode
300 if (deploymentMode == o2::framework::DeploymentMode::OnlineDDS ||
303 mCurlTimeoutDownload = 15;
304 } else if (deploymentMode == o2::framework::DeploymentMode::Grid ||
305 deploymentMode == o2::framework::DeploymentMode::FST) {
306 mCurlTimeoutDownload = 15;
307 } else if (deploymentMode == o2::framework::DeploymentMode::Local) {
308 mCurlTimeoutDownload = 5;
309 }
310 }
311
312 if (getenv("ALICEO2_CCDB_CURL_TIMEOUT_UPLOAD")) {
313 auto timeout = atoi(getenv("ALICEO2_CCDB_CURL_TIMEOUT_UPLOAD"));
314 if (timeout >= 0) { // if valid int
315 mCurlTimeoutUpload = timeout;
316 }
317 } else { // set a default depending on the deployment mode
319 if (deploymentMode == o2::framework::DeploymentMode::OnlineDDS ||
322 mCurlTimeoutUpload = 3;
323 } else if (deploymentMode == o2::framework::DeploymentMode::Grid ||
324 deploymentMode == o2::framework::DeploymentMode::FST) {
325 mCurlTimeoutUpload = 20;
326 } else if (deploymentMode == o2::framework::DeploymentMode::Local) {
327 mCurlTimeoutUpload = 20;
328 }
329 }
330 if (mDownloader) {
331 mDownloader->setRequestTimeoutTime(mCurlTimeoutDownload * 1000L);
332 }
333
334 LOGP(debug, "Curl timeouts are set to: download={:2}, upload={:2} seconds", mCurlTimeoutDownload, mCurlTimeoutUpload);
335
336 LOGP(info, "Init CcdApi with UserAgentID: {}, Host: {}{}, Curl timeouts: upload:{} download:{}", mUniqueAgentID, host,
337 mInSnapshotMode ? "(snapshot readonly mode)" : snapshotReport.c_str(), mCurlTimeoutUpload, mCurlTimeoutDownload);
338}
339
341{
342 mDownloader->runLoop(noWait);
343}
344
345// A helper function used in a few places. Updates a ROOT file with meta/header information.
346void CcdbApi::updateMetaInformationInLocalFile(std::string const& filename, std::map<std::string, std::string> const* headers, CCDBQuery const* querysummary)
347{
348 std::lock_guard<std::mutex> guard(gIOMutex);
349 auto oldlevel = gErrorIgnoreLevel;
350 gErrorIgnoreLevel = 6001; // ignoring error messages here (since we catch with IsZombie)
351 TFile snapshotfile(filename.c_str(), "UPDATE");
352 // The assumption is that the blob is a ROOT file
353 if (!snapshotfile.IsZombie()) {
354 if (querysummary && !snapshotfile.Get(CCDBQUERY_ENTRY)) {
355 snapshotfile.WriteObjectAny(querysummary, TClass::GetClass(typeid(*querysummary)), CCDBQUERY_ENTRY);
356 }
357 if (headers && !snapshotfile.Get(CCDBMETA_ENTRY)) {
358 snapshotfile.WriteObjectAny(headers, TClass::GetClass(typeid(*headers)), CCDBMETA_ENTRY);
359 }
360 snapshotfile.Write();
361 snapshotfile.Close();
362 }
363 gErrorIgnoreLevel = oldlevel;
364}
365
371std::string sanitizeObjectName(const std::string& objectName)
372{
373 std::string tmpObjectName = objectName;
374 tmpObjectName.erase(std::remove_if(tmpObjectName.begin(), tmpObjectName.end(),
375 [](auto const& c) -> bool { return (!std::isalnum(c) && c != '_' && c != '/' && c != '.'); }),
376 tmpObjectName.end());
377 return tmpObjectName;
378}
379
380std::unique_ptr<std::vector<char>> CcdbApi::createObjectImage(const void* obj, std::type_info const& tinfo, CcdbObjectInfo* info)
381{
382 // Create a binary image of the object, if CcdbObjectInfo pointer is provided, register there
383 // the assigned object class name and the filename
384 std::lock_guard<std::mutex> guard(gIOMutex);
385 std::string className = o2::utils::MemFileHelper::getClassName(tinfo);
386 std::string tmpFileName = generateFileName(className);
387 if (info) {
388 info->setFileName(tmpFileName);
389 info->setObjectType(className);
390 }
391 return o2::utils::MemFileHelper::createFileImage(obj, tinfo, tmpFileName, CCDBOBJECT_ENTRY);
392}
393
394std::unique_ptr<std::vector<char>> CcdbApi::createObjectImage(const TObject* rootObject, CcdbObjectInfo* info)
395{
396 // Create a binary image of the object, if CcdbObjectInfo pointer is provided, register there
397 // the assigned object class name and the filename
398 std::string className = rootObject->GetName();
399 std::string tmpFileName = generateFileName(className);
400 if (info) {
401 info->setFileName(tmpFileName);
402 info->setObjectType("TObject"); // why TObject and not the actual name?
403 }
404 std::lock_guard<std::mutex> guard(gIOMutex);
405 return o2::utils::MemFileHelper::createFileImage(*rootObject, tmpFileName, CCDBOBJECT_ENTRY);
406}
407
408int CcdbApi::storeAsTFile_impl(const void* obj, std::type_info const& tinfo, std::string const& path,
409 std::map<std::string, std::string> const& metadata,
410 long startValidityTimestamp, long endValidityTimestamp,
411 std::vector<char>::size_type maxSize) const
412{
413 // We need the TClass for this type; will verify if dictionary exists
414 if (!obj) {
415 LOGP(error, "nullptr is provided for object {}/{}/{}", path, startValidityTimestamp, endValidityTimestamp);
416 return -1;
417 }
418 CcdbObjectInfo info;
419 auto img = createObjectImage(obj, tinfo, &info);
420 return storeAsBinaryFile(img->data(), img->size(), info.getFileName(), info.getObjectType(),
421 path, metadata, startValidityTimestamp, endValidityTimestamp, maxSize);
422}
423
424int CcdbApi::storeAsBinaryFile(const char* buffer, size_t size, const std::string& filename, const std::string& objectType,
425 const std::string& path, const std::map<std::string, std::string>& metadata,
426 long startValidityTimestamp, long endValidityTimestamp, std::vector<char>::size_type maxSize) const
427{
428 if (maxSize > 0 && size > maxSize) {
429 LOGP(alarm, "Object will not be uploaded to {} since its size {} exceeds max allowed {}", path, size, maxSize);
430 return -1;
431 }
432 int returnValue = 0;
433
434 // Prepare URL
435 long sanitizedStartValidityTimestamp = startValidityTimestamp;
436 if (startValidityTimestamp == -1) {
437 LOGP(info, "Start of Validity not set, current timestamp used.");
438 sanitizedStartValidityTimestamp = getCurrentTimestamp();
439 }
440 long sanitizedEndValidityTimestamp = endValidityTimestamp;
441 if (endValidityTimestamp == -1) {
442 LOGP(info, "End of Validity not set, start of validity plus 1 day used.");
443 sanitizedEndValidityTimestamp = getFutureTimestamp(60 * 60 * 24 * 1);
444 }
445 if (mInSnapshotMode) { // write local file
446 if (filename.empty() || buffer == nullptr || size == 0) {
447 LOGP(alarm, "Snapshot mode does not support headers-only upload");
448 return -3;
449 }
450 auto pthLoc = getSnapshotDir(mSnapshotTopPath, path);
452 auto flLoc = getSnapshotFile(mSnapshotTopPath, path, filename);
453 // add the timestamps to the end
454 auto pent = flLoc.find_last_of('.');
455 if (pent == std::string::npos) {
456 pent = flLoc.size();
457 }
458 flLoc.insert(pent, fmt::format("_{}_{}", startValidityTimestamp, endValidityTimestamp));
459 ofstream outf(flLoc.c_str(), ios::out | ios::binary);
460 outf.write(buffer, size);
461 outf.close();
462 if (!outf.good()) {
463 throw std::runtime_error(fmt::format("Failed to write local CCDB file {}", flLoc));
464 } else {
465 std::map<std::string, std::string> metaheader(metadata);
466 // add time validity information
467 metaheader["Valid-From"] = std::to_string(startValidityTimestamp);
468 metaheader["Valid-Until"] = std::to_string(endValidityTimestamp);
469 updateMetaInformationInLocalFile(flLoc.c_str(), &metaheader);
470 std::string metaStr{};
471 for (const auto& mentry : metadata) {
472 metaStr += fmt::format("{}={};", mentry.first, mentry.second);
473 }
474 metaStr += "$USER_META;";
475 LOGP(info, "Created local snapshot {}", flLoc);
476 LOGP(info, R"(Upload with: o2-ccdb-upload --host "$ccdbhost" -p {} -f {} -k {} --starttimestamp {} --endtimestamp {} -m "{}")",
477 path, flLoc, CCDBOBJECT_ENTRY, startValidityTimestamp, endValidityTimestamp, metaStr);
478 }
479 return returnValue;
480 }
481
482 // Curl preparation
483 CURL* curl = nullptr;
484 curl = curl_easy_init();
485
486 // checking that all metadata keys do not contain invalid characters
487 checkMetadataKeys(metadata);
488
489 if (curl != nullptr) {
490 auto mime = curl_mime_init(curl);
491 auto field = curl_mime_addpart(mime);
492 curl_mime_name(field, "send");
493 if (!filename.empty()) {
494 curl_mime_filedata(field, filename.c_str());
495 }
496 if (buffer != nullptr && size > 0) {
497 curl_mime_data(field, buffer, size);
498 } else {
499 curl_mime_data(field, "", 0);
500 }
501
502 curlSetSSLOptions(curl);
503
504 curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
505 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
506 curl_easy_setopt(curl, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
507 curl_easy_setopt(curl, CURLOPT_TIMEOUT, mCurlTimeoutUpload);
508
509 CURLcode res = CURL_LAST;
510
511 for (size_t hostIndex = 0; hostIndex < hostsPool.size() && res > 0; hostIndex++) {
512 std::string fullUrl = getFullUrlForStorage(curl, path, objectType, metadata, sanitizedStartValidityTimestamp, sanitizedEndValidityTimestamp, hostIndex);
513 LOG(debug3) << "Full URL Encoded: " << fullUrl;
514 /* what URL that receives this POST */
515 curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str());
516
517 // Per host: the gate token is per endpoint (see appendGateToken).
518 struct curl_slist* headerlist = curl_slist_append(nullptr, "Expect:");
519 headerlist = appendGateToken(headerlist, fullUrl);
520 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
521
522 /* Perform the request, res will get the return code */
523 res = CURL_perform(curl);
524 /* Check for errors */
525 if (res != CURLE_OK) {
526 if (res == CURLE_OPERATION_TIMEDOUT) {
527 LOGP(alarm, "curl_easy_perform() timed out. Consider increasing the timeout using the env var `ALICEO2_CCDB_CURL_TIMEOUT_UPLOAD` (seconds), current one is {}", mCurlTimeoutUpload);
528 } else { // generic message
529 LOGP(alarm, "curl_easy_perform() failed: {}", curl_easy_strerror(res));
530 }
531 returnValue = res;
532 }
533 curl_slist_free_all(headerlist);
534 }
535
536 /* always cleanup */
537 curl_easy_cleanup(curl);
538
539 /* free mime */
540 curl_mime_free(mime);
541 } else {
542 LOGP(alarm, "curl initialization failure");
543 returnValue = -2;
544 }
545 return returnValue;
546}
547
548int CcdbApi::storeAsTFile(const TObject* rootObject, std::string const& path, std::map<std::string, std::string> const& metadata,
549 long startValidityTimestamp, long endValidityTimestamp, std::vector<char>::size_type maxSize) const
550{
551 // Prepare file
552 if (!rootObject) {
553 LOGP(error, "nullptr is provided for object {}/{}/{}", path, startValidityTimestamp, endValidityTimestamp);
554 return -1;
555 }
556 CcdbObjectInfo info;
557 auto img = createObjectImage(rootObject, &info);
558 return storeAsBinaryFile(img->data(), img->size(), info.getFileName(), info.getObjectType(), path, metadata, startValidityTimestamp, endValidityTimestamp, maxSize);
559}
560
561std::string CcdbApi::getFullUrlForStorage(CURL* curl, const std::string& path, const std::string& objtype,
562 const std::map<std::string, std::string>& metadata,
563 long startValidityTimestamp, long endValidityTimestamp, int hostIndex) const
564{
565 // Prepare timestamps
566 std::string startValidityString = getTimestampString(startValidityTimestamp < 0 ? getCurrentTimestamp() : startValidityTimestamp);
567 std::string endValidityString = getTimestampString(endValidityTimestamp < 0 ? getFutureTimestamp(60 * 60 * 24 * 1) : endValidityTimestamp);
568 // Get url
569 std::string url = getHostUrl(hostIndex);
570 // Build URL
571 std::string fullUrl = url + "/" + path + "/" + startValidityString + "/" + endValidityString + "/";
572 // Add type as part of metadata
573 // we need to URL encode the object type, since in case it has special characters (like the "<", ">" for templated classes) it won't work otherwise
574 char* objtypeEncoded = curl_easy_escape(curl, objtype.c_str(), objtype.size());
575 fullUrl += "ObjectType=" + std::string(objtypeEncoded) + "/";
576 curl_free(objtypeEncoded);
577 // Add general metadata
578 for (auto& kv : metadata) {
579 std::string mfirst = kv.first;
580 std::string msecond = kv.second;
581 // same trick for the metadata as for the object type
582 char* mfirstEncoded = curl_easy_escape(curl, mfirst.c_str(), mfirst.size());
583 char* msecondEncoded = curl_easy_escape(curl, msecond.c_str(), msecond.size());
584 fullUrl += std::string(mfirstEncoded) + "=" + std::string(msecondEncoded) + "/";
585 curl_free(mfirstEncoded);
586 curl_free(msecondEncoded);
587 }
588 return fullUrl;
589}
590
591// todo make a single method of the one above and below
592std::string CcdbApi::getFullUrlForRetrieval(CURL* curl, const std::string& path, const std::map<std::string, std::string>& metadata, long timestamp, int hostIndex) const
593{
594 if (mInSnapshotMode) {
595 return getSnapshotFile(mSnapshotTopPath, path);
596 }
597
598 // Prepare timestamps
599 std::string validityString = getTimestampString(timestamp < 0 ? getCurrentTimestamp() : timestamp);
600 // Get host url
601 std::string hostUrl = getHostUrl(hostIndex);
602 // Build URL
603 std::string fullUrl = hostUrl + "/" + path + "/" + validityString + "/";
604 // Add metadata
605 for (auto& kv : metadata) {
606 std::string mfirst = kv.first;
607 std::string msecond = kv.second;
608 // trick for the metadata in case it contains special characters
609 char* mfirstEncoded = curl_easy_escape(curl, mfirst.c_str(), mfirst.size());
610 char* msecondEncoded = curl_easy_escape(curl, msecond.c_str(), msecond.size());
611 fullUrl += std::string(mfirstEncoded) + "=" + std::string(msecondEncoded) + "/";
612 curl_free(mfirstEncoded);
613 curl_free(msecondEncoded);
614 }
615 return fullUrl;
616}
617
622 char* memory;
623 unsigned int size;
624};
625
635static size_t WriteMemoryCallback(void* contents, size_t size, size_t nmemb, void* userp)
636{
637 size_t realsize = size * nmemb;
638 auto* mem = (struct MemoryStruct*)userp;
639
640 mem->memory = (char*)realloc(mem->memory, mem->size + realsize + 1);
641 if (mem->memory == nullptr) {
642 printf("not enough memory (realloc returned NULL)\n");
643 return 0;
644 }
645
646 memcpy(&(mem->memory[mem->size]), contents, realsize);
647 mem->size += realsize;
648 mem->memory[mem->size] = 0;
649
650 return realsize;
651}
652
664static size_t WriteToFileCallback(void* ptr, size_t size, size_t nmemb, FILE* stream)
665{
666 size_t written = fwrite(ptr, size, nmemb, stream);
667 return written;
668}
669
677static CURLcode ssl_ctx_callback(CURL*, void*, void* parm)
678{
679 std::string msg((const char*)parm);
680 int start = 0, end = msg.find('\n');
681
682 if (msg.length() > 0 && end == -1) {
683 LOG(warn) << msg;
684 } else if (end > 0) {
685 while (end > 0) {
686 LOG(warn) << msg.substr(start, end - start);
687 start = end + 1;
688 end = msg.find('\n', start);
689 }
690 }
691 return CURLE_OK;
692}
693
694void CcdbApi::curlSetSSLOptions(CURL* curl_handle)
695{
696 CredentialsKind cmk = mJAlienCredentials->getPreferedCredentials();
697
698 /* NOTE: return early, the warning should be printed on SSL callback if needed */
699 if (cmk == cNOT_FOUND) {
700 return;
701 }
702
703 TJAlienCredentialsObject cmo = mJAlienCredentials->get(cmk);
704
705 char* CAPath = getenv("X509_CERT_DIR");
706 if (CAPath) {
707 curl_easy_setopt(curl_handle, CURLOPT_CAPATH, CAPath);
708 }
709 curl_easy_setopt(curl_handle, CURLOPT_CAINFO, nullptr);
710 curl_easy_setopt(curl_handle, CURLOPT_SSLCERT, cmo.certpath.c_str());
711 curl_easy_setopt(curl_handle, CURLOPT_SSLKEY, cmo.keypath.c_str());
712
713 // NOTE: for lazy logging only
714 curl_easy_setopt(curl_handle, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback);
715 curl_easy_setopt(curl_handle, CURLOPT_SSL_CTX_DATA, mJAlienCredentials->getMessages().c_str());
716
717 // CURLcode ret = curl_easy_setopt(curl_handle, CURLOPT_SSL_CTX_FUNCTION, *ssl_ctx_callback);
718}
719
720using CurlWriteCallback = size_t (*)(void*, size_t, size_t, void*);
721
722void CcdbApi::initCurlOptionsForRetrieve(CURL* curlHandle, void* chunk, CurlWriteCallback writeCallback, bool followRedirect) const
723{
724 curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, writeCallback);
725 curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, chunk);
726 curl_easy_setopt(curlHandle, CURLOPT_FOLLOWLOCATION, followRedirect ? 1L : 0L);
727}
728
729namespace
730{
731template <typename MapType = std::map<std::string, std::string>>
732size_t header_map_callback(char* buffer, size_t size, size_t nitems, void* userdata)
733{
734 auto* headers = static_cast<MapType*>(userdata);
735 auto header = std::string(buffer, size * nitems);
736 std::string::size_type index = header.find(':', 0);
737 if (index != std::string::npos) {
738 const auto key = boost::algorithm::trim_copy(header.substr(0, index));
739 const auto value = boost::algorithm::trim_copy(header.substr(index + 1));
740 LOGP(debug, "Adding #{} {} -> {}", headers->size(), key, value);
741 bool insert = true;
742 if (key == "Content-Length") {
743 auto cl = headers->find("Content-Length");
744 if (cl != headers->end()) {
745 if (std::stol(cl->second) < stol(value)) {
746 headers->erase(key);
747 } else {
748 insert = false;
749 }
750 }
751 }
752
753 // Keep only the first ETag encountered
754 if (key == "ETag") {
755 auto cl = headers->find("ETag");
756 if (cl != headers->end()) {
757 insert = false;
758 }
759 }
760
761 // Keep only the first Content-Type encountered
762 if (key == "Content-Type") {
763 auto cl = headers->find("Content-Type");
764 if (cl != headers->end()) {
765 insert = false;
766 }
767 }
768
769 if (insert) {
770 headers->insert(std::make_pair(key, value));
771 }
772 }
773 return size * nitems;
774}
775} // namespace
776
777void CcdbApi::initCurlHTTPHeaderOptionsForRetrieve(CURL* curlHandle, curl_slist*& option_list, long timestamp, std::map<std::string, std::string>* headers, std::string const& etag,
778 const std::string& createdNotAfter, const std::string& createdNotBefore, std::string_view url) const
779{
780 // struct curl_slist* list = nullptr;
781 if (!etag.empty()) {
782 option_list = curl_slist_append(option_list, ("If-None-Match: " + etag).c_str());
783 }
784
785 if (!createdNotAfter.empty()) {
786 option_list = curl_slist_append(option_list, ("If-Not-After: " + createdNotAfter).c_str());
787 }
788
789 if (!createdNotBefore.empty()) {
790 option_list = curl_slist_append(option_list, ("If-Not-Before: " + createdNotBefore).c_str());
791 }
792
793 if (headers != nullptr) {
794 option_list = curl_slist_append(option_list, ("If-None-Match: " + to_string(timestamp)).c_str());
795 curl_easy_setopt(curlHandle, CURLOPT_HEADERFUNCTION, header_map_callback<>);
796 curl_easy_setopt(curlHandle, CURLOPT_HEADERDATA, headers);
797 }
798
799 option_list = appendGateToken(option_list, url);
800
801 // Unconditionally, nullptr included: the handle is reused across hosts, and
802 // skipping the set would leave a previous host's freed list installed.
803 curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, option_list);
804
805 curl_easy_setopt(curlHandle, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
806}
807
808bool CcdbApi::receiveToFile(FILE* fileHandle, std::string const& path, std::map<std::string, std::string> const& metadata,
809 long timestamp, std::map<std::string, std::string>* headers, std::string const& etag,
810 const std::string& createdNotAfter, const std::string& createdNotBefore, bool followRedirect) const
811{
812 return receiveObject((void*)fileHandle, path, metadata, timestamp, headers, etag, createdNotAfter, createdNotBefore, followRedirect, (CurlWriteCallback)&WriteToFileCallback);
813}
814
815bool CcdbApi::receiveToMemory(void* chunk, std::string const& path, std::map<std::string, std::string> const& metadata,
816 long timestamp, std::map<std::string, std::string>* headers, std::string const& etag,
817 const std::string& createdNotAfter, const std::string& createdNotBefore, bool followRedirect) const
818{
819 return receiveObject((void*)chunk, path, metadata, timestamp, headers, etag, createdNotAfter, createdNotBefore, followRedirect, (CurlWriteCallback)&WriteMemoryCallback);
820}
821
822bool CcdbApi::receiveObject(void* dataHolder, std::string const& path, std::map<std::string, std::string> const& metadata,
823 long timestamp, std::map<std::string, std::string>* headers, std::string const& etag,
824 const std::string& createdNotAfter, const std::string& createdNotBefore, bool followRedirect, CurlWriteCallback writeCallback) const
825{
826 CURL* curlHandle;
827
828 curlHandle = curl_easy_init();
829 curl_easy_setopt(curlHandle, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
830
831 if (curlHandle != nullptr) {
832
833 curlSetSSLOptions(curlHandle);
834 initCurlOptionsForRetrieve(curlHandle, dataHolder, writeCallback, followRedirect);
835 long responseCode = 0;
836 CURLcode curlResultCode = CURL_LAST;
837
838 for (size_t hostIndex = 0; hostIndex < hostsPool.size() && (responseCode >= 400 || curlResultCode > 0); hostIndex++) {
839 std::string fullUrl = getFullUrlForRetrieval(curlHandle, path, metadata, timestamp, hostIndex);
840 curl_easy_setopt(curlHandle, CURLOPT_URL, fullUrl.c_str());
841
842 // Per host: the gate token is per endpoint (see appendGateToken).
843 curl_slist* option_list = nullptr;
844 initCurlHTTPHeaderOptionsForRetrieve(curlHandle, option_list, timestamp, headers, etag, createdNotAfter, createdNotBefore, fullUrl);
845
846 curlResultCode = CURL_perform(curlHandle);
847
848 if (curlResultCode != CURLE_OK) {
849 LOGP(alarm, "curl_easy_perform() failed: {}", curl_easy_strerror(curlResultCode));
850 } else {
851 curlResultCode = curl_easy_getinfo(curlHandle, CURLINFO_RESPONSE_CODE, &responseCode);
852 if ((curlResultCode == CURLE_OK) && (responseCode < 300)) {
853 curl_slist_free_all(option_list);
854 curl_easy_cleanup(curlHandle);
855 return true;
856 } else {
857 if (curlResultCode != CURLE_OK) {
858 LOGP(alarm, "invalid URL {}", fullUrl);
859 } else {
860 LOGP(alarm, "not found under link {}", fullUrl);
861 }
862 }
863 }
864 curl_slist_free_all(option_list);
865 }
866
867 curl_easy_cleanup(curlHandle);
868 }
869 return false;
870}
871
872TObject* CcdbApi::retrieve(std::string const& path, std::map<std::string, std::string> const& metadata,
873 long timestamp) const
874{
875 struct MemoryStruct chunk{
876 (char*)malloc(1) /*memory*/, 0 /*size*/
877 };
878
879 TObject* result = nullptr;
880
881 bool res = receiveToMemory((void*)&chunk, path, metadata, timestamp);
882
883 if (res) {
884 std::lock_guard<std::mutex> guard(gIOMutex);
885 TMessage mess(kMESS_OBJECT);
886 mess.SetBuffer(chunk.memory, chunk.size, kFALSE);
887 mess.SetReadMode();
888 mess.Reset();
889 result = (TObject*)(mess.ReadObjectAny(mess.GetClass()));
890 if (result == nullptr) {
891 LOGP(info, "couldn't retrieve the object {}", path);
892 }
893 }
894
895 free(chunk.memory);
896
897 return result;
898}
899
900std::string CcdbApi::generateFileName(const std::string& inp)
901{
902 // generate file name for the CCDB object (for now augment the input string by the timestamp)
903 std::string str = inp;
904 str.erase(std::remove_if(str.begin(), str.end(), ::isspace), str.end());
905 str = std::regex_replace(str, std::regex("::"), "-");
907 return str;
908}
909
910TObject* CcdbApi::retrieveFromTFile(std::string const& path, std::map<std::string, std::string> const& metadata,
911 long timestamp, std::map<std::string, std::string>* headers, std::string const& etag,
912 const std::string& createdNotAfter, const std::string& createdNotBefore) const
913{
914 return (TObject*)retrieveFromTFile(typeid(TObject), path, metadata, timestamp, headers, etag, createdNotAfter, createdNotBefore);
915}
916
917bool CcdbApi::retrieveBlob(std::string const& path, std::string const& targetdir, std::map<std::string, std::string> const& metadata,
918 long timestamp, bool preservePath, std::string const& localFileName, std::string const& createdNotAfter, std::string const& createdNotBefore, std::map<std::string, std::string>* outHeaders) const
919{
920
921 // we setup the target path for this blob
922 std::string fulltargetdir = targetdir + (preservePath ? ('/' + path) : "");
923
924 try {
926 } catch (std::exception e) {
927 LOGP(error, "Could not create local snapshot cache directory {}, reason: {}", fulltargetdir, e.what());
928 return false;
929 }
930
932 std::map<std::string, std::string> headers;
933 // avoid creating snapshot via loadFileToMemory itself
934 loadFileToMemory(buff, path, metadata, timestamp, &headers, "", createdNotAfter, createdNotBefore, false);
935 if ((headers.count("Error") != 0) || (buff.empty())) {
936 LOGP(error, "Unable to find object {}/{}, Aborting", path, timestamp);
937 return false;
938 }
939 // determine local filename --> use user given one / default -- or if empty string determine from content
940 auto getFileName = [&headers]() {
941 auto& s = headers["Content-Disposition"];
942 if (s != "") {
943 std::regex re("(.*;)filename=\"(.*)\"");
944 std::cmatch m;
945 if (std::regex_match(s.c_str(), m, re)) {
946 return m[2].str();
947 }
948 }
949 std::string backupname("ccdb-blob.bin");
950 LOG(error) << "Cannot determine original filename from Content-Disposition ... falling back to " << backupname;
951 return backupname;
952 };
953 auto filename = localFileName.size() > 0 ? localFileName : getFileName();
954 std::string targetpath = fulltargetdir + "/" + filename;
955 {
956 std::ofstream objFile(targetpath, std::ios::out | std::ofstream::binary);
957 std::copy(buff.begin(), buff.end(), std::ostreambuf_iterator<char>(objFile));
958 if (!objFile.good()) {
959 LOGP(error, "Unable to open local file {}, Aborting", targetpath);
960 return false;
961 }
962 }
963 CCDBQuery querysummary(path, metadata, timestamp);
964
965 updateMetaInformationInLocalFile(targetpath.c_str(), &headers, &querysummary);
966 if (outHeaders) {
967 *outHeaders = std::move(headers);
968 }
969 return true;
970}
971
972void CcdbApi::snapshot(std::string const& ccdbrootpath, std::string const& localDir, long timestamp) const
973{
974 // query all subpaths to ccdbrootpath
975 const auto allfolders = getAllFolders(ccdbrootpath);
976 std::map<std::string, std::string> metadata;
977 for (auto& folder : allfolders) {
978 retrieveBlob(folder, localDir, metadata, timestamp);
979 }
980}
981
982void* CcdbApi::extractFromTFile(TFile& file, TClass const* cl, const char* what)
983{
984 if (!cl) {
985 return nullptr;
986 }
987 auto object = file.GetObjectChecked(what, cl);
988 if (!object) {
989 // it could be that object was stored with previous convention
990 // where the classname was taken as key
991 std::string objectName(cl->GetName());
992 o2::utils::Str::trim(objectName);
993 object = file.GetObjectChecked(objectName.c_str(), cl);
994 LOG(warn) << "Did not find object under expected name " << what;
995 if (!object) {
996 return nullptr;
997 }
998 LOG(warn) << "Found object under deprecated name " << cl->GetName();
999 }
1000 auto result = object;
1001 // We need to handle some specific cases as ROOT ties them deeply
1002 // to the file they are contained in
1003 if (cl->InheritsFrom("TObject")) {
1004 // make a clone
1005 // detach from the file
1006 auto tree = dynamic_cast<TTree*>((TObject*)object);
1007 if (tree) {
1008 tree->LoadBaskets(0x1L << 32); // make tree memory based
1009 tree->SetDirectory(nullptr);
1010 result = tree;
1011 } else {
1012 auto h = dynamic_cast<TH1*>((TObject*)object);
1013 if (h) {
1014 h->SetDirectory(nullptr);
1015 result = h;
1016 }
1017 }
1018 }
1019 return result;
1020}
1021
1022void* CcdbApi::extractFromLocalFile(std::string const& filename, std::type_info const& tinfo, std::map<std::string, std::string>* headers) const
1023{
1024 if (!std::filesystem::exists(filename)) {
1025 LOG(error) << "Local snapshot " << filename << " not found \n";
1026 return nullptr;
1027 }
1028 std::lock_guard<std::mutex> guard(gIOMutex);
1029 auto tcl = tinfo2TClass(tinfo);
1030 TFile f(filename.c_str(), "READ");
1031 if (headers) {
1032 auto storedmeta = retrieveMetaInfo(f);
1033 if (storedmeta) {
1034 *headers = *storedmeta; // do a simple deep copy
1035 delete storedmeta;
1036 }
1037 if ((isSnapshotMode() || mPreferSnapshotCache) && headers->find("ETag") == headers->end()) { // generate dummy ETag to profit from the caching
1038 (*headers)["ETag"] = filename;
1039 }
1040 if (headers->find("fileSize") == headers->end()) {
1041 (*headers)["fileSize"] = fmt::format("{}", f.GetEND());
1042 }
1043 }
1044 return extractFromTFile(f, tcl);
1045}
1046
1047bool CcdbApi::initTGrid() const
1048{
1049 if (mNeedAlienToken && !gGrid) {
1050 static bool allowNoToken = getenv("ALICEO2_CCDB_NOTOKENCHECK") && atoi(getenv("ALICEO2_CCDB_NOTOKENCHECK"));
1051 if (!allowNoToken && !checkAlienToken()) {
1052 LOG(fatal) << "Alien Token Check failed - Please get an alien token before running with https CCDB endpoint, or alice-ccdb.cern.ch!";
1053 }
1054 TGrid::Connect("alien");
1055 static bool errorShown = false;
1056 if (!gGrid && errorShown == false) {
1057 if (allowNoToken) {
1058 LOG(error) << "TGrid::Connect returned nullptr. May be due to missing alien token";
1059 } else {
1060 LOG(fatal) << "TGrid::Connect returned nullptr. May be due to missing alien token";
1061 }
1062 errorShown = true;
1063 }
1064 }
1065 return gGrid != nullptr;
1066}
1067
1068void* CcdbApi::downloadFilesystemContent(std::string const& url, std::type_info const& tinfo, std::map<std::string, std::string>* headers) const
1069{
1070 if ((url.find("alien:/", 0) != std::string::npos) && !initTGrid()) {
1071 return nullptr;
1072 }
1073 std::lock_guard<std::mutex> guard(gIOMutex);
1074 auto memfile = TMemFile::Open(url.c_str(), "OPEN");
1075 if (memfile) {
1076 auto cl = tinfo2TClass(tinfo);
1077 auto content = extractFromTFile(*memfile, cl);
1078 if (headers && headers->find("fileSize") == headers->end()) {
1079 (*headers)["fileSize"] = fmt::format("{}", memfile->GetEND());
1080 }
1081 delete memfile;
1082 return content;
1083 }
1084 return nullptr;
1085}
1086
1087void* CcdbApi::interpretAsTMemFileAndExtract(char* contentptr, size_t contentsize, std::type_info const& tinfo)
1088{
1089 void* result = nullptr;
1090 Int_t previousErrorLevel = gErrorIgnoreLevel;
1091 gErrorIgnoreLevel = kFatal;
1092 std::lock_guard<std::mutex> guard(gIOMutex);
1093 TMemFile memFile("name", contentptr, contentsize, "READ");
1094 gErrorIgnoreLevel = previousErrorLevel;
1095 if (!memFile.IsZombie()) {
1096 auto tcl = tinfo2TClass(tinfo);
1097 result = extractFromTFile(memFile, tcl);
1098 if (!result) {
1099 LOG(error) << o2::utils::Str::concat_string("Couldn't retrieve object corresponding to ", tcl->GetName(), " from TFile");
1100 }
1101 memFile.Close();
1102 }
1103 return result;
1104}
1105
1106// navigate sequence of URLs until TFile content is found; object is extracted and returned
1107void* CcdbApi::navigateURLsAndRetrieveContent(CURL* curl_handle, std::string const& url, std::type_info const& tinfo, std::map<std::string, std::string>* headers) const
1108{
1109 // a global internal data structure that can be filled with HTTP header information
1110 // static --> to avoid frequent alloc/dealloc as optimization
1111 // not sure if thread_local takes away that benefit
1112 static thread_local std::multimap<std::string, std::string> headerData;
1113
1114 // let's see first of all if the url is something specific that curl cannot handle
1115 if ((url.find("alien:/", 0) != std::string::npos) || (url.find("file:/", 0) != std::string::npos)) {
1116 return downloadFilesystemContent(url, tinfo, headers);
1117 }
1118 // add other final cases here
1119 // example root://
1120
1121 // otherwise make an HTTP/CURL request
1122 // specify URL to get
1123 curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
1124
1125 MemoryStruct chunk{(char*)malloc(1), 0};
1126 initCurlOptionsForRetrieve(curl_handle, (void*)&chunk, WriteMemoryCallback, false);
1127
1128 curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, header_map_callback<decltype(headerData)>);
1129 headerData.clear();
1130 curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, (void*)&headerData);
1131
1132 curlSetSSLOptions(curl_handle);
1133
1134 auto res = CURL_perform(curl_handle);
1135 long response_code = -1;
1136 void* content = nullptr;
1137 bool errorflag = false;
1138 if (res == CURLE_OK && curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &response_code) == CURLE_OK) {
1139 if (headers) {
1140 for (auto& p : headerData) {
1141 (*headers)[p.first] = p.second;
1142 }
1143 }
1144 if (200 <= response_code && response_code < 300) {
1145 // good response and the content is directly provided and should have been dumped into "chunk"
1146 content = interpretAsTMemFileAndExtract(chunk.memory, chunk.size, tinfo);
1147 if (headers && headers->find("fileSize") == headers->end()) {
1148 (*headers)["fileSize"] = fmt::format("{}", chunk.size);
1149 }
1150 } else if (response_code == 304) {
1151 // this means the object exist but I am not serving
1152 // it since it's already in your possession
1153
1154 // there is nothing to be done here
1155 LOGP(debug, "Object exists but I am not serving it since it's already in your possession");
1156 }
1157 // this is a more general redirection
1158 else if (300 <= response_code && response_code < 400) {
1159 // we try content locations in order of appearance until one succeeds
1160 // 1st: The "Location" field
1161 // 2nd: Possible "Content-Location" fields - Location field
1162
1163 // some locations are relative to the main server so we need to fix/complement them
1164 auto complement_Location = [this](std::string const& loc) {
1165 if (loc[0] == '/') {
1166 // if it's just a path (noticed by trailing '/' we prepend the server url
1167 return getURL() + loc;
1168 }
1169 return loc;
1170 };
1171
1172 std::vector<std::string> locs;
1173 auto iter = headerData.find("Location");
1174 if (iter != headerData.end()) {
1175 locs.push_back(complement_Location(iter->second));
1176 }
1177 // add alternative locations (not yet included)
1178 auto iter2 = headerData.find("Content-Location");
1179 if (iter2 != headerData.end()) {
1180 auto range = headerData.equal_range("Content-Location");
1181 for (auto it = range.first; it != range.second; ++it) {
1182 if (std::find(locs.begin(), locs.end(), it->second) == locs.end()) {
1183 locs.push_back(complement_Location(it->second));
1184 }
1185 }
1186 }
1187 for (auto& l : locs) {
1188 if (l.size() > 0) {
1189 LOG(debug) << "Trying content location " << l;
1190 content = navigateURLsAndRetrieveContent(curl_handle, l, tinfo, headers);
1191 if (content /* or other success marker in future */) {
1192 break;
1193 }
1194 }
1195 }
1196 } else if (response_code == 404) {
1197 LOG(error) << "Requested resource does not exist: " << url;
1198 errorflag = true;
1199 } else {
1200 LOG(error) << "Error in fetching object " << url << ", curl response code:" << response_code;
1201 errorflag = true;
1202 }
1203 // cleanup
1204 if (chunk.memory != nullptr) {
1205 free(chunk.memory);
1206 }
1207 } else {
1208 LOGP(alarm, "Curl request to {} failed with result {}, response code: {}", url, int(res), response_code);
1209 errorflag = true;
1210 }
1211 // indicate that an error occurred ---> used by caching layers (such as CCDBManager)
1212 if (errorflag && headers) {
1213 (*headers)["Error"] = "An error occurred during retrieval";
1214 }
1215 return content;
1216}
1217
1218void* CcdbApi::retrieveFromTFile(std::type_info const& tinfo, std::string const& path,
1219 std::map<std::string, std::string> const& metadata, long timestamp,
1220 std::map<std::string, std::string>* headers, std::string const& etag,
1221 const std::string& createdNotAfter, const std::string& createdNotBefore) const
1222{
1223 if (!mSnapshotCachePath.empty()) {
1224 // protect this sensitive section by a multi-process named semaphore
1225 auto semaphore_barrier = std::make_unique<CCDBSemaphore>(mSnapshotCachePath, path);
1226 std::string logfile = mSnapshotCachePath + "/log";
1227 std::fstream out(logfile, ios_base::out | ios_base::app);
1228 if (out.is_open()) {
1229 out << "CCDB-access[" << getpid() << "] of " << mUniqueAgentID << " to " << path << " timestamp " << timestamp << "\n";
1230 }
1231 auto snapshotfile = getSnapshotFile(mSnapshotCachePath, path);
1232 bool snapshoting = false;
1233 if (!std::filesystem::exists(snapshotfile)) {
1234 snapshoting = true;
1235 out << "CCDB-access[" << getpid() << "] ... " << mUniqueAgentID << " downloading to snapshot " << snapshotfile << "\n";
1236 // if file not already here and valid --> snapshot it
1237 if (!retrieveBlob(path, mSnapshotCachePath, metadata, timestamp)) {
1238 out << "CCDB-access[" << getpid() << "] ... " << mUniqueAgentID << " failed to create directory for " << snapshotfile << "\n";
1239 }
1240 } else {
1241 out << "CCDB-access[" << getpid() << "] ... " << mUniqueAgentID << "serving from local snapshot " << snapshotfile << "\n";
1242 }
1243
1244 auto res = extractFromLocalFile(snapshotfile, tinfo, headers);
1245 if (!snapshoting) { // if snapshot was created at this call, the log was already done
1246 logReading(path, timestamp, headers, "retrieve from snapshot");
1247 }
1248 return res;
1249 }
1250
1251 // normal mode follows
1252
1253 CURL* curl_handle = curl_easy_init();
1254 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
1255 std::string fullUrl = getFullUrlForRetrieval(curl_handle, path, metadata, timestamp); // todo check if function still works correctly in case mInSnapshotMode
1256 // if we are in snapshot mode we can simply open the file; extract the object and return
1257 if (mInSnapshotMode) {
1258 auto res = extractFromLocalFile(fullUrl, tinfo, headers);
1259 if (res) {
1260 logReading(path, timestamp, headers, "retrieve from snapshot");
1261 }
1262 return res;
1263 }
1264
1265 curl_slist* option_list = nullptr;
1266 initCurlHTTPHeaderOptionsForRetrieve(curl_handle, option_list, timestamp, headers, etag, createdNotAfter, createdNotBefore, fullUrl);
1267 auto content = navigateURLsAndRetrieveContent(curl_handle, fullUrl, tinfo, headers);
1268
1269 for (size_t hostIndex = 1; hostIndex < hostsPool.size() && !(content); hostIndex++) {
1270 fullUrl = getFullUrlForRetrieval(curl_handle, path, metadata, timestamp, hostIndex);
1271 // Per host: the gate token is per endpoint (see appendGateToken).
1272 curl_slist_free_all(option_list);
1273 option_list = nullptr;
1274 initCurlHTTPHeaderOptionsForRetrieve(curl_handle, option_list, timestamp, headers, etag, createdNotAfter, createdNotBefore, fullUrl);
1275 content = navigateURLsAndRetrieveContent(curl_handle, fullUrl, tinfo, headers);
1276 }
1277 if (content) {
1278 logReading(path, timestamp, headers, "retrieve");
1279 }
1280 curl_slist_free_all(option_list);
1281 curl_easy_cleanup(curl_handle);
1282 return content;
1283}
1284
1285size_t CurlWrite_CallbackFunc_StdString2(void* contents, size_t size, size_t nmemb, std::string* s)
1286{
1287 size_t newLength = size * nmemb;
1288 size_t oldLength = s->size();
1289 try {
1290 s->resize(oldLength + newLength);
1291 } catch (std::bad_alloc& e) {
1292 LOG(error) << "memory error when getting data from CCDB";
1293 return 0;
1294 }
1295
1296 std::copy((char*)contents, (char*)contents + newLength, s->begin() + oldLength);
1297 return size * nmemb;
1298}
1299
1300std::string CcdbApi::list(std::string const& path, bool latestOnly, std::string const& returnFormat, long createdNotAfter, long createdNotBefore) const
1301{
1302 CURL* curl;
1303 CURLcode res = CURL_LAST;
1304 std::string result;
1305
1306 curl = curl_easy_init();
1307 if (curl != nullptr) {
1308 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWrite_CallbackFunc_StdString2);
1309 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);
1310 curl_easy_setopt(curl, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
1311
1312 curlSetSSLOptions(curl);
1313
1314 std::string fullUrl;
1315 // Perform the request, res will get the return code
1316 for (size_t hostIndex = 0; hostIndex < hostsPool.size() && res != CURLE_OK; hostIndex++) {
1317 fullUrl = getHostUrl(hostIndex);
1318 fullUrl += latestOnly ? "/latest/" : "/browse/";
1319 fullUrl += path;
1320 curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str());
1321
1322 // Per host: the gate token is per endpoint (see appendGateToken).
1323 struct curl_slist* headers = nullptr;
1324 headers = curl_slist_append(headers, (std::string("Accept: ") + returnFormat).c_str());
1325 headers = curl_slist_append(headers, (std::string("Content-Type: ") + returnFormat).c_str());
1326 if (createdNotAfter >= 0) {
1327 headers = curl_slist_append(headers, ("If-Not-After: " + std::to_string(createdNotAfter)).c_str());
1328 }
1329 if (createdNotBefore >= 0) {
1330 headers = curl_slist_append(headers, ("If-Not-Before: " + std::to_string(createdNotBefore)).c_str());
1331 }
1332 headers = appendGateToken(headers, fullUrl);
1333 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
1334
1335 res = CURL_perform(curl);
1336 if (res != CURLE_OK) {
1337 LOGP(alarm, "CURL_perform() failed: {}", curl_easy_strerror(res));
1338 }
1339 curl_slist_free_all(headers);
1340 }
1341 curl_easy_cleanup(curl);
1342 }
1343
1344 return result;
1345}
1346
1347std::string CcdbApi::getTimestampString(long timestamp) const
1348{
1349 stringstream ss;
1350 ss << timestamp;
1351 return ss.str();
1352}
1353
1354void CcdbApi::deleteObject(std::string const& path, long timestamp) const
1355{
1356 CURL* curl;
1357 CURLcode res;
1358 long timestampLocal = timestamp == -1 ? getCurrentTimestamp() : timestamp;
1359
1360 curl = curl_easy_init();
1361 if (curl != nullptr) {
1362 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
1363 curl_easy_setopt(curl, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
1364 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
1365 curlSetSSLOptions(curl);
1366
1367 for (size_t hostIndex = 0; hostIndex < hostsPool.size(); hostIndex++) {
1368 // Inside the loop: hoisted out, the stream accumulates and the second
1369 // host's URL is the first with the second appended.
1370 stringstream fullUrl;
1371 fullUrl << getHostUrl(hostIndex) << "/" << path << "/" << timestampLocal;
1372 curl_easy_setopt(curl, CURLOPT_URL, fullUrl.str().c_str());
1373
1374 // A DELETE is a write, so it needs the gate token as storing does -- per
1375 // host, since the token is per endpoint (see appendGateToken).
1376 struct curl_slist* list = appendGateToken(nullptr, fullUrl.str());
1377 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
1378
1379 // Perform the request, res will get the return code
1380 res = CURL_perform(curl);
1381 if (res != CURLE_OK) {
1382 LOGP(alarm, "CURL_perform() failed: {}", curl_easy_strerror(res));
1383 }
1384 curl_slist_free_all(list);
1385 }
1386 // After the loop, not inside it: cleaning up per host left every later
1387 // iteration using a freed handle.
1388 curl_easy_cleanup(curl);
1389 }
1390}
1391
1392void CcdbApi::truncate(std::string const& path) const
1393{
1394 CURL* curl;
1395 CURLcode res;
1396 for (size_t i = 0; i < hostsPool.size(); i++) {
1397 // Declared inside the loop: a stringstream hoisted out of it accumulates,
1398 // so the second host's URL would be the first one with the second appended
1399 // to it. Latent until now -- every caller used a single-host pool.
1400 stringstream fullUrl;
1401 std::string url = getHostUrl(i);
1402 fullUrl << url << "/truncate/" << path;
1403
1404 curl = curl_easy_init();
1405 curl_easy_setopt(curl, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
1406 if (curl != nullptr) {
1407 curl_easy_setopt(curl, CURLOPT_URL, fullUrl.str().c_str());
1408
1409 // Truncating is a write, so it needs the gate token exactly as storing
1410 // does. This was the one write path left without it, which a broker
1411 // answers 401 -- failing every CCDB suite in their teardown, since each
1412 // one truncates the path it just wrote.
1413 struct curl_slist* list = appendGateToken(nullptr, fullUrl.str());
1414 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
1415 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
1416 curlSetSSLOptions(curl);
1417
1418 // Perform the request, res will get the return code
1419 res = CURL_perform(curl);
1420 if (res != CURLE_OK) {
1421 LOGP(alarm, "CURL_perform() failed: {}", curl_easy_strerror(res));
1422 }
1423 curl_easy_cleanup(curl);
1424 curl_slist_free_all(list);
1425 }
1426 }
1427}
1428
1429size_t write_data(void*, size_t size, size_t nmemb, void*)
1430{
1431 return size * nmemb;
1432}
1433
1435{
1436 CURL* curl;
1437 CURLcode res = CURL_LAST;
1438 bool result = false;
1439
1440 curl = curl_easy_init();
1441 curl_easy_setopt(curl, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
1442 if (curl) {
1443 // NOTE: mUrl, not getHostUrl(hostIndex), even though hostIndex is unused.
1444 // For a failover setup mUrl is the whole comma-separated list, which curl
1445 // rejects as malformed, so every multi-host instance reports itself
1446 // unreachable however healthy its hosts are -- and testCcdbApiMultipleUrls,
1447 // whose cases are gated on this, is skipped rather than run.
1448 //
1449 // Fixing it is a separate change: the suite then runs for the first time
1450 // and its storeAndRetrieve fails, so the multi-host store/retrieve path
1451 // needs looking at before this can be corrected. Callers outside the tests
1452 // are affected too -- HMPID/PedestalsCalculationSpec sets mWriteToDB from
1453 // this, and TPC workflows branch on it.
1454 for (size_t hostIndex = 0; hostIndex < hostsPool.size() && res != CURLE_OK; hostIndex++) {
1455 curl_easy_setopt(curl, CURLOPT_URL, mUrl.data());
1456 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
1457 curlSetSSLOptions(curl);
1458 res = CURL_perform(curl);
1459 result = (res == CURLE_OK);
1460 }
1461
1462 /* always cleanup */
1463 curl_easy_cleanup(curl);
1464 }
1465 return result;
1466}
1467
1468std::vector<std::string> CcdbApi::parseSubFolders(std::string const& reply) const
1469{
1470 // this needs some text filtering
1471 // go through reply line by line until we see "SubFolders:"
1472 std::stringstream ss(reply.c_str());
1473 std::string line;
1474 std::vector<std::string> folders;
1475
1476 size_t numberoflines = std::count(reply.begin(), reply.end(), '\n');
1477 bool inSubFolderSection = false;
1478
1479 for (size_t linenumber = 0; linenumber < numberoflines; ++linenumber) {
1480 std::getline(ss, line);
1481 if (inSubFolderSection && line.size() > 0) {
1482 // remove all white space
1483 folders.push_back(sanitizeObjectName(line));
1484 }
1485
1486 if (line.compare("Subfolders:") == 0) {
1487 inSubFolderSection = true;
1488 }
1489 }
1490 return folders;
1491}
1492
1493namespace
1494{
1495size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata)
1496{
1497 auto* headers = static_cast<std::vector<std::string>*>(userdata);
1498 auto header = std::string(buffer, size * nitems);
1499 headers->emplace_back(std::string(header.data()));
1500 return size * nitems;
1501}
1502} // namespace
1503
1504bool stdmap_to_jsonfile(std::map<std::string, std::string> const& meta, std::string const& filename)
1505{
1506
1507 // create directory structure if necessary
1508 auto p = std::filesystem::path(filename).parent_path();
1509 if (!std::filesystem::exists(p)) {
1510 std::filesystem::create_directories(p);
1511 }
1512
1513 rapidjson::StringBuffer buffer;
1514 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
1515 writer.StartObject();
1516 for (const auto& pair : meta) {
1517 writer.Key(pair.first.c_str());
1518 writer.String(pair.second.c_str());
1519 }
1520 writer.EndObject();
1521
1522 // Write JSON to file
1523 std::ofstream file(filename);
1524 if (file.is_open()) {
1525 file << buffer.GetString();
1526 file.close();
1527 } else {
1528 return false;
1529 }
1530 return true;
1531}
1532
1533bool jsonfile_to_stdmap(std::map<std::string, std::string>& meta, std::string const& filename)
1534{
1535 // Read JSON from file
1536 std::ifstream file(filename);
1537 if (!file.is_open()) {
1538 std::cerr << "Failed to open file for reading." << std::endl;
1539 return false;
1540 }
1541
1542 std::string jsonStr((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
1543
1544 // Parse JSON
1545 rapidjson::Document document;
1546 document.Parse(jsonStr.c_str());
1547
1548 if (document.HasParseError()) {
1549 std::cerr << "Error parsing JSON" << std::endl;
1550 return false;
1551 }
1552
1553 // Convert JSON to std::map
1554 for (auto itr = document.MemberBegin(); itr != document.MemberEnd(); ++itr) {
1555 meta[itr->name.GetString()] = itr->value.GetString();
1556 }
1557 return true;
1558}
1559
1560std::map<std::string, std::string> CcdbApi::retrieveHeaders(std::string const& path, std::map<std::string, std::string> const& metadata, long timestamp) const
1561{
1562 // lambda that actually does the call to the CCDB server
1563 auto do_remote_header_call = [this, &path, &metadata, timestamp]() -> std::map<std::string, std::string> {
1564 CURL* curl = curl_easy_init();
1565 CURLcode res = CURL_LAST;
1566 std::string fullUrl = getFullUrlForRetrieval(curl, path, metadata, timestamp);
1567 std::map<std::string, std::string> headers;
1568
1569 if (curl != nullptr) {
1570 struct curl_slist* list = nullptr;
1571 list = curl_slist_append(list, ("If-None-Match: " + std::to_string(timestamp)).c_str());
1572 list = appendGateToken(list, fullUrl);
1573
1574 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
1575
1576 /* get us the resource without a body! */
1577 curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
1578 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
1579 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_map_callback<>);
1580 curl_easy_setopt(curl, CURLOPT_HEADERDATA, &headers);
1581 curl_easy_setopt(curl, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
1582
1583 curlSetSSLOptions(curl);
1584
1585 // Perform the request, res will get the return code
1586 long httpCode = 404;
1587 CURLcode getCodeRes = CURL_LAST;
1588 for (size_t hostIndex = 0; hostIndex < hostsPool.size() && (httpCode >= 400 || res > 0 || getCodeRes > 0); hostIndex++) {
1589 curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str());
1590 res = CURL_perform(curl);
1591 if (res != CURLE_OK && res != CURLE_UNSUPPORTED_PROTOCOL) {
1592 // We take out the unsupported protocol error because we are only querying
1593 // header info which is returned in any case. Unsupported protocol error
1594 // occurs sometimes because of redirection to alien for blobs.
1595 LOG(error) << "CURL_perform() failed: " << curl_easy_strerror(res);
1596 }
1597 getCodeRes = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
1598 }
1599 if (httpCode == 404) {
1600 headers.clear();
1601 }
1602 curl_easy_cleanup(curl);
1603 }
1604 return headers;
1605 };
1606
1607 if (!mSnapshotCachePath.empty()) {
1608 // protect this sensitive section by a multi-process named semaphore
1609 auto semaphore_barrier = std::make_unique<CCDBSemaphore>(mSnapshotCachePath + std::string("_headers"), path);
1610
1611 std::string logfile = mSnapshotCachePath + "/log";
1612 std::fstream out(logfile, ios_base::out | ios_base::app);
1613 if (out.is_open()) {
1614 out << "CCDB-header-access[" << getpid() << "] of " << mUniqueAgentID << " to " << path << " timestamp " << timestamp << "\n";
1615 }
1616 auto snapshotfile = getSnapshotFile(mSnapshotCachePath, path + "/" + std::to_string(timestamp), "header.json");
1617 if (!std::filesystem::exists(snapshotfile)) {
1618 out << "CCDB-header-access[" << getpid() << "] ... " << mUniqueAgentID << " storing to snapshot " << snapshotfile << "\n";
1619
1620 // if file not already here and valid --> snapshot it
1621 auto meta = do_remote_header_call();
1622
1623 // cache the result
1624 if (!stdmap_to_jsonfile(meta, snapshotfile)) {
1625 LOG(warn) << "Failed to cache the header information to disc";
1626 }
1627 return meta;
1628 } else {
1629 out << "CCDB-header-access[" << getpid() << "] ... " << mUniqueAgentID << "serving from local snapshot " << snapshotfile << "\n";
1630 std::map<std::string, std::string> meta;
1631 if (!jsonfile_to_stdmap(meta, snapshotfile)) {
1632 LOG(warn) << "Failed to read cached information from disc";
1633 return do_remote_header_call();
1634 }
1635 return meta;
1636 }
1637 }
1638 return do_remote_header_call();
1639}
1640
1641bool CcdbApi::getCCDBEntryHeaders(std::string const& url, std::string const& etag, std::vector<std::string>& headers, const std::string& agentID)
1642{
1643 auto curl = curl_easy_init();
1644 headers.clear();
1645 if (!curl) {
1646 return true;
1647 }
1648
1649 struct curl_slist* list = nullptr;
1650 list = curl_slist_append(list, ("If-None-Match: " + etag).c_str());
1651 list = appendGateToken(list, url);
1652
1653 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
1654
1655 curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
1656 /* get us the resource without a body! */
1657 curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
1658 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
1659 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
1660 curl_easy_setopt(curl, CURLOPT_HEADERDATA, &headers);
1661 if (!agentID.empty()) {
1662 curl_easy_setopt(curl, CURLOPT_USERAGENT, agentID.c_str());
1663 }
1664
1665 curlSetSSLOptions(curl);
1666
1667 /* Perform the request */
1668 curl_easy_perform(curl);
1669 long http_code = 404;
1670 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
1671 if (http_code == 304) {
1672 return false;
1673 }
1674 return true;
1675}
1676
1677void CcdbApi::parseCCDBHeaders(std::vector<std::string> const& headers, std::vector<std::string>& pfns, std::string& etag)
1678{
1679 static std::string etagHeader = "ETag: ";
1680 static std::string locationHeader = "Content-Location: ";
1681 // Trimmed: `headers` holds raw header lines, CRLF and all, and the etag goes
1682 // straight back out as an If-None-Match request header.
1683 for (auto h : headers) {
1684 if (h.find(etagHeader) == 0) {
1685 etag = trimHeaderValue(std::string_view(h).substr(etagHeader.size()));
1686 } else if (h.find(locationHeader) == 0) {
1687 pfns.emplace_back(trimHeaderValue(std::string_view(h).substr(locationHeader.size())));
1688 }
1689 }
1690}
1691
1693{
1694 auto object = file.GetObjectChecked(CCDBQUERY_ENTRY, TClass::GetClass(typeid(o2::ccdb::CCDBQuery)));
1695 if (object) {
1696 return static_cast<CCDBQuery*>(object);
1697 }
1698 return nullptr;
1699}
1700
1701std::map<std::string, std::string>* CcdbApi::retrieveMetaInfo(TFile& file)
1702{
1703 auto object = file.GetObjectChecked(CCDBMETA_ENTRY, TClass::GetClass(typeid(std::map<std::string, std::string>)));
1704 if (object) {
1705 return static_cast<std::map<std::string, std::string>*>(object);
1706 }
1707 return nullptr;
1708}
1709
1710namespace
1711{
1712void traverseAndFillFolders(CcdbApi const& api, std::string const& top, std::vector<std::string>& folders)
1713{
1714 // LOG(info) << "Querying " << top;
1715 auto reply = api.list(top);
1716 folders.emplace_back(top);
1717 // LOG(info) << reply;
1718 auto subfolders = api.parseSubFolders(reply);
1719 if (subfolders.size() > 0) {
1720 // LOG(info) << subfolders.size() << " folders in " << top;
1721 for (auto& sub : subfolders) {
1722 traverseAndFillFolders(api, sub, folders);
1723 }
1724 } else {
1725 // LOG(info) << "NO subfolders in " << top;
1726 }
1727}
1728} // namespace
1729
1730std::vector<std::string> CcdbApi::getAllFolders(std::string const& top) const
1731{
1732 std::vector<std::string> folders;
1733 traverseAndFillFolders(*this, top, folders);
1734 return folders;
1735}
1736
1737TClass* CcdbApi::tinfo2TClass(std::type_info const& tinfo)
1738{
1739 TClass* cl = TClass::GetClass(tinfo);
1740 if (!cl) {
1741 throw std::runtime_error(fmt::format("Could not retrieve ROOT dictionary for type {}, aborting", tinfo.name()));
1742 return nullptr;
1743 }
1744 return cl;
1745}
1746
1747int CcdbApi::updateMetadata(std::string const& path, std::map<std::string, std::string> const& metadata, long timestamp, std::string const& id, long newEOV)
1748{
1749 int ret = -1;
1750 CURL* curl = curl_easy_init();
1751 curl_easy_setopt(curl, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
1752 if (curl != nullptr) {
1753 CURLcode res;
1754 for (size_t hostIndex = 0; hostIndex < hostsPool.size(); hostIndex++) {
1755 // Inside the loop: hoisted out, the stream accumulates and the second
1756 // host's URL is the first with the second appended.
1757 stringstream fullUrl;
1758 fullUrl << getHostUrl(hostIndex) << "/" << path << "/" << timestamp;
1759 if (newEOV > 0) {
1760 fullUrl << "/" << newEOV;
1761 }
1762 if (!id.empty()) {
1763 fullUrl << "/" << id;
1764 }
1765 fullUrl << "?";
1766
1767 for (auto& kv : metadata) {
1768 std::string mfirst = kv.first;
1769 std::string msecond = kv.second;
1770 // same trick for the metadata as for the object type
1771 char* mfirstEncoded = curl_easy_escape(curl, mfirst.c_str(), mfirst.size());
1772 char* msecondEncoded = curl_easy_escape(curl, msecond.c_str(), msecond.size());
1773 fullUrl << std::string(mfirstEncoded) + "=" + std::string(msecondEncoded) + "&";
1774 curl_free(mfirstEncoded);
1775 curl_free(msecondEncoded);
1776 }
1777
1778 if (curl != nullptr) {
1779 LOG(debug) << "passing to curl: " << fullUrl.str();
1780 curl_easy_setopt(curl, CURLOPT_URL, fullUrl.str().c_str());
1781 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); // make sure we use PUT
1782 curl_easy_setopt(curl, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
1783 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
1784 // A PUT is a write, so it needs the gate token as storing does -- per
1785 // host, since the token is per endpoint (see appendGateToken).
1786 struct curl_slist* list = appendGateToken(nullptr, fullUrl.str());
1787 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
1788 curlSetSSLOptions(curl);
1789
1790 // Perform the request, res will get the return code
1791 res = CURL_perform(curl);
1792 if (res != CURLE_OK) {
1793 LOGP(alarm, "CURL_perform() failed: {}, code: {}", curl_easy_strerror(res), int(res));
1794 ret = int(res);
1795 } else {
1796 ret = 0;
1797 }
1798 curl_slist_free_all(list);
1799 }
1800 }
1801 // After the loop, not inside it: cleaning up per host left every later
1802 // iteration using a freed handle.
1803 curl_easy_cleanup(curl);
1804 }
1805 return ret;
1806}
1807
1808void CcdbApi::initHostsPool(std::string hosts)
1809{
1810 hostsPool.clear();
1811 auto splitted = hosts | std::views::transform([](char c) { return (c == ';') ? ',' : c; }) | std::views::split(',');
1812 for (auto&& part : splitted) {
1813 hostsPool.emplace_back(part.begin(), part.end());
1814 }
1815}
1816
1817std::string CcdbApi::getHostUrl(int hostIndex) const
1818{
1819 return hostsPool.at(hostIndex);
1820}
1821
1822void CcdbApi::scheduleDownload(RequestContext& requestContext, size_t* requestCounter) const
1823{
1824 auto data = new DownloaderRequestData(); // Deleted in transferFinished of CCDBDownloader.cxx
1825 data->hoPair.object = &requestContext.dest;
1826
1827 std::function<bool(std::string)> localContentCallback = [this, &requestContext](std::string url) {
1828 return this->loadLocalContentToMemory(requestContext.dest, url);
1829 };
1830
1831 auto writeCallback = [](void* contents, size_t size, size_t nmemb, void* chunkptr) {
1832 auto& ho = *static_cast<HeaderObjectPair_t*>(chunkptr);
1833 auto& chunk = *ho.object;
1834 size_t realsize = size * nmemb, sz = 0;
1835 ho.counter++;
1836 try {
1837 if (chunk.capacity() < chunk.size() + realsize) {
1838 // estimate headers size when converted to annotated text string
1839 const char hannot[] = "header";
1840 size_t hsize = getFlatHeaderSize(ho.header);
1841 auto cl = ho.header.find("Content-Length");
1842 if (cl != ho.header.end()) {
1843 size_t sizeFromHeader = std::stol(cl->second);
1844 sz = hsize + std::max(chunk.size() * (sizeFromHeader ? 1 : 2) + realsize, sizeFromHeader);
1845 } else {
1846 sz = hsize + std::max(chunk.size() * 2, chunk.size() + realsize);
1847 // LOGP(debug, "SIZE IS NOT IN HEADER, allocate {}", sz);
1848 }
1849 chunk.reserve(sz);
1850 }
1851 char* contC = (char*)contents;
1852 chunk.insert(chunk.end(), contC, contC + realsize);
1853 } catch (std::exception e) {
1854 // LOGP(alarm, "failed to reserve {} bytes in CURL write callback (realsize = {}): {}", sz, realsize, e.what());
1855 realsize = 0;
1856 }
1857 return realsize;
1858 };
1859
1860 CURL* curl_handle = curl_easy_init();
1861 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
1862 std::string fullUrl = getFullUrlForRetrieval(curl_handle, requestContext.path, requestContext.metadata, requestContext.timestamp);
1863
1864 data->headers = &requestContext.headers;
1865 data->hosts = hostsPool;
1866 data->path = requestContext.path;
1867 data->timestamp = requestContext.timestamp;
1868 data->localContentCallback = localContentCallback;
1869 data->userAgent = mUniqueAgentID;
1870
1871 // One header list per host, built HERE because this is where the gate-token
1872 // table is visible -- the downloader only indexes them. A single shared list
1873 // sent the first host's token to every host it failed over to, which a broker
1874 // answers 401: the failover then retrieved nothing while looking like a
1875 // network failure (testCcdbApi multi_host_test).
1876 data->optionsLists.reserve(hostsPool.size());
1877 for (size_t hostIndex = 0; hostIndex < hostsPool.size(); hostIndex++) {
1878 curl_slist* hostOptions = nullptr;
1879 const std::string hostUrl = getFullUrlForRetrieval(curl_handle, requestContext.path, requestContext.metadata,
1880 requestContext.timestamp, hostIndex);
1881 initCurlHTTPHeaderOptionsForRetrieve(curl_handle, hostOptions, requestContext.timestamp, &requestContext.headers,
1882 requestContext.etag, requestContext.createdNotAfter, requestContext.createdNotBefore,
1883 hostUrl);
1884 data->optionsLists.push_back(hostOptions);
1885 }
1886 // initCurlHTTPHeaderOptionsForRetrieve sets CURLOPT_HTTPHEADER as a side
1887 // effect, so the handle currently points at the LAST host's list. Point it
1888 // back at host 0, which is the one this transfer starts with.
1889 if (!data->optionsLists.empty()) {
1890 curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, data->optionsLists.front());
1891 }
1892
1893 curl_easy_setopt(curl_handle, CURLOPT_URL, fullUrl.c_str());
1894 initCurlOptionsForRetrieve(curl_handle, (void*)(&data->hoPair), writeCallback, false);
1895 curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, header_map_callback<decltype(data->hoPair.header)>);
1896 curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, (void*)&(data->hoPair.header));
1897 curl_easy_setopt(curl_handle, CURLOPT_PRIVATE, (void*)data);
1898 curlSetSSLOptions(curl_handle);
1899
1900 asynchPerform(curl_handle, requestCounter);
1901}
1902
1903std::string CcdbApi::determineSemaphoreName(std::string const& basedir, std::string const& ccdbpath)
1904{
1905 std::hash<std::string> hasher;
1906 std::string semhashedstring = "aliceccdb" + std::to_string(hasher(basedir + ccdbpath)).substr(0, 16);
1907 return semhashedstring;
1908}
1909
1910boost::interprocess::named_semaphore* CcdbApi::createNamedSemaphore(std::string const& path) const
1911{
1912 std::string semhashedstring = determineSemaphoreName(mSnapshotCachePath, path);
1913 // LOG(info) << "Creating named semaphore with name " << semhashedstring.c_str();
1914 try {
1915 return new boost::interprocess::named_semaphore(boost::interprocess::open_or_create_t{}, semhashedstring.c_str(), 1);
1916 } catch (std::exception e) {
1917 LOG(warn) << "Exception occurred during CCDB (cache) semaphore setup; Continuing without";
1918 return nullptr;
1919 }
1920}
1921
1922void CcdbApi::releaseNamedSemaphore(boost::interprocess::named_semaphore* sem, std::string const& path) const
1923{
1924 if (sem) {
1925 sem->post();
1926 if (sem->try_wait()) { // if nobody else is waiting remove the semaphore resource
1927 sem->post();
1928 boost::interprocess::named_semaphore::remove(determineSemaphoreName(mSnapshotCachePath, path).c_str());
1929 }
1930 }
1931}
1932
1933bool CcdbApi::removeSemaphore(std::string const& semaname, bool remove)
1934{
1935 // removes a given named semaphore from the system
1936 try {
1937 boost::interprocess::named_semaphore semaphore(boost::interprocess::open_only, semaname.c_str());
1938 std::cout << "Found CCDB semaphore: " << semaname << "\n";
1939 if (remove) {
1940 auto success = boost::interprocess::named_semaphore::remove(semaname.c_str());
1941 if (success) {
1942 std::cout << "Removed CCDB semaphore: " << semaname << "\n";
1943 }
1944 return success;
1945 }
1946 return true;
1947 } catch (std::exception const& e) {
1948 // no EXISTING under this name semaphore found
1949 // nothing to be done
1950 }
1951 return false;
1952}
1953
1954// helper function checking for leaking semaphores associated to CCDB cache files and removing them
1955// walks a local CCDB snapshot tree and checks
1956void CcdbApi::removeLeakingSemaphores(std::string const& snapshotdir, bool remove)
1957{
1958 namespace fs = std::filesystem;
1959 std::string fileName{"snapshot.root"};
1960 try {
1961 auto absolutesnapshotdir = fs::weakly_canonical(fs::absolute(snapshotdir));
1962 for (const auto& entry : fs::recursive_directory_iterator(absolutesnapshotdir)) {
1963 if (entry.is_directory()) {
1964 const fs::path& currentDir = fs::canonical(fs::absolute(entry.path()));
1965 fs::path filePath = currentDir / fileName;
1966 if (fs::exists(filePath) && fs::is_regular_file(filePath)) {
1967 std::cout << "Directory with file '" << fileName << "': " << currentDir << std::endl;
1968
1969 // we need to obtain the path relative to snapshotdir
1970 auto pathtokens = o2::utils::Str::tokenize(currentDir, '/', true);
1971 auto numtokens = pathtokens.size();
1972 if (numtokens < 3) {
1973 // cannot be a CCDB path
1974 continue;
1975 }
1976 // path are last 3 entries
1977 std::string path = pathtokens[numtokens - 3] + "/" + pathtokens[numtokens - 2] + "/" + pathtokens[numtokens - 1];
1978 auto semaname = o2::ccdb::CcdbApi::determineSemaphoreName(absolutesnapshotdir, path);
1979 removeSemaphore(semaname, remove);
1980 }
1981 }
1982 }
1983 } catch (std::exception const& e) {
1984 LOG(info) << "Semaphore search had exception " << e.what();
1985 }
1986}
1987
1988void CcdbApi::getFromSnapshot(bool createSnapshot, std::string const& path,
1989 long timestamp, std::map<std::string, std::string>& headers,
1990 std::string& snapshotpath, o2::pmr::vector<char>& dest, int& fromSnapshot, std::string const& etag) const
1991{
1992 if (createSnapshot) { // create named semaphore
1993 std::string logfile = mSnapshotCachePath + "/log";
1994 std::fstream logStream = std::fstream(logfile, ios_base::out | ios_base::app);
1995 if (logStream.is_open()) {
1996 logStream << "CCDB-access[" << getpid() << "] of " << mUniqueAgentID << " to " << path << " timestamp " << timestamp << " for load to memory\n";
1997 }
1998 }
1999 if (mInSnapshotMode) { // file must be there, otherwise a fatal will be produced;
2000 if (etag.empty()) {
2001 loadFileToMemory(dest, getSnapshotFile(mSnapshotTopPath, path), &headers);
2002 }
2003 fromSnapshot = 1;
2004 } else if (mPreferSnapshotCache && std::filesystem::exists(snapshotpath)) {
2005 // if file is available, use it, otherwise cache it below from the server. Do this only when etag is empty since otherwise the object was already fetched and cached
2006 if (etag.empty()) {
2007 loadFileToMemory(dest, snapshotpath, &headers);
2008 }
2009 fromSnapshot = 2;
2010 }
2011}
2012
2013void CcdbApi::saveSnapshot(RequestContext& requestContext) const
2014{
2015 // Consider saving snapshot
2016 if (!mSnapshotCachePath.empty() && !(mInSnapshotMode && mSnapshotTopPath == mSnapshotCachePath)) { // store in the snapshot only if the object was not read from the snapshot
2017 auto semaphore_barrier = std::make_unique<CCDBSemaphore>(mSnapshotCachePath, requestContext.path);
2018
2019 auto snapshotdir = getSnapshotDir(mSnapshotCachePath, requestContext.path);
2020 std::string snapshotpath = getSnapshotFile(mSnapshotCachePath, requestContext.path);
2022 std::fstream logStream;
2023 if (logStream.is_open()) {
2024 logStream << "CCDB-access[" << getpid() << "] ... " << mUniqueAgentID << " downloading to snapshot " << snapshotpath << " from memory\n";
2025 }
2026 { // dump image to a file
2027 LOGP(debug, "creating snapshot {} -> {}", requestContext.path, snapshotpath);
2028 CCDBQuery querysummary(requestContext.path, requestContext.metadata, requestContext.timestamp);
2029 {
2030 std::ofstream objFile(snapshotpath, std::ios::out | std::ofstream::binary);
2031 std::copy(requestContext.dest.begin(), requestContext.dest.end(), std::ostreambuf_iterator<char>(objFile));
2032 }
2033 // now open the same file as root file and store metadata
2034 updateMetaInformationInLocalFile(snapshotpath, &requestContext.headers, &querysummary);
2035 }
2036 }
2037}
2038
2039void CcdbApi::loadFileToMemory(std::vector<char>& dest, std::string const& path,
2040 std::map<std::string, std::string> const& metadata, long timestamp,
2041 std::map<std::string, std::string>* headers, std::string const& etag,
2042 const std::string& createdNotAfter, const std::string& createdNotBefore, bool considerSnapshot) const
2043{
2045 destP.reserve(dest.size());
2046 loadFileToMemory(destP, path, metadata, timestamp, headers, etag, createdNotAfter, createdNotBefore, considerSnapshot);
2047 dest.clear();
2048 dest.reserve(destP.size());
2049 for (const auto c : destP) {
2050 dest.push_back(c);
2051 }
2052}
2053
2055 std::map<std::string, std::string> const& metadata, long timestamp,
2056 std::map<std::string, std::string>* headers, std::string const& etag,
2057 const std::string& createdNotAfter, const std::string& createdNotBefore, bool considerSnapshot) const
2058{
2059 RequestContext requestContext(dest, metadata, *headers);
2060 requestContext.path = path;
2061 // std::map<std::string, std::string> metadataCopy = metadata; // Create a copy because metadata will be passed as a pointer so it cannot be constant. The const in definition is for backwards compatability.
2062 // requestContext.metadata = metadataCopy;
2063 requestContext.timestamp = timestamp;
2064 requestContext.etag = etag;
2065 requestContext.createdNotAfter = createdNotAfter;
2066 requestContext.createdNotBefore = createdNotBefore;
2067 requestContext.considerSnapshot = considerSnapshot;
2068 std::vector<RequestContext> contexts = {requestContext};
2069 vectoredLoadFileToMemory(contexts);
2070}
2071
2072void CcdbApi::appendFlatHeader(o2::pmr::vector<char>& dest, const std::map<std::string, std::string>& headers)
2073{
2074 size_t hsize = getFlatHeaderSize(headers), cnt = dest.size();
2075 dest.resize(cnt + hsize);
2076 auto addString = [&dest, &cnt](const std::string& s) {
2077 for (char c : s) {
2078 dest[cnt++] = c;
2079 }
2080 dest[cnt++] = 0;
2081 };
2082
2083 for (auto& h : headers) {
2084 addString(h.first);
2085 addString(h.second);
2086 }
2087 *reinterpret_cast<int*>(&dest[cnt]) = hsize; // store size
2088 std::memcpy(&dest[cnt + sizeof(int)], FlatHeaderAnnot, sizeof(FlatHeaderAnnot)); // annotate the flattened headers map
2089}
2090
2091void CcdbApi::navigateSourcesAndLoadFile(RequestContext& requestContext, int& fromSnapshot, size_t* requestCounter) const
2092{
2093 LOGP(debug, "loadFileToMemory {} ETag=[{}]", requestContext.path, requestContext.etag);
2094 bool createSnapshot = requestContext.considerSnapshot && !mSnapshotCachePath.empty(); // create snaphot if absent
2095
2096 std::string snapshotpath;
2097 if (mInSnapshotMode || std::filesystem::exists(snapshotpath = getSnapshotFile(mSnapshotCachePath, requestContext.path))) {
2098 auto semaphore_barrier = std::make_unique<CCDBSemaphore>(mSnapshotCachePath, requestContext.path);
2099 // if we are in snapshot mode we can simply open the file, unless the etag is non-empty:
2100 // this would mean that the object was is already fetched and in this mode we don't to validity checks!
2101 getFromSnapshot(createSnapshot, requestContext.path, requestContext.timestamp, requestContext.headers, snapshotpath, requestContext.dest, fromSnapshot, requestContext.etag);
2102 } else { // look on the server
2103 scheduleDownload(requestContext, requestCounter);
2104 }
2105}
2106
2107void CcdbApi::vectoredLoadFileToMemory(std::vector<RequestContext>& requestContexts) const
2108{
2109 std::vector<int> fromSnapshots(requestContexts.size());
2110 size_t requestCounter = 0;
2111
2112 // Get files from snapshots and schedule downloads
2113 for (int i = 0; i < requestContexts.size(); i++) {
2114 // navigateSourcesAndLoadFile either retrieves file from snapshot immediately, or schedules it to be downloaded when mDownloader->runLoop is ran at a later time
2115 auto& requestContext = requestContexts.at(i);
2116 navigateSourcesAndLoadFile(requestContext, fromSnapshots.at(i), &requestCounter);
2117 }
2118
2119 // Download the rest
2120 while (requestCounter > 0) {
2121 mDownloader->runLoop(0);
2122 }
2123
2124 // Save snapshots
2125 for (int i = 0; i < requestContexts.size(); i++) {
2126 auto& requestContext = requestContexts.at(i);
2127 if (!requestContext.dest.empty()) {
2128 logReading(requestContext.path, requestContext.timestamp, &requestContext.headers,
2129 fmt::format("{}{}", requestContext.considerSnapshot ? "load to memory" : "retrieve", fromSnapshots.at(i) ? " from snapshot" : ""));
2130 if (requestContext.considerSnapshot && fromSnapshots.at(i) != 2) {
2131 saveSnapshot(requestContext);
2132 }
2133 }
2134 }
2135}
2136
2138{
2139 if (url.find("alien:/", 0) != std::string::npos) {
2140 std::map<std::string, std::string> localHeaders;
2141 loadFileToMemory(dest, url, &localHeaders, false);
2142 auto it = localHeaders.find("Error");
2143 if (it != localHeaders.end() && it->second == "An error occurred during retrieval") {
2144 return false;
2145 } else {
2146 return true;
2147 }
2148 }
2149 if ((url.find("file:/", 0) != std::string::npos)) {
2150 std::string path = url.substr(7);
2151 if (std::filesystem::exists(path)) {
2152 std::map<std::string, std::string> localHeaders;
2153 loadFileToMemory(dest, url, &localHeaders, o2::utils::Str::endsWith(path, ".root"));
2154 auto it = localHeaders.find("Error");
2155 if (it != localHeaders.end() && it->second == "An error occurred during retrieval") {
2156 return false;
2157 } else {
2158 return true;
2159 }
2160 }
2161 }
2162 return false;
2163}
2164
2165void CcdbApi::loadFileToMemory(o2::pmr::vector<char>& dest, const std::string& path, std::map<std::string, std::string>* localHeaders, bool fetchLocalMetaData) const
2166{
2167 // Read file to memory as vector. For special case of the locally cached file retriev metadata stored directly in the file
2168 constexpr size_t MaxCopySize = 0x1L << 25;
2169 auto signalError = [&dest, localHeaders]() {
2170 dest.clear();
2171 dest.reserve(1);
2172 if (localHeaders) { // indicate that an error occurred ---> used by caching layers (such as CCDBManager)
2173 (*localHeaders)["Error"] = "An error occurred during retrieval";
2174 }
2175 };
2176 if (path.find("alien:/") == 0 && !initTGrid()) {
2177 signalError();
2178 return;
2179 }
2180 std::string fname(path);
2181 if (fname.find("?filetype=raw") == std::string::npos) {
2182 fname += "?filetype=raw";
2183 }
2184 std::unique_ptr<TFile> sfile{TFile::Open(fname.c_str())};
2185 if (!sfile || sfile->IsZombie()) {
2186 LOG(error) << "Failed to open file " << fname;
2187 signalError();
2188 return;
2189 }
2190 size_t totalread = 0, fsize = sfile->GetSize(), b00 = sfile->GetBytesRead();
2191 dest.resize(fsize);
2192 char* dptr = dest.data();
2193 sfile->Seek(0);
2194 long nread = 0;
2195 do {
2196 size_t b0 = sfile->GetBytesRead(), b1 = b0 - b00;
2197 size_t readsize = fsize - b1 > MaxCopySize ? MaxCopySize : fsize - b1;
2198 if (readsize == 0) {
2199 break;
2200 }
2201 sfile->Seek(totalread, TFile::kBeg);
2202 bool failed = sfile->ReadBuffer(dptr, (Int_t)readsize);
2203 nread = sfile->GetBytesRead() - b0;
2204 if (failed || nread < 0) {
2205 LOG(error) << "failed to copy file " << fname << " to memory buffer";
2206 signalError();
2207 return;
2208 }
2209 dptr += nread;
2210 totalread += nread;
2211 } while (nread == (long)MaxCopySize);
2212
2213 if (localHeaders && fetchLocalMetaData) {
2214 TMemFile memFile("name", const_cast<char*>(dest.data()), dest.size(), "READ");
2215 auto storedmeta = (std::map<std::string, std::string>*)extractFromTFile(memFile, TClass::GetClass("std::map<std::string, std::string>"), CCDBMETA_ENTRY);
2216 if (storedmeta) {
2217 *localHeaders = *storedmeta; // do a simple deep copy
2218 delete storedmeta;
2219 }
2220 if ((isSnapshotMode() || mPreferSnapshotCache) && localHeaders->find("ETag") == localHeaders->end()) { // generate dummy ETag to profit from the caching
2221 (*localHeaders)["ETag"] = path;
2222 }
2223 if (localHeaders->find("fileSize") == localHeaders->end()) {
2224 (*localHeaders)["fileSize"] = fmt::format("{}", memFile.GetEND());
2225 }
2226 }
2227 return;
2228}
2229
2230void CcdbApi::checkMetadataKeys(std::map<std::string, std::string> const& metadata) const
2231{
2232
2233 // function to check if any key contains invalid characters
2234 // if so, a fatal will be issued
2235
2236 const std::regex regexPatternSearch(R"([ :;.,\\/'?!\‍(\)\{\}\[\]@<>=+*#$&`|~^%])");
2237 bool isInvalid = false;
2238
2239 for (auto& el : metadata) {
2240 auto keyMd = el.first;
2241 auto tmp = keyMd;
2242 std::smatch searchRes;
2243 while (std::regex_search(keyMd, searchRes, regexPatternSearch)) {
2244 isInvalid = true;
2245 LOG(error) << "Invalid character found in metadata key '" << tmp << "\': '" << searchRes.str() << "\'";
2246 keyMd = searchRes.suffix();
2247 }
2248 }
2249 if (isInvalid) {
2250 LOG(fatal) << "Some metadata keys have invalid characters, please fix!";
2251 }
2252 return;
2253}
2254
2255void CcdbApi::logReading(const std::string& path, long ts, const std::map<std::string, std::string>* headers, const std::string& comment) const
2256{
2257 std::string upath{path};
2258 if (headers) {
2259 auto ent = headers->find("Valid-From");
2260 if (ent != headers->end()) {
2261 upath += "/" + ent->second;
2262 }
2263 ent = headers->find("ETag");
2264 if (ent != headers->end()) {
2265 upath += "/" + ent->second;
2266 }
2267 }
2268 upath.erase(remove(upath.begin(), upath.end(), '\"'), upath.end());
2269 LOGP(info, "ccdb reads {}{}{} for {} ({}, agent_id: {}), ", mUrl, mUrl.back() == '/' ? "" : "/", upath, ts < 0 ? getCurrentTimestamp() : ts, comment, mUniqueAgentID);
2270}
2271
2272void CcdbApi::asynchPerform(CURL* handle, size_t* requestCounter) const
2273{
2274 mDownloader->asynchSchedule(handle, requestCounter);
2275}
2276
2277CURLcode CcdbApi::CURL_perform(CURL* handle) const
2278{
2279 if (mIsCCDBDownloaderPreferred) {
2280 return mDownloader->perform(handle);
2281 }
2282 CURLcode result;
2283 for (int i = 1; i <= mCurlRetries && (result = curl_easy_perform(handle)) != CURLE_OK; i++) {
2284 usleep(mCurlDelayRetries * i);
2285 }
2286 return result;
2287}
2288
2293CCDBSemaphore::CCDBSemaphore(std::string const& snapshotpath, std::string const& path)
2294{
2295 LOG(debug) << "Entering semaphore barrier";
2296 mSemName = CcdbApi::determineSemaphoreName(snapshotpath, path);
2297 try {
2298 mSem = new boost::interprocess::named_semaphore(boost::interprocess::open_or_create_t{}, mSemName.c_str(), 1);
2299 } catch (std::exception e) {
2300 LOG(warn) << "Exception occurred during CCDB (cache) semaphore setup; Continuing without";
2301 mSem = nullptr;
2302 }
2303 // automatically wait
2304 if (mSem) {
2305 gSemaRegistry.add(this);
2306 mSem->wait();
2307 }
2308}
2309
2311{
2312 LOG(debug) << "Ending semaphore barrier";
2313 if (mSem) {
2314 mSem->post();
2315 if (mSem->try_wait()) { // if nobody else is waiting remove the semaphore resource
2316 mSem->post();
2317 boost::interprocess::named_semaphore::remove(mSemName.c_str());
2318 }
2319 gSemaRegistry.remove(this);
2320 }
2321}
2322
2324{
2325 LOG(debug) << "Cleaning up semaphore registry with count " << mStore.size();
2326 for (auto& s : mStore) {
2327 delete s;
2328 mStore.erase(s);
2329 }
2330}
2331
2333{
2334 mStore.insert(ptr);
2335}
2336
2338{
2339 mStore.erase(ptr);
2340}
2341
2342} // namespace o2::ccdb
std::string createdNotBefore
std::string createdNotAfter
size_t maxSize
std::string etag
std::string url
std::ostringstream debug
int32_t i
#define failed(...)
Definition Utils.h:42
uint8_t errorflag
Definition RawData.h:0
uint32_t res
Definition RawData.h:0
uint32_t c
Definition RawData.h:2
TBranch * ptr
StringRef key
Class for time synchronization of RawReader instances.
void setRequestTimeoutTime(int timeoutMS)
CURLcode perform(CURL *handle)
void asynchSchedule(CURL *handle, size_t *requestCounter)
void setKeepaliveTimeoutTime(int timeoutMS)
CCDBSemaphore(std::string const &cachepath, std::string const &path)
Definition CcdbApi.cxx:2293
static void curlSetSSLOptions(CURL *curl)
Definition CcdbApi.cxx:694
static std::string generateFileName(const std::string &inp)
Definition CcdbApi.cxx:900
std::string list(std::string const &path="", bool latestOnly=false, std::string const &returnFormat="text/plain", long createdNotAfter=-1, long createdNotBefore=-1) const
Definition CcdbApi.cxx:1300
int storeAsTFile_impl(const void *obj1, std::type_info const &info, std::string const &path, std::map< std::string, std::string > const &metadata, long startValidityTimestamp=-1, long endValidityTimestamp=-1, std::vector< char >::size_type maxSize=0) const
Definition CcdbApi.cxx:408
static bool checkAlienToken()
Definition CcdbApi.cxx:198
void runDownloaderLoop(bool noWait)
Definition CcdbApi.cxx:340
void releaseNamedSemaphore(boost::interprocess::named_semaphore *sem, std::string const &path) const
Definition CcdbApi.cxx:1922
static std::map< std::string, std::string > * retrieveMetaInfo(TFile &)
Definition CcdbApi.cxx:1701
void scheduleDownload(RequestContext &requestContext, size_t *requestCounter) const
Definition CcdbApi.cxx:1822
TObject * retrieve(std::string const &path, std::map< std::string, std::string > const &metadata, long timestamp) const
Definition CcdbApi.cxx:872
void init(std::string const &hosts)
Definition CcdbApi.cxx:235
TObject * retrieveFromTFile(std::string const &path, std::map< std::string, std::string > const &metadata, long timestamp, std::map< std::string, std::string > *headers, std::string const &etag, const std::string &createdNotAfter, const std::string &createdNotBefore) const
Definition CcdbApi.cxx:910
std::string const & getURL() const
Definition CcdbApi.h:88
void getFromSnapshot(bool createSnapshot, std::string const &path, long timestamp, std::map< std::string, std::string > &headers, std::string &snapshotpath, o2::pmr::vector< char > &dest, int &fromSnapshot, std::string const &etag) const
Definition CcdbApi.cxx:1988
bool loadLocalContentToMemory(o2::pmr::vector< char > &dest, std::string &url) const
Definition CcdbApi.cxx:2137
static void removeLeakingSemaphores(std::string const &basedir, bool remove=false)
Definition CcdbApi.cxx:1956
void saveSnapshot(RequestContext &requestContext) const
Definition CcdbApi.cxx:2013
static std::unique_ptr< std::vector< char > > createObjectImage(const T *obj, CcdbObjectInfo *info=nullptr)
Definition CcdbApi.h:104
static void * extractFromTFile(TFile &file, TClass const *cl, const char *what=CCDBOBJECT_ENTRY)
Definition CcdbApi.cxx:982
int storeAsTFile(const TObject *rootObject, std::string const &path, std::map< std::string, std::string > const &metadata, long startValidityTimestamp=-1, long endValidityTimestamp=-1, std::vector< char >::size_type maxSize=0) const
Definition CcdbApi.cxx:548
void snapshot(std::string const &ccdbrootpath, std::string const &localDir, long timestamp) const
Definition CcdbApi.cxx:972
bool isSnapshotMode() const
Definition CcdbApi.h:94
bool isHostReachable() const
Definition CcdbApi.cxx:1434
void loadFileToMemory(std::vector< char > &dest, std::string const &path, std::map< std::string, std::string > const &metadata, long timestamp, std::map< std::string, std::string > *headers, std::string const &etag, const std::string &createdNotAfter, const std::string &createdNotBefore, bool considerSnapshot=true) const
Definition CcdbApi.cxx:2039
static std::string determineSemaphoreName(std::string const &basedir, std::string const &objectpath)
Definition CcdbApi.cxx:1903
void deleteObject(std::string const &path, long timestamp=-1) const
Definition CcdbApi.cxx:1354
static void appendFlatHeader(o2::pmr::vector< char > &dest, const std::map< std::string, std::string > &headers)
Definition CcdbApi.cxx:2072
std::vector< std::string > getAllFolders(std::string const &top) const
Definition CcdbApi.cxx:1730
void vectoredLoadFileToMemory(std::vector< RequestContext > &requestContext) const
Definition CcdbApi.cxx:2107
boost::interprocess::named_semaphore * createNamedSemaphore(std::string const &path) const
Definition CcdbApi.cxx:1910
static bool removeSemaphore(std::string const &name, bool remove=false)
Definition CcdbApi.cxx:1933
std::map< std::string, std::string > retrieveHeaders(std::string const &path, std::map< std::string, std::string > const &metadata, long timestamp=-1) const
Definition CcdbApi.cxx:1560
CcdbApi()
Default constructor.
Definition CcdbApi.cxx:171
static bool getCCDBEntryHeaders(std::string const &url, std::string const &etag, std::vector< std::string > &headers, const std::string &agentID="")
Definition CcdbApi.cxx:1641
static CCDBQuery * retrieveQueryInfo(TFile &)
Definition CcdbApi.cxx:1692
static constexpr const char * CCDBQUERY_ENTRY
Definition CcdbApi.h:336
void truncate(std::string const &path) const
Definition CcdbApi.cxx:1392
int storeAsBinaryFile(const char *buffer, size_t size, const std::string &fileName, const std::string &objectType, const std::string &path, const std::map< std::string, std::string > &metadata, long startValidityTimestamp, long endValidityTimestamp, std::vector< char >::size_type maxSize=0) const
Definition CcdbApi.cxx:424
static void parseCCDBHeaders(std::vector< std::string > const &headers, std::vector< std::string > &pfns, std::string &etag)
Definition CcdbApi.cxx:1677
bool retrieveBlob(std::string const &path, std::string const &targetdir, std::map< std::string, std::string > const &metadata, long timestamp, bool preservePathStructure=true, std::string const &localFileName="snapshot.root", std::string const &createdNotAfter="", std::string const &createdNotBefore="", std::map< std::string, std::string > *headers=nullptr) const
Definition CcdbApi.cxx:917
int updateMetadata(std::string const &path, std::map< std::string, std::string > const &metadata, long timestamp, std::string const &id="", long newEOV=0)
Definition CcdbApi.cxx:1747
static constexpr const char * CCDBMETA_ENTRY
Definition CcdbApi.h:337
static constexpr const char * CCDBOBJECT_ENTRY
Definition CcdbApi.h:338
void navigateSourcesAndLoadFile(RequestContext &requestContext, int &fromSnapshot, size_t *requestCounter) const
Definition CcdbApi.cxx:2091
std::vector< std::string > parseSubFolders(std::string const &reply) const
Definition CcdbApi.cxx:1468
virtual ~CcdbApi()
Default destructor.
Definition CcdbApi.cxx:187
void add(CCDBSemaphore const *ptr)
Definition CcdbApi.cxx:2332
void remove(CCDBSemaphore const *ptr)
Definition CcdbApi.cxx:2337
const GLfloat * m
Definition glcorearb.h:4066
GLuint64EXT * result
Definition glcorearb.h:5662
GLint void * img
Definition glcorearb.h:550
GLuint buffer
Definition glcorearb.h:655
GLuint entry
Definition glcorearb.h:5735
GLsizeiptr size
Definition glcorearb.h:659
GLuint GLuint end
Definition glcorearb.h:469
GLuint index
Definition glcorearb.h:781
GLdouble GLdouble GLdouble GLdouble top
Definition glcorearb.h:4077
GLdouble f
Definition glcorearb.h:310
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLenum GLint * range
Definition glcorearb.h:1899
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLboolean * data
Definition glcorearb.h:298
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
GLuint object
Definition glcorearb.h:4041
GLuint start
Definition glcorearb.h:469
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLuint GLuint stream
Definition glcorearb.h:1806
GLbitfield GLuint64 timeout
Definition glcorearb.h:1573
GLuint id
Definition glcorearb.h:650
information complementary to a CCDB object (path, metadata, startTimeValidity, endTimeValidity etc)
bool stdmap_to_jsonfile(std::map< std::string, std::string > const &meta, std::string const &filename)
Definition CcdbApi.cxx:1504
size_t write_data(void *, size_t size, size_t nmemb, void *)
Definition CcdbApi.cxx:1429
long getCurrentTimestamp()
returns the timestamp in long corresponding to "now"
size_t(*)(void *, size_t, size_t, void *) CurlWriteCallback
Definition CcdbApi.cxx:720
std::string sanitizeObjectName(const std::string &objectName)
Definition CcdbApi.cxx:371
std::mutex gIOMutex
Definition CcdbApi.cxx:62
bool jsonfile_to_stdmap(std::map< std::string, std::string > &meta, std::string const &filename)
Definition CcdbApi.cxx:1533
long getFutureTimestamp(int secondsInFuture)
returns the timestamp in long corresponding to "now + secondsInFuture"
size_t CurlWrite_CallbackFunc_StdString2(void *contents, size_t size, size_t nmemb, std::string *s)
Definition CcdbApi.cxx:1285
std::string timestamp() noexcept
Definition Clock.h:84
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
std::vector< T, fair::mq::pmr::polymorphic_allocator< T > > vector
void createDirectoriesIfAbsent(std::string const &path)
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
std::string filename()
void empty(int)
std::map< std::string, std::string > const & metadata
Definition CcdbApi.h:361
std::map< std::string, std::string > & headers
Definition CcdbApi.h:363
o2::pmr::vector< char > & dest
Definition CcdbApi.h:359
o2::pmr::vector< char > * object
static DeploymentMode deploymentMode()
static std::string getClassName(const T &obj)
get the class name of the object
static std::unique_ptr< FileImage > createFileImage(const TObject &obj, const std::string &fileName, const std::string &objName)
static void trim(std::string &s)
Definition StringUtils.h:70
static std::vector< std::string > tokenize(const std::string &src, char delim, bool trimToken=true, bool skipEmpty=true)
static std::string concat_string(Ts const &... ts)
static bool endsWith(const std::string &s, const std::string &ending)
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))
const std::string str
uint64_t const void const *restrict const msg
Definition x9.h:153