Project
Loading...
Searching...
No Matches
testCcdbApiHeaders.cxx
Go to the documentation of this file.
1// Copyright 2019-2025 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#define BOOST_TEST_MODULE CCDB
18#define BOOST_TEST_MAIN
19#define BOOST_TEST_DYN_LINK
20
21#include <set>
24#include "CCDB/CcdbApi.h"
25#include <boost/test/unit_test.hpp>
26#include <cstdlib>
27
28static std::string basePath;
29// std::string ccdbUrl = "http://localhost:8080";
30std::string ccdbUrl = "http://ccdb-test.cern.ch:8080";
31bool hostReachable = false;
32
37struct Fixture {
39 {
40 auto& ccdbManager = o2::ccdb::BasicCCDBManager::instance();
41 // These suites upload, so they need a WRITABLE instance -- ccdb-test by
42 // default, not the official CCDB.
43 if (const char* host = std::getenv("ALICEO2_CCDB_HOST")) {
44 ccdbUrl = host;
45 }
46 ccdbManager.setURL(ccdbUrl);
47 hostReachable = ccdbManager.getCCDBAccessor().isHostReachable();
48 char hostname[_POSIX_HOST_NAME_MAX];
49 gethostname(hostname, _POSIX_HOST_NAME_MAX);
50 basePath = std::string("Users/m/meide/Tests/") + hostname + "/pid-" + getpid() + "/BasicCCDBManager/";
51
52 LOG(info) << "Path we will use in this test suite : " + basePath << std::endl;
53 LOG(info) << "ccdb url: " << ccdbUrl << std::endl;
54 LOG(info) << "Is host reachable ? --> " << hostReachable << std::endl;
55 }
57 {
58 if (hostReachable) {
59 o2::ccdb::BasicCCDBManager::instance().getCCDBAccessor().truncate(basePath + "*"); // This deletes the data after test is run, disable if you want to inspect the data
60 LOG(info) << "Test data truncated/deleted (" << basePath << ")" << std::endl;
61 }
62 }
63};
69struct if_reachable {
70 boost::test_tools::assertion_result operator()(boost::unit_test::test_unit_id)
71 {
72 return hostReachable;
73 }
74};
75
76// Only compare known and stable keys (avoid volatile ones like Date)
77static const std::set<std::string> sStableKeys = {
78 "ETag",
79 "Valid-From",
80 "Valid-Until",
81 "Created",
82 "Last-Modified",
83 "Content-Disposition",
84 "Content-Location",
85 "path",
86 "partName",
87 "Content-MD5",
88 "Hello" // TODO find other headers to compare to
89};
90
91// Test that we get back the same header header keys as we put in (for stable keys)
92
93BOOST_AUTO_TEST_CASE(testCachedHeaders, *boost::unit_test::precondition(if_reachable()))
94{
96 // First store objects to test with
97 auto& ccdbManager = o2::ccdb::BasicCCDBManager::instance();
98 std::string pathA = basePath + "CachingA";
99 std::string pathB = basePath + "CachingB";
100 std::string pathC = basePath + "CachingC";
101 std::string ccdbObjO = "testObjectO";
102 std::string ccdbObjN = "testObjectN";
103 std::string ccdbObjX = "testObjectX";
104 std::map<std::string, std::string> md = {
105 {"Hello", "World"},
106 {"Key1", "Value1"},
107 {"Key2", "Value2"},
108 };
109 long start = 1000, stop = 3000;
110 ccdbManager.getCCDBAccessor().storeAsTFileAny<std::string>(&ccdbObjO, pathA, md, start, stop);
111 ccdbManager.getCCDBAccessor().storeAsTFileAny<std::string>(&ccdbObjN, pathB, md, start, stop);
112 ccdbManager.getCCDBAccessor().storeAsTFileAny<std::string>(&ccdbObjX, pathC, md, start, stop);
113 // initilize the BasicCCDBManager
114 ccdbManager.clearCache();
115 ccdbManager.setCaching(true); // This is what we want to test.
116
118 // Plan: get one object, then another, then the first again and check the headers are the same
119 std::map<std::string, std::string> headers1, headers2, headers3;
120
121 auto* obj1 = ccdbManager.getForTimeStamp<std::string>(pathA, (start + stop) / 2, &headers1);
122 auto* obj2 = ccdbManager.getForTimeStamp<std::string>(pathB, (start + stop) / 2, &headers2);
123 auto* obj3 = ccdbManager.getForTimeStamp<std::string>(pathA, (start + stop) / 2, &headers3); // Should lead to a cache hit!
124
127 BOOST_REQUIRE(obj1 != nullptr);
128 BOOST_REQUIRE(obj2 != nullptr);
129 BOOST_REQUIRE(obj3 != nullptr);
130
131 LOG(debug) << "obj1: " << *obj1;
132 LOG(debug) << "obj2: " << *obj2;
133 LOG(debug) << "obj3: " << *obj3;
134
135 // Sanity check
137 BOOST_TEST(*obj1 == ccdbObjO);
138 BOOST_TEST(*obj3 == ccdbObjO);
139 BOOST_TEST(obj3 == obj1); // should be the same object in memory since it is cached
140
141 BOOST_TEST(obj2 != obj1);
142
143 (*obj1) = "ModifiedObject";
144 BOOST_TEST(*obj1 == "ModifiedObject");
145 BOOST_TEST(*obj3 == "ModifiedObject"); // obj3 and obj1 are the same object in memory
146
147 // Check that the headers are the same for the two retrievals of the same object
148 BOOST_REQUIRE(headers1.size() != 0);
149 BOOST_REQUIRE(headers3.size() != 0);
150
151 LOG(debug) << "Headers1 size: " << headers1.size();
152 for (const auto& h : headers1) {
153 LOG(debug) << " " << h.first << " -> " << h.second;
154 }
155 LOG(debug) << "Headers3 size: " << headers3.size();
156 for (const auto& h : headers3) {
157 LOG(debug) << " " << h.first << " -> " << h.second;
158 }
159
160 for (const auto& stableKey : sStableKeys) {
161 LOG(info) << "Checking key: " << stableKey;
162
163 BOOST_REQUIRE(headers1.count(stableKey) > 0);
164 BOOST_REQUIRE(headers3.count(stableKey) > 0);
165 BOOST_TEST(headers1.at(stableKey) == headers3.at(stableKey));
166 }
167 BOOST_TEST(headers1 != headers2, "The headers for different objects should be different");
168
169 // Test that we can change the map and the two headers are not affected
170 headers1["NewKey"] = "NewValue";
171 headers3["NewKey"] = "DifferentValue";
172 BOOST_TEST(headers1["NewKey"] != headers3["NewKey"]); // This tests that we have a deep copy of the headers
173}
174
175BOOST_AUTO_TEST_CASE(testNonCachedHeaders, *boost::unit_test::precondition(if_reachable()))
176{
178 // First store objects to test with
179 auto& ccdbManager = o2::ccdb::BasicCCDBManager::instance();
180 std::string pathA = basePath + "NonCachingA";
181 std::string pathB = basePath + "NonCachingB";
182 std::string ccdbObjO = "testObjectO";
183 std::string ccdbObjN = "testObjectN";
184 std::map<std::string, std::string> md = {
185 {"Hello", "World"},
186 {"Key1", "Value1"},
187 {"Key2", "Value2"},
188 };
189 long start = 1000, stop = 2000;
190 ccdbManager.getCCDBAccessor().storeAsTFileAny(&ccdbObjO, pathA, md, start, stop);
191 ccdbManager.getCCDBAccessor().storeAsTFileAny(&ccdbObjN, pathB, md, start, stop);
192 // initilize the BasicCCDBManager
193 ccdbManager.clearCache();
194 ccdbManager.setCaching(false); // This is what we want to test, no caching
195
197 // Plan: get one object, then another, then the first again. Then check that the contents is the same but not the object in memory
198 std::map<std::string, std::string> headers1, headers2, headers3;
199
200 auto* obj1 = ccdbManager.getForTimeStamp<std::string>(pathA, (start + stop) / 2, &headers1);
201 auto* obj2 = ccdbManager.getForTimeStamp<std::string>(pathB, (start + stop) / 2, &headers2);
202 auto* obj3 = ccdbManager.getForTimeStamp<std::string>(pathA, (start + stop) / 2, &headers3); // Should not be cached since explicitly disabled
203
204 ccdbManager.setCaching(true); // Restore default state
207 BOOST_REQUIRE(obj1 != nullptr);
208 BOOST_REQUIRE(obj2 != nullptr);
209 BOOST_REQUIRE(obj3 != nullptr);
210
211 LOG(debug) << "obj1: " << *obj1;
212 LOG(debug) << "obj2: " << *obj2;
213 LOG(debug) << "obj3: " << *obj3;
214
215 // Sanity check
217 BOOST_TEST(*obj1 == ccdbObjO);
218 BOOST_TEST(*obj3 == ccdbObjO);
219 BOOST_TEST(obj2 != obj1);
220 BOOST_TEST(obj3 != obj1); // should NOT be the same object in memory
221 (*obj1) = "ModifiedObject";
222 BOOST_TEST(*obj1 == "ModifiedObject");
223 BOOST_TEST(*obj3 != "ModifiedObject"); // obj3 and obj1 are NOT the same object in memory
224
225 BOOST_TEST(headers1.size() == headers3.size());
226
227 // Remove the date header since it may be different even for the same object since we might have asked in different seconds
228 headers1.erase("Date");
229 headers3.erase("Date");
230 BOOST_TEST(headers1 == headers3, "The headers for the same object should be the same even if not cached");
231
232 BOOST_TEST(headers1 != headers2, "The headers for different objects should be different");
233 BOOST_TEST(headers1.size() != 0);
234 BOOST_TEST(headers3.size() != 0);
235 BOOST_TEST(headers2.size() != 0);
236 BOOST_TEST(headers1 != headers2, "The headers for different objects should be different");
237
238 // cleanup
239 delete obj1;
240 delete obj2;
241 delete obj3;
242}
243
244BOOST_AUTO_TEST_CASE(CacheFirstRetrievalAndHeadersPersistence, *boost::unit_test::precondition(if_reachable()))
245{
248 // Prepare two validity slots for same path to test ETag change later
249 std::string path = basePath + "ObjA";
250 std::string objV1 = "ObjectVersion1";
251 std::string objV2 = "ObjectVersion2";
252 std::map<std::string, std::string> meta1{
253 {"UserKey1", "UValue1"},
254 {"UserKey2", "UValue2"}};
255 long v1start = 10'000;
256 long v1stop = 20'000;
257 long v2start = v1stop; // contiguous slot
258 long v2stop = v2start + (v1stop - v1start);
259 long mid1 = (v1start + v1stop) / 2;
260 // Store 2 versions
261 mgr.getCCDBAccessor().storeAsTFileAny(&objV1, path, meta1, v1start, v1stop);
262 mgr.getCCDBAccessor().storeAsTFileAny(&objV2, path, meta1, v2start, v2stop);
263
264 mgr.clearCache();
265 mgr.setCaching(true);
266 mgr.setFatalWhenNull(true);
267 mgr.setTimestamp(mid1);
268
270 std::map<std::string, std::string> headers1, headers2, headers4, headers5;
271
272 // 1) First retrieval WITH headers inside 1st slot
273 auto* p1 = mgr.getForTimeStamp<std::string>(path, mid1, &headers1);
274 size_t fetchedSizeAfterFirst = mgr.getFetchedSize();
275 // 2) Second retrieval (cache hit)
276 auto* p2 = mgr.getForTimeStamp<std::string>(path, mid1, &headers2);
277 size_t fetchedSizeAfterSecond = mgr.getFetchedSize();
278 // 3) Third retrieval (cache hit) WITHOUT passing headers
279 auto* p3 = mgr.getForTimeStamp<std::string>(path, mid1);
280 // 4) Fourth retrieval with headers again -> should still produce same headers
281 auto* p4 = mgr.getForTimeStamp<std::string>(path, mid1, &headers4);
282 // 5) Fifth retrieval with headers again to check persistence
283 auto* p5 = mgr.getForTimeStamp<std::string>(path, mid1, &headers5);
284
285 mgr.setFatalWhenNull(false); // restore default
286
288
289 BOOST_TEST(p1 != nullptr);
290 BOOST_TEST(*p1 == objV1);
291
292 BOOST_TEST(headers1.count("UserKey1") == 1);
293 BOOST_TEST(headers1.count("UserKey2") == 1);
294 BOOST_TEST(headers1["UserKey1"] == "UValue1");
295 BOOST_TEST(headers1["UserKey2"] == "UValue2");
296 BOOST_TEST(headers1.count("Valid-From") == 1);
297 BOOST_TEST(headers1.count("Valid-Until") == 1);
298 BOOST_TEST(headers1.count("ETag") == 1);
299
300 /* Need to manually amend the headers1 to have cache valid until for comparison sake,
301 * the header is not set in the first request.
302 * It is only set if the internal cache of CCDB has seen this object before, apperently.
303 * This will never happen in this test since it was just created and not asked for before.
304 */
305 headers1["Cache-Valid-Until"] = std::to_string(v1stop);
306
307 /* In rare cases the header date might be different, if the second has ticked over between the requests
308 */
309 headers1.erase("Date");
310 headers2.erase("Date");
311 headers4.erase("Date");
312 headers5.erase("Date");
313
314 BOOST_TEST(p2 == p1); // same pointer for cached scenario
315 BOOST_TEST(headers2 == headers1); // identical header map
316 BOOST_TEST(fetchedSizeAfterSecond == fetchedSizeAfterFirst); // no new fetch
317
318 BOOST_TEST(p3 == p1);
319
320 BOOST_TEST(p4 == p1);
321 BOOST_TEST(headers4 == headers1);
322
323 // Mutate the returned header map locally and ensure it does not corrupt internal cache
324 headers4["UserKey1"] = "Tampered";
325 BOOST_TEST(p5 == p1);
326 BOOST_TEST(headers5["UserKey1"] == "UValue1"); // internal unchanged
327}
328
329BOOST_AUTO_TEST_CASE(FailedFetchDoesNotGiveMetadata, *boost::unit_test::precondition(if_reachable()))
330{
331
334 std::string path = basePath + "FailThenRecover";
335 std::string content = "ContentX";
336 std::map<std::string, std::string> meta{{"Alpha", "Beta"}};
337 long s = 300'000, e = 310'000;
338 mgr.getCCDBAccessor().storeAsTFileAny(&content, path, meta, s, e);
339 mgr.clearCache();
340 mgr.setCaching(true);
341 mgr.setFatalWhenNull(false);
342
344 // Intentionally pick a timestamp outside validity to fail first
345 long badTS = s - 1000;
346 long goodTS = (s + e) / 2;
347 std::map<std::string, std::string> hFail, hGood;
348 auto* badObj = mgr.getForTimeStamp<std::string>(path, badTS, &hFail);
349 auto* goodObj = mgr.getForTimeStamp<std::string>(path, goodTS, &hGood);
350
352 BOOST_TEST(!hFail.empty()); // Should have some headers
353 BOOST_TEST(hFail["Alpha"] != "Beta"); // But not the metadata
354 BOOST_TEST(hGood.count("Alpha") == 1);
355 BOOST_TEST(hGood["Alpha"] == "Beta");
356
357 mgr.setFatalWhenNull(true);
358}
359
360BOOST_AUTO_TEST_CASE(FirstCallWithoutHeadersThenWithHeaders, *boost::unit_test::precondition(if_reachable()))
361{
362
364 std::string path = basePath + "LateHeaders";
365 std::string body = "Late";
366 std::map<std::string, std::string> meta{{"LateKey", "LateVal"}};
367 long s = 400'000, e = 410'000;
368 mgr.getCCDBAccessor().storeAsTFileAny(&body, path, meta, s, e);
369
370 mgr.clearCache();
371 mgr.setCaching(true);
372 long ts = (s + e) / 2;
373
374 // 1) First call with nullptr headers
375 auto* first = mgr.getForTimeStamp<std::string>(path, ts);
376 BOOST_TEST(first != nullptr);
377 BOOST_TEST(*first == body);
378
379 // 2) Second call asking for headers - should return the full set
380 std::map<std::string, std::string> h2;
381 auto* second = mgr.getForTimeStamp<std::string>(path, ts, &h2);
382 BOOST_TEST(second == first);
383 BOOST_TEST(h2.count("LateKey") == 1);
384 BOOST_TEST(h2["LateKey"] == "LateVal");
385 BOOST_TEST(h2.count("Valid-From") == 1);
386 BOOST_TEST(h2.count("Valid-Until") == 1);
387}
388
389BOOST_AUTO_TEST_CASE(HeadersAreStableAcrossMultipleHits, *boost::unit_test::precondition(if_reachable()))
390{
391
393 std::string path = basePath + "StableHeaders";
394 std::string body = "Stable";
395 std::map<std::string, std::string> meta{{"HK", "HV"}};
396 long s = 500'000, e = 510'000;
397 mgr.getCCDBAccessor().storeAsTFileAny(&body, path, meta, s, e);
398
399 mgr.clearCache();
400 mgr.setCaching(true);
401 long ts = (s + e) / 2;
402
403 std::map<std::string, std::string> h1;
404 auto* o1 = mgr.getForTimeStamp<std::string>(path, ts, &h1);
405 BOOST_TEST(o1 != nullptr);
406 BOOST_TEST(h1.count("HK") == 1);
407
408 std::string etag = h1["ETag"];
409 for (int i = 0; i < 15; ++i) {
410 std::map<std::string, std::string> hi;
411 auto* oi = mgr.getForTimeStamp<std::string>(path, ts, &hi);
412 BOOST_TEST(oi == o1);
413 BOOST_TEST(hi.count("HK") == 1);
414 BOOST_TEST(hi["ETag"] == etag);
415 }
416}
std::string etag
std::ostringstream debug
int32_t i
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error s
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char e
constexpr int p2()
constexpr int p1()
constexpr to accelerate the coordinates changing
Class for time synchronization of RawReader instances.
static BasicCCDBManager & instance()
void truncate(std::string const &path) const
Definition CcdbApi.cxx:1401
GLint first
Definition glcorearb.h:399
GLuint GLuint pathB
Definition glcorearb.h:5477
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
GLuint start
Definition glcorearb.h:469
GLuint pathA
Definition glcorearb.h:5477
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
boost::test_tools::assertion_result operator()(boost::unit_test::test_unit_id)
std::string ccdbUrl
bool hostReachable
std::string ccdbUrl
BOOST_GLOBAL_FIXTURE(Fixture)
bool hostReachable
BOOST_AUTO_TEST_CASE(testCachedHeaders, *boost::unit_test::precondition(if_reachable()))
bool hostReachable
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
BOOST_TEST(digits==digitsD, boost::test_tools::per_element())