Code

store: Don't overwrite attribute values if the new timestamp is too old.
[sysdb.git] / t / unit / core / store_test.c
1 /*
2  * SysDB - t/unit/core/store_test.c
3  * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 #include "core/store.h"
29 #include "core/store-private.h"
30 #include "libsysdb_test.h"
32 #include <check.h>
33 #include <string.h>
35 static void
36 populate(void)
37 {
38         sdb_data_t datum;
40         sdb_store_host("h1", 1);
41         sdb_store_host("h2", 1);
43         datum.type = SDB_TYPE_STRING;
44         datum.data.string = "v1";
45         sdb_store_attribute("h1", "k1", &datum, 1);
46         datum.data.string = "v2";
47         sdb_store_attribute("h1", "k2", &datum, 2);
48         datum.data.string = "v3";
49         sdb_store_attribute("h1", "k3", &datum, 2);
51         /* make sure that older updates don't overwrite existing values */
52         datum.data.string = "fail";
53         sdb_store_attribute("h1", "k2", &datum, 1);
54         sdb_store_attribute("h1", "k3", &datum, 2);
56         sdb_store_service("h2", "s1", 1);
57         sdb_store_service("h2", "s2", 1);
58 } /* populate */
60 START_TEST(test_store_host)
61 {
62         struct {
63                 const char *name;
64                 sdb_time_t  last_update;
65                 int         expected;
66         } golden_data[] = {
67                 { "a", 1, 0 },
68                 { "a", 2, 0 },
69                 { "a", 1, 1 },
70                 { "b", 1, 0 },
71                 { "b", 1, 1 },
72                 { "A", 1, 1 }, /* case-insensitive */
73                 { "A", 3, 0 },
74         };
76         struct {
77                 const char *name;
78                 _Bool       has;
79         } golden_hosts[] = {
80                 { "a", 1 == 1 },
81                 { "b", 1 == 1 },
82                 { "c", 0 == 1 },
83                 { "A", 1 == 1 },
84         };
86         size_t i;
88         for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
89                 int status;
91                 status = sdb_store_host(golden_data[i].name,
92                                 golden_data[i].last_update);
93                 fail_unless(status == golden_data[i].expected,
94                                 "sdb_store_host(%s, %d) = %d; expected: %d",
95                                 golden_data[i].name, (int)golden_data[i].last_update,
96                                 status, golden_data[i].expected);
97         }
99         for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_hosts); ++i) {
100                 _Bool has;
102                 has = sdb_store_has_host(golden_hosts[i].name);
103                 fail_unless(has == golden_hosts[i].has,
104                                 "sdb_store_has_host(%s) = %d; expected: %d",
105                                 golden_hosts[i].name, has, golden_hosts[i].has);
106         }
108 END_TEST
110 START_TEST(test_store_get_host)
112         char *golden_hosts[] = { "a", "b", "c" };
113         char *unknown_hosts[] = { "x", "y", "z" };
114         size_t i;
116         for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_hosts); ++i) {
117                 int status = sdb_store_host(golden_hosts[i], 1);
118                 fail_unless(status >= 0,
119                                 "sdb_store_host(%s) = %d; expected: >=0",
120                                 golden_hosts[i], status);
121         }
123         for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_hosts); ++i) {
124                 sdb_store_obj_t *sobj1, *sobj2;
125                 int ref_cnt;
127                 fail_unless(sdb_store_has_host(golden_hosts[i]),
128                                 "sdb_store_has_host(%s) = FALSE; expected: TRUE",
129                                 golden_hosts[i]);
131                 sobj1 = sdb_store_get_host(golden_hosts[i]);
132                 fail_unless(sobj1 != NULL,
133                                 "sdb_store_get_host(%s) = NULL; expected: <host>",
134                                 golden_hosts[i]);
135                 ref_cnt = SDB_OBJ(sobj1)->ref_cnt;
137                 fail_unless(ref_cnt > 1,
138                                 "sdb_store_get_host(%s) did not increment ref count: "
139                                 "got: %d; expected: >1", golden_hosts[i], ref_cnt);
141                 sobj2 = sdb_store_get_host(golden_hosts[i]);
142                 fail_unless(sobj2 != NULL,
143                                 "sdb_store_get_host(%s) = NULL; expected: <host>",
144                                 golden_hosts[i]);
146                 fail_unless(sobj1 == sobj2,
147                                 "sdb_store_get_host(%s) returned different objects "
148                                 "in successive calls", golden_hosts[i]);
149                 fail_unless(SDB_OBJ(sobj2)->ref_cnt == ref_cnt + 1,
150                                 "sdb_store_get_hosts(%s) did not increment ref count "
151                                 "(first call: %d; second call: %d)",
152                                 golden_hosts[i], ref_cnt, SDB_OBJ(sobj2)->ref_cnt);
154                 sdb_object_deref(SDB_OBJ(sobj1));
155                 sdb_object_deref(SDB_OBJ(sobj2));
156         }
157         for (i = 0; i < SDB_STATIC_ARRAY_LEN(unknown_hosts); ++i) {
158                 sdb_store_obj_t *sobj;
160                 fail_unless(!sdb_store_has_host(unknown_hosts[i]),
161                                 "sdb_store_has_host(%s) = TRUE; expected: FALSE",
162                                 unknown_hosts[i]);
164                 sobj = sdb_store_get_host(unknown_hosts[i]);
165                 fail_unless(!sobj, "sdb_store_get_host(%s) = <host:%s>; expected: NULL",
166                                 unknown_hosts[i], sobj ? SDB_OBJ(sobj)->name : "NULL");
167         }
169 END_TEST
171 START_TEST(test_store_attr)
173         struct {
174                 const char *host;
175                 const char *key;
176                 char       *value;
177                 sdb_time_t  last_update;
178                 int         expected;
179         } golden_data[] = {
180                 { "k", "k",  "v",  1, -1 },
181                 { "k", "k",  "v",  1, -1 }, /* retry to ensure the host is not created */
182                 { "l", "k1", "v1", 1,  0 },
183                 { "l", "k1", "v2", 2,  0 },
184                 { "l", "k1", "v3", 2,  1 },
185                 { "l", "k2", "v1", 1,  0 },
186                 { "m", "k",  "v1", 1,  0 },
187                 { "m", "k",  "v2", 1,  1 },
188         };
190         size_t i;
192         sdb_store_host("l", 1);
193         sdb_store_host("m", 1);
194         for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
195                 sdb_data_t datum;
196                 int status;
198                 /* XXX: test other types as well */
199                 datum.type = SDB_TYPE_STRING;
200                 datum.data.string = golden_data[i].value;
202                 status = sdb_store_attribute(golden_data[i].host,
203                                 golden_data[i].key, &datum,
204                                 golden_data[i].last_update);
205                 fail_unless(status == golden_data[i].expected,
206                                 "sdb_store_attribute(%s, %s, %s, %d) = %d; expected: %d",
207                                 golden_data[i].host, golden_data[i].key, golden_data[i].value,
208                                 golden_data[i].last_update, status, golden_data[i].expected);
209         }
211 END_TEST
213 START_TEST(test_store_service)
215         struct {
216                 const char *host;
217                 const char *svc;
218                 sdb_time_t  last_update;
219                 int         expected;
220         } golden_data[] = {
221                 { "k", "s",  1, -1 },
222                 { "k", "s",  1, -1 }, /* retry to ensure the host is not created */
223                 { "l", "s1", 1,  0 },
224                 { "l", "s1", 2,  0 },
225                 { "l", "s1", 2,  1 },
226                 { "l", "s2", 1,  0 },
227                 { "m", "s",  1,  0 },
228                 { "m", "s",  1,  1 },
229         };
231         size_t i;
233         sdb_store_host("m", 1);
234         sdb_store_host("l", 1);
235         for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
236                 int status;
238                 status = sdb_store_service(golden_data[i].host,
239                                 golden_data[i].svc, golden_data[i].last_update);
240                 fail_unless(status == golden_data[i].expected,
241                                 "sdb_store_service(%s, %s, %d) = %d; expected: %d",
242                                 golden_data[i].host, golden_data[i].svc,
243                                 golden_data[i].last_update, status, golden_data[i].expected);
244         }
246 END_TEST
248 START_TEST(test_store_service_attr)
250         struct {
251                 const char *host;
252                 const char *svc;
253                 const char *attr;
254                 const sdb_data_t value;
255                 sdb_time_t  last_update;
256                 int expected;
257         } golden_data[] = {
258                 { "k", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, -1 },
259                 /* retry, it should still fail */
260                 { "k", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, -1 },
261                 { "l", "sX", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, -1 },
262                 /* retry, it should still fail */
263                 { "l", "sX", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, -1 },
264                 { "l", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1,  0 },
265                 { "l", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1,  1 },
266                 { "l", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 2,  0 },
267                 { "l", "s1", "a2", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1,  0 },
268                 { "l", "s1", "a2", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1,  1 },
269                 { "l", "s2", "a2", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1,  0 },
270                 { "m", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1,  0 },
271         };
273         size_t i;
275         sdb_store_host("m", 1);
276         sdb_store_host("l", 1);
277         sdb_store_service("m", "s1", 1);
278         sdb_store_service("l", "s1", 1);
279         sdb_store_service("l", "s2", 1);
281         for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
282                 int status;
284                 status = sdb_store_service_attr(golden_data[i].host,
285                                 golden_data[i].svc, golden_data[i].attr,
286                                 &golden_data[i].value, golden_data[i].last_update);
287                 fail_unless(status == golden_data[i].expected,
288                                 "sdb_store_service_attr(%s, %s, %s, %d, %d) = %d; "
289                                 "expected: %d", golden_data[i].host, golden_data[i].svc,
290                                 golden_data[i].attr, golden_data[i].value.data.integer,
291                                 golden_data[i].last_update, status, golden_data[i].expected);
292         }
294 END_TEST
296 static void
297 verify_json_output(sdb_strbuf_t *buf, const char *expected, int flags)
299         int pos;
300         size_t len1, len2;
301         size_t i;
303         len1 = strlen(sdb_strbuf_string(buf));
304         len2 = strlen(expected);
306         pos = -1;
307         if (len1 != len2)
308                 pos = (int)(len1 <= len2 ? len1 : len2);
310         for (i = 0; i < (len1 <= len2 ? len1 : len2); ++i) {
311                 if (sdb_strbuf_string(buf)[i] != expected[i]) {
312                         pos = (int)i;
313                         break;
314                 }
315         }
317         fail_unless(pos == -1,
318                         "sdb_store_tojson(%x) returned unexpected result\n"
319                         "         got: %s\n              %*s\n    expected: %s",
320                         flags, sdb_strbuf_string(buf), pos + 1, "^", expected);
321 } /* verify_json_output */
323 START_TEST(test_store_tojson)
325         sdb_strbuf_t *buf;
326         size_t i;
328         struct {
329                 int flags;
330                 const char *expected;
331         } golden_data[] = {
332                 { 0, "{\"hosts\":["
333                                 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
334                                         "\"update_interval\": \"0s\", \"backends\": [], "
335                                         "\"attributes\": ["
336                                                 "{\"name\": \"k1\", \"value\": \"v1\", "
337                                                         "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
338                                                         "\"update_interval\": \"0s\", \"backends\": []},"
339                                                 "{\"name\": \"k2\", \"value\": \"v2\", "
340                                                         "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
341                                                         "\"update_interval\": \"0s\", \"backends\": []},"
342                                                 "{\"name\": \"k3\", \"value\": \"v3\", "
343                                                         "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
344                                                         "\"update_interval\": \"0s\", \"backends\": []}"
345                                         "], "
346                                         "\"services\": []},"
347                                 "{\"name\": \"h2\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
348                                         "\"update_interval\": \"0s\", \"backends\": [], "
349                                         "\"attributes\": [], "
350                                         "\"services\": ["
351                                                 "{\"name\": \"s1\", "
352                                                         "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
353                                                         "\"update_interval\": \"0s\", \"backends\": []},"
354                                                 "{\"name\": \"s2\", "
355                                                         "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
356                                                         "\"update_interval\": \"0s\", \"backends\": []}"
357                                         "]}"
358                         "]}" },
359                 { SDB_SKIP_SERVICES,
360                         "{\"hosts\":["
361                                 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
362                                         "\"update_interval\": \"0s\", \"backends\": [], "
363                                         "\"attributes\": ["
364                                                 "{\"name\": \"k1\", \"value\": \"v1\", "
365                                                         "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
366                                                         "\"update_interval\": \"0s\", \"backends\": []},"
367                                                 "{\"name\": \"k2\", \"value\": \"v2\", "
368                                                         "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
369                                                         "\"update_interval\": \"0s\", \"backends\": []},"
370                                                 "{\"name\": \"k3\", \"value\": \"v3\", "
371                                                         "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
372                                                         "\"update_interval\": \"0s\", \"backends\": []}"
373                                         "]},"
374                                 "{\"name\": \"h2\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
375                                         "\"update_interval\": \"0s\", \"backends\": [], "
376                                         "\"attributes\": []}"
377                         "]}" },
378                 { SDB_SKIP_ATTRIBUTES,
379                         "{\"hosts\":["
380                                 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
381                                         "\"update_interval\": \"0s\", \"backends\": [], "
382                                         "\"services\": []},"
383                                 "{\"name\": \"h2\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
384                                         "\"update_interval\": \"0s\", \"backends\": [], "
385                                         "\"services\": ["
386                                                 "{\"name\": \"s1\", "
387                                                         "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
388                                                         "\"update_interval\": \"0s\", \"backends\": []},"
389                                                 "{\"name\": \"s2\", "
390                                                         "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
391                                                         "\"update_interval\": \"0s\", \"backends\": []}"
392                                         "]}"
393                         "]}" },
394                 { SDB_SKIP_SERVICES | SDB_SKIP_ATTRIBUTES,
395                         "{\"hosts\":["
396                                 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
397                                         "\"update_interval\": \"0s\", \"backends\": []},"
398                                 "{\"name\": \"h2\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
399                                         "\"update_interval\": \"0s\", \"backends\": []}"
400                         "]}" },
401         };
403         buf = sdb_strbuf_create(0);
404         fail_unless(buf != NULL, "INTERNAL ERROR: failed to create string buffer");
405         populate();
407         for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
408                 int status;
410                 sdb_strbuf_clear(buf);
412                 status = sdb_store_tojson(buf, golden_data[i].flags);
413                 fail_unless(status == 0,
414                                 "sdb_store_tojson(%x) = %d; expected: 0",
415                                 golden_data[i].flags, status);
417                 verify_json_output(buf, golden_data[i].expected, golden_data[i].flags);
418         }
419         sdb_strbuf_destroy(buf);
421 END_TEST
423 START_TEST(test_interval)
425         sdb_store_obj_t *host;
427         /* 10 us interval */
428         sdb_store_host("host", 10);
429         sdb_store_host("host", 20);
430         sdb_store_host("host", 30);
431         sdb_store_host("host", 40);
433         host = sdb_store_get_host("host");
434         fail_unless(host != NULL,
435                         "INTERNAL ERROR: store doesn't have host after adding it");
437         fail_unless(host->interval == 10,
438                         "sdb_store_host() did not calculate interval correctly: "
439                         "got: %"PRIscTIME"; expected: %"PRIscTIME, host->interval, 10);
441         /* multiple updates for the same timestamp don't modify the interval */
442         sdb_store_host("host", 40);
443         sdb_store_host("host", 40);
444         sdb_store_host("host", 40);
445         sdb_store_host("host", 40);
447         fail_unless(host->interval == 10,
448                         "sdb_store_host() changed interval when doing multiple updates "
449                         "using the same timestamp; got: %"PRIscTIME"; "
450                         "expected: %"PRIscTIME, host->interval, 10);
452         /* multiple updates using an timestamp don't modify the interval */
453         sdb_store_host("host", 20);
454         sdb_store_host("host", 20);
455         sdb_store_host("host", 20);
456         sdb_store_host("host", 20);
458         fail_unless(host->interval == 10,
459                         "sdb_store_host() changed interval when doing multiple updates "
460                         "using an old timestamp; got: %"PRIscTIME"; expected: %"PRIscTIME,
461                         host->interval, 10);
463         /* new interval: 20 us */
464         sdb_store_host("host", 60);
465         fail_unless(host->interval == 11,
466                         "sdb_store_host() did not calculate interval correctly: "
467                         "got: %"PRIscTIME"; expected: %"PRIscTIME, host->interval, 11);
469         /* new interval: 40 us */
470         sdb_store_host("host", 100);
471         fail_unless(host->interval == 13,
472                         "sdb_store_host() did not calculate interval correctly: "
473                         "got: %"PRIscTIME"; expected: %"PRIscTIME, host->interval, 11);
475         sdb_object_deref(SDB_OBJ(host));
477 END_TEST
479 static int
480 iter_incr(sdb_store_obj_t *obj, void *user_data)
482         intptr_t *i = user_data;
484         fail_unless(obj != NULL,
485                         "sdb_store_iterate callback received NULL obj; expected: "
486                         "<store base obj>");
487         fail_unless(i != NULL,
488                         "sdb_store_iterate callback received NULL user_data; "
489                         "expected: <pointer to data>");
491         ++(*i);
492         return 0;
493 } /* iter_incr */
495 static int
496 iter_error(sdb_store_obj_t *obj, void *user_data)
498         intptr_t *i = user_data;
500         fail_unless(obj != NULL,
501                         "sdb_store_iterate callback received NULL obj; expected: "
502                         "<store base obj>");
503         fail_unless(i != NULL,
504                         "sdb_store_iterate callback received NULL user_data; "
505                         "expected: <pointer to data>");
507         ++(*i);
508         return -1;
509 } /* iter_error */
511 START_TEST(test_iterate)
513         intptr_t i = 0;
514         int check;
516         /* empty store */
517         check = sdb_store_iterate(iter_incr, &i);
518         fail_unless(check == -1,
519                         "sdb_store_iterate(), empty store = %d; expected: -1", check);
520         fail_unless(i == 0,
521                         "sdb_store_iterate called callback %d times; expected: 0", (int)i);
523         populate();
525         check = sdb_store_iterate(iter_incr, &i);
526         fail_unless(check == 0,
527                         "sdb_store_iterate() = %d; expected: 0", check);
528         fail_unless(i == 2,
529                         "sdb_store_iterate called callback %d times; expected: 1", (int)i);
531         i = 0;
532         check = sdb_store_iterate(iter_error, &i);
533         fail_unless(check == -1,
534                         "sdb_store_iterate(), error callback = %d; expected: -1", check);
535         fail_unless(i == 1,
536                         "sdb_store_iterate called callback %d times "
537                         "(callback returned error); expected: 1", (int)i);
539 END_TEST
541 Suite *
542 core_store_suite(void)
544         Suite *s = suite_create("core::store");
545         TCase *tc;
547         tc = tcase_create("core");
548         tcase_add_test(tc, test_store_tojson);
549         tcase_add_test(tc, test_store_host);
550         tcase_add_test(tc, test_store_get_host);
551         tcase_add_test(tc, test_store_attr);
552         tcase_add_test(tc, test_store_service);
553         tcase_add_test(tc, test_store_service_attr);
554         tcase_add_test(tc, test_interval);
555         tcase_add_test(tc, test_iterate);
556         tcase_add_unchecked_fixture(tc, NULL, sdb_store_clear);
557         suite_add_tcase(s, tc);
559         return s;
560 } /* core_store_suite */
562 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */