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
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:1335
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())