Project
Loading...
Searching...
No Matches
testCcdbApiDownloader.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
12#define BOOST_TEST_MODULE CCDB
13#define BOOST_TEST_MAIN
14#define BOOST_TEST_DYN_LINK
15
18#include <CCDB/CCDBDownloader.h>
19#include <curl/curl.h>
20#include <chrono>
21#include <iostream>
22#include <unistd.h> // Sleep function to wait for asynch results
23#include <fairlogger/Logger.h>
24
25#include <boost/test/unit_test.hpp>
26#include <boost/asio/ip/host_name.hpp>
27#include <uv.h>
28
29#include <boost/algorithm/string.hpp>
31
32using namespace std;
33
34namespace o2
35{
36namespace ccdb
37{
38
39size_t CurlWrite_CallbackFunc_StdString2(void* contents, size_t size, size_t nmemb, std::string* s)
40{
41 size_t newLength = size * nmemb;
42 size_t oldLength = s->size();
43 try {
44 s->resize(oldLength + newLength);
45 } catch (std::bad_alloc& e) {
46 LOG(error) << "memory error when getting data from CCDB";
47 return 0;
48 }
49
50 std::copy((char*)contents, (char*)contents + newLength, s->begin() + oldLength);
51 return size * nmemb;
52}
53
54std::string uniqueAgentID()
55{
56 std::string host = boost::asio::ip::host_name();
57 char const* jobID = getenv("ALIEN_PROC_ID");
58 if (jobID) {
59 return fmt::format("{}-{}-{}-{}", host, getCurrentTimestamp() / 1000, o2::utils::Str::getRandomString(6), jobID);
60 } else {
61 return fmt::format("{}-{}-{}", host, getCurrentTimestamp() / 1000, o2::utils::Str::getRandomString(6));
62 }
63}
64
65CURL* createTestHandle(std::string* dst)
66{
67 CURL* handle = curl_easy_init();
68 curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, CurlWrite_CallbackFunc_StdString2);
69 curl_easy_setopt(handle, CURLOPT_WRITEDATA, dst);
70 curl_easy_setopt(handle, CURLOPT_URL, "http://ccdb-test.cern.ch:8080/");
71 auto userAgent = uniqueAgentID();
72 curl_easy_setopt(handle, CURLOPT_USERAGENT, userAgent.c_str());
73 return handle;
74}
75
76namespace
77{
78template <typename MapType = std::map<std::string, std::string>>
79size_t header_map_callback(char* buffer, size_t size, size_t nitems, void* userdata)
80{
81 auto* headers = static_cast<MapType*>(userdata);
82 auto header = std::string(buffer, size * nitems);
83 std::string::size_type index = header.find(':', 0);
84 if (index != std::string::npos) {
85 const auto key = boost::algorithm::trim_copy(header.substr(0, index));
86 const auto value = boost::algorithm::trim_copy(header.substr(index + 1));
87 headers->insert(std::make_pair(key, value));
88 }
89 return size * nitems;
90}
91} // namespace
92
93size_t writeCallbackNoLambda(void* contents, size_t size, size_t nmemb, void* chunkptr)
94{
95 auto& ho = *static_cast<HeaderObjectPair_t*>(chunkptr);
96 auto& chunk = *ho.object;
97 size_t realsize = size * nmemb, sz = 0;
98 ho.counter++;
99 try {
100 if (chunk.capacity() < chunk.size() + realsize) {
101 auto cl = ho.header.find("Content-Length");
102 if (cl != ho.header.end()) {
103 sz = std::max(chunk.size() + realsize, (size_t)std::stol(cl->second));
104 } else {
105 sz = chunk.size() + realsize;
106 // LOGP(debug, "SIZE IS NOT IN HEADER, allocate {}", sz);
107 }
108 chunk.reserve(sz);
109 }
110 char* contC = (char*)contents;
111 chunk.insert(chunk.end(), contC, contC + realsize);
112 } catch (std::exception e) {
113 // LOGP(alarm, "failed to reserve {} bytes in CURL write callback (realsize = {}): {}", sz, realsize, e.what());
114 realsize = 0;
115 }
116 return realsize;
117}
118
119std::vector<CURL*> prepareAsyncHandles(size_t num, std::vector<o2::pmr::vector<char>*>& dests)
120{
121 std::vector<CURL*> handles;
122
123 for (int i = 0; i < num; i++) {
124 auto dest = new o2::pmr::vector<char>();
125 dests.push_back(dest);
126 CURL* curl_handle = curl_easy_init();
127 handles.push_back(curl_handle);
128
129 auto data = new DownloaderRequestData();
130 data->hoPair.object = dest;
131 data->hosts.emplace_back("http://ccdb-test.cern.ch:8080");
132 data->path = "Analysis/ALICE3/Centrality";
133 data->timestamp = 1646729604010;
134 data->localContentCallback = nullptr;
135
136 curl_easy_setopt(curl_handle, CURLOPT_URL, "http://ccdb-test.cern.ch:8080/Analysis/ALICE3/Centrality/1646729604010");
137 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, writeCallbackNoLambda);
138 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void*)&(data->hoPair));
139
140 curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, header_map_callback<decltype(data->hoPair.header)>);
141 curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, (void*)&(data->hoPair.header));
142 curl_easy_setopt(curl_handle, CURLOPT_PRIVATE, (void*)data);
143 }
144 return handles;
145}
146
147BOOST_AUTO_TEST_CASE(asynch_schedule_test)
148{
149 int TRANSFERS = 5;
150
151 if (curl_global_init(CURL_GLOBAL_ALL)) {
152 fprintf(stderr, "Could not init curl\n");
153 return;
154 }
155
156 CCDBDownloader downloader;
157 std::vector<o2::pmr::vector<char>*> dests;
158 auto handles = prepareAsyncHandles(TRANSFERS, dests);
159 size_t transfersLeft = 0;
160
161 for (auto handle : handles) {
162 downloader.asynchSchedule(handle, &transfersLeft);
163 }
164
165 while (transfersLeft > 0) {
166 downloader.runLoop(false);
167 }
168
169 for (int i = 0; i < TRANSFERS; i++) {
170 // I would claim that accessing the handles after they are complete
171 // is actually not supported by the current API, because it was
172 // previously relying on leaking the handles. Disabling the whole
173 // thing until we verify that's actually the case.
174 //
175 // long httpCode;
176 // curl_easy_getinfo(handles[i], CURLINFO_HTTP_CODE, &httpCode);
177 // BOOST_CHECK_EQUAL(httpCode, 200);
178 // BOOST_CHECK_NE(dests[i]->size(), 0);
179 // curl_easy_cleanup(handles[i]);
180 delete dests[i];
181 }
182 curl_global_cleanup();
183}
184
186{
187 if (curl_global_init(CURL_GLOBAL_ALL)) {
188 fprintf(stderr, "Could not init curl\n");
189 return;
190 }
191
192 CCDBDownloader downloader;
193 std::string dst = "";
194 CURL* handle = createTestHandle(&dst);
195
196 CURLcode curlCode = downloader.perform(handle);
197
198 BOOST_CHECK_EQUAL(curlCode, CURLE_OK);
199
200 long httpCode;
201 curl_easy_getinfo(handle, CURLINFO_HTTP_CODE, &httpCode);
202 BOOST_CHECK_EQUAL(httpCode, 200);
203
204 curl_easy_cleanup(handle);
205 curl_global_cleanup();
206}
207
208BOOST_AUTO_TEST_CASE(blocking_batch_test)
209{
210 if (curl_global_init(CURL_GLOBAL_ALL)) {
211 fprintf(stderr, "Could not init curl\n");
212 return;
213 }
214
215 CCDBDownloader downloader;
216 std::vector<CURL*> handleVector;
217 std::vector<std::string*> destinations;
218 for (int i = 0; i < 100; i++) {
219 destinations.push_back(new std::string());
220 handleVector.push_back(createTestHandle(destinations.back()));
221 }
222
223 auto curlCodes = downloader.batchBlockingPerform(handleVector);
224 for (CURLcode code : curlCodes) {
225 BOOST_CHECK_EQUAL(code, CURLE_OK);
226 }
227
228 for (CURL* handle : handleVector) {
229 long httpCode;
230 curl_easy_getinfo(handle, CURLINFO_HTTP_CODE, &httpCode);
231 BOOST_CHECK_EQUAL(httpCode, 200);
232 curl_easy_cleanup(handle);
233 }
234
235 for (std::string* dst : destinations) {
236 delete dst;
237 }
238
239 curl_global_cleanup();
240}
241
242BOOST_AUTO_TEST_CASE(test_with_break)
243{
244 if (curl_global_init(CURL_GLOBAL_ALL)) {
245 fprintf(stderr, "Could not init curl\n");
246 return;
247 }
248
249 CCDBDownloader downloader;
250 std::vector<CURL*> handleVector;
251 std::vector<std::string*> destinations;
252 for (int i = 0; i < 100; i++) {
253 destinations.push_back(new std::string());
254 handleVector.push_back(createTestHandle(destinations.back()));
255 }
256
257 auto curlCodes = downloader.batchBlockingPerform(handleVector);
258
259 for (CURLcode code : curlCodes) {
260 BOOST_CHECK_EQUAL(code, CURLE_OK);
261 }
262
263 for (CURL* handle : handleVector) {
264 long httpCode;
265 curl_easy_getinfo(handle, CURLINFO_HTTP_CODE, &httpCode);
266 BOOST_CHECK_EQUAL(httpCode, 200);
267 curl_easy_cleanup(handle);
268 }
269
270 for (std::string* dst : destinations) {
271 delete dst;
272 }
273
274 sleep(10);
275
276 std::vector<CURL*> handleVector2;
277 std::vector<std::string*> destinations2;
278 for (int i = 0; i < 100; i++) {
279 destinations2.push_back(new std::string());
280 handleVector2.push_back(createTestHandle(destinations2.back()));
281 }
282
283 auto curlCodes2 = downloader.batchBlockingPerform(handleVector2);
284 for (CURLcode code : curlCodes2) {
285 BOOST_CHECK_EQUAL(code, CURLE_OK);
286 }
287
288 for (CURL* handle : handleVector2) {
289 long httpCode;
290 curl_easy_getinfo(handle, CURLINFO_HTTP_CODE, &httpCode);
291 BOOST_CHECK_EQUAL(httpCode, 200);
292 curl_easy_cleanup(handle);
293 }
294
295 for (std::string* dst : destinations2) {
296 delete dst;
297 }
298
299 curl_global_cleanup();
300}
301
302void onUVClose(uv_handle_t* handle)
303{
304 if (handle != nullptr) {
305 free(handle);
306 }
307}
308
309void closeAllHandles(uv_handle_t* handle, void* arg)
310{
311 if (!uv_is_closing(handle)) {
312 uv_close(handle, onUVClose);
313 }
314}
315
317{
318 // Mock function to be used by tested timer
319}
320
321BOOST_AUTO_TEST_CASE(external_loop_test)
322{
323 // Prepare uv_loop to be provided to the downloader
324 auto uvLoop = new uv_loop_t();
325 uv_loop_init(uvLoop);
326
327 // Prepare test timer. It will be used to check whether the downloader affects external handles.
328 auto testTimer = (uv_timer_t*)malloc(sizeof(uv_timer_t));
329 uv_timer_init(uvLoop, testTimer);
330 uv_timer_start(testTimer, testTimerCB, 10, 10);
331
332 if (curl_global_init(CURL_GLOBAL_ALL)) {
333 fprintf(stderr, "Could not init curl\n");
334 return;
335 }
336
337 // Regular downloader test
338 auto downloader = new o2::ccdb::CCDBDownloader(uvLoop);
339 std::string dst = "";
340 CURL* handle = createTestHandle(&dst);
341
342 CURLcode curlCode = downloader->perform(handle);
343
344 BOOST_CHECK_EQUAL(curlCode, CURLE_OK);
345
346 long httpCode;
347 curl_easy_getinfo(handle, CURLINFO_HTTP_CODE, &httpCode);
348 BOOST_CHECK_EQUAL(httpCode, 200);
349
350 curl_easy_cleanup(handle);
351 curl_global_cleanup();
352
353 // Check if test timer and external loop are still alive
354 BOOST_CHECK_NE(uv_is_active((uv_handle_t*)testTimer), 0);
355 BOOST_CHECK_NE(uv_loop_alive(uvLoop), 0);
356
357 // Downloader must be closed before uv_loop.
358 // The reason for that are the uv_poll handles attached to the curl multi handle.
359 // The multi handle must be cleaned (via destuctor) before poll handles attached to them are removed (via walking and closing).
360 delete downloader;
361 while (uv_loop_alive(uvLoop) || uv_loop_close(uvLoop) == UV_EBUSY) {
362 uv_walk(uvLoop, closeAllHandles, nullptr);
363 uv_run(uvLoop, UV_RUN_ONCE);
364 }
365 delete uvLoop;
366}
367
368BOOST_AUTO_TEST_CASE(trim_host_url_test)
369{
370 CCDBDownloader downloader;
371 BOOST_CHECK_EQUAL(downloader.trimHostUrl("http://localhost:8080"), "http://localhost:8080");
372 BOOST_CHECK_EQUAL(downloader.trimHostUrl("http://localhost"), "http://localhost");
373 BOOST_CHECK_EQUAL(downloader.trimHostUrl("http://localhost:8080/some/path"), "http://localhost:8080");
374 BOOST_CHECK_EQUAL(downloader.trimHostUrl("http://localhost/some/path"), "http://localhost");
375 BOOST_CHECK_EQUAL(downloader.trimHostUrl("http://localhost:8080/Task/Detector/1?HTTPOnly=true"), "http://localhost:8080");
376}
377
378} // namespace ccdb
379} // namespace o2
struct uv_timer_s uv_timer_t
struct uv_handle_s uv_handle_t
struct uv_loop_s uv_loop_t
int32_t i
double num
StringRef key
CURLcode perform(CURL *handle)
void asynchSchedule(CURL *handle, size_t *requestCounter)
std::string trimHostUrl(std::string full_host_url) const
std::vector< CURLcode > batchBlockingPerform(std::vector< CURL * > const &handleVector)
GLuint buffer
Definition glcorearb.h:655
GLsizeiptr size
Definition glcorearb.h:659
GLuint index
Definition glcorearb.h:781
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLenum GLenum dst
Definition glcorearb.h:1767
GLboolean * data
Definition glcorearb.h:298
CURL * createTestHandle(std::string *dst)
std::string uniqueAgentID()
void closeAllHandles(uv_handle_t *handle, void *arg)
std::vector< CURL * > prepareAsyncHandles(size_t num, std::vector< o2::pmr::vector< char > * > &dests)
void testTimerCB(uv_timer_t *handle)
BOOST_AUTO_TEST_CASE(asynch_schedule_test)
long getCurrentTimestamp()
returns the timestamp in long corresponding to "now"
size_t writeCallbackNoLambda(void *contents, size_t size, size_t nmemb, void *chunkptr)
void onUVClose(uv_handle_t *handle)
size_t CurlWrite_CallbackFunc_StdString2(void *contents, size_t size, size_t nmemb, std::string *s)
Definition CcdbApi.cxx:1176
std::vector< T, o2::pmr::polymorphic_allocator< T > > vector
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
Defining DataPointCompositeObject explicitly as copiable.
o2::pmr::vector< char > * object
static std::string getRandomString(int length)
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
BOOST_CHECK_EQUAL(triggersD.size(), triggers.size())