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