test_journal.c 20.9 KB
Newer Older
1
/*  Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <unistd.h>
#include <sys/stat.h>
#include <tap/basic.h>
#include <tap/files.h>

#include "libknot/libknot.h"
#include "knot/journal/journal.c"
#include "knot/zone/zone.h"
#include "knot/zone/zone-diff.h"
#include "libknot/rrtype/soa.h"
#include "test_conf.h"

#define RAND_RR_LABEL 16
#define RAND_RR_PAYLOAD 64
#define MIN_SOA_SIZE 22

Daniel Salzman's avatar
Daniel Salzman committed
38 39 40 41
char *test_dir_name;
journal_db_t *db;
journal_t *j;
const knot_dname_t *apex = (const uint8_t *)"\4test";
42

43 44 45
static void set_conf(int zonefile_sync, size_t journal_usage)
{
	char conf_str[512];
Daniel Salzman's avatar
Daniel Salzman committed
46 47 48 49 50 51 52
	snprintf(conf_str, sizeof(conf_str),
	         "zone:\n"
	         " - domain: %s\n"
	         "   zonefile-sync: %d\n"
	         "   max-journal-usage: %zu\n"
	         "   max-journal-depth: 1000\n",
	         (const char *)(apex + 1), zonefile_sync, journal_usage);
53
	int ret = test_conf(conf_str, NULL);
54
	(void)ret;
55 56 57
	assert(ret == KNOT_EOK);
}

58 59 60 61 62
static void unset_conf(void)
{
	conf_update(NULL, CONF_UPD_FNONE);
}

63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
/*! \brief Generate random string with given length. */
static int randstr(char* dst, size_t len)
{
	for (int i = 0; i < len - 1; ++i) {
		dst[i] = '0' + (int) (('Z'-'0') * (rand() / (RAND_MAX + 1.0)));
	}
	dst[len - 1] = '\0';

	return 0;
}

/*! \brief Init RRSet with type SOA and given serial. */
static void init_soa(knot_rrset_t *rr, const uint32_t serial, const knot_dname_t *apex)
{
	knot_rrset_init(rr, knot_dname_copy(apex, NULL), KNOT_RRTYPE_SOA, KNOT_CLASS_IN);

	uint8_t soa_data[MIN_SOA_SIZE] = { 0 };
	int ret = knot_rrset_add_rdata(rr, soa_data, sizeof(soa_data), 3600, NULL);
	knot_soa_serial_set(&rr->rrs, serial);
	(void)ret;
	assert(ret == KNOT_EOK);
}

/*! \brief Init RRSet with type TXT, random owner and random payload. */
static void init_random_rr(knot_rrset_t *rr , const knot_dname_t *apex)
{
	/* Create random label. */
	char owner[RAND_RR_LABEL + knot_dname_size(apex)];
	owner[0] = RAND_RR_LABEL - 1;
	randstr(owner + 1, RAND_RR_LABEL);

	/* Append zone apex. */
	memcpy(owner + RAND_RR_LABEL, apex, knot_dname_size(apex));
	knot_rrset_init(rr, knot_dname_copy((knot_dname_t *)owner, NULL),
			KNOT_RRTYPE_TXT, KNOT_CLASS_IN);

	/* Create random RDATA. */
	uint8_t txt[RAND_RR_PAYLOAD + 1];
	txt[0] = RAND_RR_PAYLOAD - 1;
	randstr((char *)(txt + 1), RAND_RR_PAYLOAD);

	int ret = knot_rrset_add_rdata(rr, txt, RAND_RR_PAYLOAD, 3600, NULL);
	(void)ret;
	assert(ret == KNOT_EOK);
}

/*! \brief Init changeset with random changes. */
Daniel Salzman's avatar
Daniel Salzman committed
110
static void init_random_changeset(changeset_t *ch, const uint32_t from, const uint32_t to,
111
                                  const size_t size, const knot_dname_t *apex, bool is_bootstrap)
112 113 114 115
{
	// Add SOAs
	knot_rrset_t soa;

116 117 118 119 120 121 122 123
	if (is_bootstrap) {
		ch->soa_from = NULL;
	} else {
		init_soa(&soa, from, apex);
		ch->soa_from = knot_rrset_copy(&soa, NULL);
		assert(ch->soa_from);
		knot_rrset_clear(&soa, NULL);
	}
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140

	init_soa(&soa, to, apex);
	ch->soa_to = knot_rrset_copy(&soa, NULL);
	assert(ch->soa_to);
	knot_rrset_clear(&soa, NULL);

	// Add RRs to add section
	for (size_t i = 0; i < size / 2; ++i) {
		knot_rrset_t rr;
		init_random_rr(&rr, apex);
		int ret = changeset_add_addition(ch, &rr, 0);
		(void)ret;
		assert(ret == KNOT_EOK);
		knot_rrset_clear(&rr, NULL);
	}

	// Add RRs to remove section
141
	for (size_t i = 0; i < size / 2 && !is_bootstrap; ++i) {
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
		knot_rrset_t rr;
		init_random_rr(&rr, apex);
		int ret = changeset_add_removal(ch, &rr, 0);
		(void)ret;
		assert(ret == KNOT_EOK);
		knot_rrset_clear(&rr, NULL);
	}
}

static void changeset_set_soa_serials(changeset_t *ch, uint32_t from, uint32_t to,
				      const knot_dname_t *apex)
{
	knot_rrset_t soa;

	init_soa(&soa, from, apex);
	knot_rrset_free(&ch->soa_from, NULL);
	ch->soa_from = knot_rrset_copy(&soa, NULL);
	assert(ch->soa_from);
	knot_rrset_clear(&soa, NULL);

	init_soa(&soa, to, apex);
	knot_rrset_free(&ch->soa_to, NULL);
	ch->soa_to = knot_rrset_copy(&soa, NULL);
	assert(ch->soa_to);
	knot_rrset_clear(&soa, NULL);
}

/*! \brief Compare two changesets for equality. */
static bool changesets_eq(const changeset_t *ch1, changeset_t *ch2)
{
	if (changeset_size(ch1) != changeset_size(ch2)) {
		return false;
	}

	changeset_iter_t it1;
	changeset_iter_all(&it1, ch1);
	changeset_iter_t it2;
	changeset_iter_all(&it2, ch2);

	knot_rrset_t rr1 = changeset_iter_next(&it1);
	knot_rrset_t rr2 = changeset_iter_next(&it2);
	bool ret = true;
	while (!knot_rrset_empty(&rr1)) {
		if (!knot_rrset_equal(&rr1, &rr2, KNOT_RRSET_COMPARE_WHOLE)) {
			ret = false;
			break;
		}
		rr1 = changeset_iter_next(&it1);
		rr2 = changeset_iter_next(&it2);
	}

	changeset_iter_clear(&it1);
	changeset_iter_clear(&it2);

	return ret;
}

static bool changesets_list_eq(list_t *l1, list_t *l2)
{
	node_t *n = NULL;
	node_t *k = HEAD(*l2);
	WALK_LIST(n, *l1) {
		if (k == NULL) {
			return false;
		}

		changeset_t *ch1 = (changeset_t *) n;
		changeset_t *ch2 = (changeset_t *) k;
		if (!changesets_eq(ch1, ch2)) {
			return false;
		}

		k = k->next;
	}

	if (k->next != NULL) {
		return false;
	}

	return true;
}

/*! \brief Test a list of changesets for continuity. */
static bool test_continuity(list_t *l)
{
	node_t *n = NULL;
	uint32_t key1, key2;
	WALK_LIST(n, *l) {
		if (n == TAIL(*l)) {
			break;
		}
		changeset_t *ch1 = (changeset_t *) n;
		changeset_t *ch2 = (changeset_t *) n->next;
		key1 = knot_soa_serial(&ch1->soa_to->rrs);
		key2 = knot_soa_serial(&ch2->soa_from->rrs);
		if (key1 != key2) {
			return KNOT_EINVAL;
		}
	}

	return KNOT_EOK;
}

static void test_journal_db(void)
{
	int ret, ret2 = KNOT_EOK;

249
	ret = journal_db_init(&db, test_dir_name, 2 * 1024 * 1024, JOURNAL_MODE_ASYNC);
250
	is_int(KNOT_EOK, ret, "journal: init db (%d)", ret);
251

252
	ret = journal_open_db(&db);
253
	is_int(KNOT_EOK, ret, "journal: open db (%d)", ret);
254

255
	journal_db_close(&db);
256 257
	ok(db == NULL, "journal: close and destroy db");

258
	ret = journal_db_init(&db, test_dir_name, 4 * 1024 * 1024, JOURNAL_MODE_ASYNC);
259
	if (ret == KNOT_EOK) ret2 = journal_open_db(&db);
260
	ok(ret == KNOT_EOK && ret2 == KNOT_EOK, "journal: open with bigger mapsize (%d, %d)", ret, ret2);
261
	journal_db_close(&db);
262

263
	ret = journal_db_init(&db, test_dir_name, 1024 * 1024, JOURNAL_MODE_ASYNC);
264
	if (ret == KNOT_EOK) ret2 = journal_open_db(&db);
265
	ok(ret == KNOT_EOK && ret2 == KNOT_EOK, "journal: open with smaller mapsize (%d, %d)", ret, ret2);
266
	journal_db_close(&db);
Daniel Salzman's avatar
Daniel Salzman committed
267
}
268 269 270 271 272 273

/*! \brief Test behavior with real changesets. */
static void test_store_load(void)
{
	int ret, ret2 = KNOT_EOK;

Daniel Salzman's avatar
Daniel Salzman committed
274 275
	set_conf(1000, 512 * 1024);

276 277 278
	j = journal_new();
	ok(j != NULL, "journal: new");

279
	ret = journal_db_init(&db, test_dir_name, (512 + 1024) * 1024, JOURNAL_MODE_ASYNC);
280
	if (ret == KNOT_EOK) ret2 = journal_open(j, &db, apex);
281
	is_int(KNOT_EOK, ret, "journal: open (%d, %d)", ret, ret2);
282 283 284

	/* Save and load changeset. */
	changeset_t *m_ch = changeset_new(apex);
285
	init_random_changeset(m_ch, 0, 1, 128, apex, false);
286
	ret = journal_store_changeset(j, m_ch);
287
	is_int(KNOT_EOK, ret, "journal: store changeset (%d)", ret);
288
	ret = journal_check(j, JOURNAL_CHECK_INFO);
289
	is_int(KNOT_EOK, ret, "journal check (%d)", ret);
290 291 292 293 294 295
	list_t l, k;
	init_list(&l);
	init_list(&k);
	ret = journal_load_changesets(j, &l, 0);
	add_tail(&k, &m_ch->n);
	ok(ret == KNOT_EOK && changesets_list_eq(&l, &k), "journal: load changeset (%d)", ret);
296
	ret = journal_check(j, JOURNAL_CHECK_STDERR);
297
	is_int(KNOT_EOK, ret, "journal check (%d)", ret);
298 299

	changesets_free(&l);
300
	changesets_free(&k);
Daniel Salzman's avatar
Daniel Salzman committed
301

302 303
	/* Flush the journal. */
	ret = journal_flush(j);
304
	is_int(KNOT_EOK, ret, "journal: first and simple flush (%d)", ret);
305
	ret = journal_check(j, JOURNAL_CHECK_STDERR);
306
	is_int(KNOT_EOK, ret, "journal check (%d)", ret);
307 308 309 310 311 312 313
	init_list(&l);
	init_list(&k);

	/* Fill the journal. */
	ret = KNOT_EOK;
	uint32_t serial = 1;
	for (; ret == KNOT_EOK; ++serial) {
314
		changeset_t *m_ch2 = changeset_new(apex);
315
		init_random_changeset(m_ch2, serial, serial + 1, 128, apex, false);
316
		ret = journal_store_changeset(j, m_ch2);
317
		if (ret != KNOT_EOK) {
318
			changeset_free(m_ch2);
319 320
			break;
		}
321
		add_tail(&k, &m_ch2->n);
322
	}
323
	is_int(KNOT_EBUSY, ret, "journal: overfill with changesets (%d inserted) (%d should= %d)",
Daniel Salzman's avatar
Daniel Salzman committed
324
	   serial, ret, KNOT_EBUSY);
325
	ret = journal_check(j, JOURNAL_CHECK_STDERR);
326
	is_int(KNOT_EOK, ret, "journal check (%d)", ret);
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341

	/* Load all changesets stored until now. */
	ret = journal_load_changesets(j, &l, 1);
	ok(ret == KNOT_EOK && changesets_list_eq(&l, &k), "journal: load changesets (%d)", ret);

	changesets_free(&l);
	init_list(&l);
	ret = journal_load_changesets(j, &l, 1);
	ok(ret == KNOT_EOK && changesets_list_eq(&l, &k), "journal: re-load changesets (%d)", ret);

	changesets_free(&l);
	init_list(&l);

	/* Flush the journal. */
	ret = journal_flush(j);
342
	is_int(KNOT_EOK, ret, "journal: second flush (%d)", ret);
343
	ret = journal_check(j, JOURNAL_CHECK_STDERR);
344
	is_int(KNOT_EOK, ret, "journal check (%d)", ret);
345 346 347 348 349 350 351 352 353 354 355 356 357

	/* Test whether the journal kept changesets after flush. */
	ret = journal_load_changesets(j, &l, 1);
	ok(ret == KNOT_EOK && changesets_list_eq(&l, &k), "journal: load right after flush (%d)", ret);

	changesets_free(&k);
	changesets_free(&l);
	init_list(&k);
	init_list(&l);

	/* Store next changeset. */
	changeset_t ch;
	changeset_init(&ch, apex);
358
	init_random_changeset(&ch, serial, serial + 1, 128, apex, false);
359
	ret = journal_store_changeset(j, &ch);
360
	changeset_clear(&ch);
361
	is_int(KNOT_EOK, ret, "journal: store after flush (%d)", ret);
362
	ret = journal_check(j, JOURNAL_CHECK_STDERR);
363
	is_int(KNOT_EOK, ret, "journal check (%d)", ret);
364 365 366 367 368

	/* Load last changesets. */
	init_list(&l);
	ret = journal_load_changesets(j, &l, serial);
	changesets_free(&l);
369
	is_int(KNOT_EOK, ret, "journal: load changesets after flush (%d)", ret);
370 371 372

	/* Flush the journal again. */
	ret = journal_flush(j);
373
	is_int(KNOT_EOK, ret, "journal: flush again (%d)", ret);
374
	ret = journal_check(j, JOURNAL_CHECK_STDERR);
375
	is_int(KNOT_EOK, ret, "journal check (%d)", ret);
376 377 378 379

	/* Fill the journal using a list. */
	uint32_t m_serial = 1;
	for (; m_serial < serial / 2; ++m_serial) {
380
		changeset_t *m_ch7 = changeset_new(apex);
381
		init_random_changeset(m_ch7, m_serial, m_serial + 1, 128, apex, false);
382
		add_tail(&l, &m_ch7->n);
383 384
	}
	ret = journal_store_changesets(j, &l);
385
	is_int(KNOT_EOK, ret, "journal: fill with changesets using a list (%d inserted)", m_serial);
386
	ret = journal_check(j, JOURNAL_CHECK_STDERR);
387
	is_int(KNOT_EOK, ret, "journal check (%d)", ret);
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403

	/* Cleanup. */
	changesets_free(&l);
	init_list(&l);

	/* Load all previous changesets. */
	ret = journal_load_changesets(j, &l, 1);
	ok(ret == KNOT_EOK && knot_soa_serial(&((changeset_t *)TAIL(l))->soa_to->rrs) == m_serial,
	   "journal: load all changesets");

	/* Check for changeset ordering. */
	ok(test_continuity(&l) == KNOT_EOK, "journal: changesets are in order");

	/* Cleanup. */
	changesets_free(&l);
	init_list(&l);
404
	ret = journal_flush(j);
405
	is_int(KNOT_EOK, ret, "journal: allways ok journal_flush 0");
406
	ret = drop_journal(j, NULL); /* Clear the journal for the collision test */
407
	is_int(KNOT_EOK, ret, "journal: allways ok drop_journal");
408 409 410 411 412

	/* Test for serial number collision handling. We insert changesets
	 * with valid serial sequence that overflows and then collides with itself.
	 * The sequence is 0 -> 1 -> 2 -> 2147483647 -> 4294967294 -> 1 which should
	 * remove changesets 0->1 and 1->2. */
413 414
	ok(EMPTY_LIST(k), "journal: empty list k");
	ok(EMPTY_LIST(l), "journal: empty list l");
415
	changeset_t *m_ch3 = changeset_new(apex);
416
	init_random_changeset(m_ch3, 0, 1, 128, apex, false);
417
	ret = journal_store_changeset(j, m_ch3);
418
	is_int(KNOT_EOK, ret, "journal: allways ok journal_store_changeset 1");
419
	changeset_set_soa_serials(m_ch3, 1, 2, apex);
420
	ret = journal_store_changeset(j, m_ch3);
421
	is_int(KNOT_EOK, ret, "journal: allways ok journal_store_changeset 2");
422 423
	changeset_set_soa_serials(m_ch3, 2, 2147483647, apex);
	add_tail(&k, &m_ch3->n);
424
	ret = journal_store_changeset(j, m_ch3);
425
	is_int(KNOT_EOK, ret, "journal: allways ok journal_store_changeset 3");
426
	changeset_t *m_ch4 = changeset_new(apex);
427
	init_random_changeset(m_ch4, 2147483647, 4294967294, 128, apex, false);
428
	add_tail(&k, &m_ch4->n);
429
	ret = journal_store_changeset(j, m_ch4);
430
	is_int(KNOT_EOK, ret, "journal: allways ok journal_store_changeset 4");
431
	changeset_t *m_ch5 = changeset_new(apex);
432
	init_random_changeset(m_ch5, 4294967294, 1, 128, apex, false);
433
	add_tail(&k, &m_ch5->n);
434
	ret = journal_store_changeset(j, m_ch5);
435
	is_int(KNOT_EBUSY, ret, "journal: allways ok journal_store_changeset 5");
436
	ret = journal_flush(j);
437
	is_int(KNOT_EOK, ret, "journal: allways ok journal_flush 1");
438
	ret = journal_store_changeset(j, m_ch5);
439
	is_int(KNOT_EOK, ret, "journal: allways ok journal_store_changeset 6");
440
	ret = journal_flush(j);
441
	is_int(KNOT_EOK, ret, "journal: allways ok journal_flush 2");
442 443 444 445 446 447
	ret = journal_load_changesets(j, &l, 0);
	ret2 = journal_load_changesets(j, &l, 1);
	int ret3 = journal_load_changesets(j, &l, 2);
	fprintf(stderr, "ret=%d ret2=%d ret3=%d\n", ret, ret2, ret3);
	ok(ret == KNOT_ENOENT && ret2 == KNOT_ENOENT && ret3 == KNOT_EOK &&
	   changesets_list_eq(&l, &k), "journal: serial collision");
448
	ret = journal_check(j, JOURNAL_CHECK_STDERR);
449
	is_int(KNOT_EOK, ret, "journal check (%d)", ret);
450 451 452 453

	/* Cleanup. */
	changesets_free(&l);
	changesets_free(&k);
454

455 456
	init_list(&l);
	init_list(&k);
457

458 459 460 461 462 463 464 465
	/* Check bootstrap changeset */
	ret = drop_journal(j, NULL);
	ok(ret == KNOT_EOK, "journal: drop_journal must be ok");

	changeset_t *m_ch6 = changeset_new(apex);
	init_random_changeset(m_ch6, 0, 1, 128, apex, true);
	ret = journal_store_changeset(j, m_ch6);
	ok(ret == KNOT_EOK, "journal: store bootstrap (%d)", ret);
466
	ret = journal_check(j, JOURNAL_CHECK_STDERR);
467 468 469 470 471 472 473 474 475
	ok(ret == KNOT_EOK, "journal check (%d)", ret);
	changeset_t *m_ch7 = changeset_new(apex);
	init_random_changeset(m_ch7, 1, 2, 128, apex, false);
	ret = journal_store_changeset(j, m_ch7);
	ok(ret == KNOT_EOK, "journal: store after bootstrap (%d)", ret);
	add_tail(&k, &m_ch6->n);
	add_tail(&k, &m_ch7->n);
	ret = journal_load_bootstrap(j, &l);
	ok(ret == KNOT_EOK && changesets_list_eq(&l, &k), "journal: load boostrap (%d)", ret);
476
	ret = journal_check(j, JOURNAL_CHECK_STDERR);
477 478 479 480 481 482 483 484
	ok(ret == KNOT_EOK, "journal check (%d)", ret);

	changesets_free(&l);
	changesets_free(&k);

	init_list(&l);
	init_list(&k);

485 486 487
	ret = journal_scrape(j);
	ok(ret == KNOT_EOK, "journal: scrape must be ok");

488
	unset_conf();
489 490
}

Daniel Salzman's avatar
Daniel Salzman committed
491 492 493
const uint8_t *rdA = (const uint8_t *) "\x01\x02\x03\x04";
const uint8_t *rdB = (const uint8_t *) "\x01\x02\x03\x05";
const uint8_t *rdC = (const uint8_t *) "\x01\x02\x03\x06";
494

495 496
// frees owner
static knot_rrset_t * tm_rrset(knot_dname_t * owner, const uint8_t * rdata)
497 498 499
{
	knot_rrset_t * rrs = knot_rrset_new(owner, KNOT_RRTYPE_A, KNOT_CLASS_IN, NULL);
	knot_rrset_add_rdata(rrs, rdata, 4, 3600, NULL);
500
	free(owner);
501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
	return rrs;
}

static knot_dname_t * tm_owner(const char * prefix, const knot_dname_t *apex)
{
	knot_dname_t * ret = malloc(strlen(prefix) + knot_dname_size(apex) + 2);
	ret[0] = strlen(prefix);
	strcpy((char *) (ret + 1), prefix);
	memcpy(ret + ret[0] + 1, apex, knot_dname_size(apex));
	return ret;
}

static knot_rrset_t * tm_rrs(const knot_dname_t * apex, int x)
{
	static knot_rrset_t * rrsA = NULL;
	static knot_rrset_t * rrsB = NULL;
	static knot_rrset_t * rrsC = NULL;
518 519 520 521 522 523 524 525 526

	if (apex == NULL) {
		knot_rrset_free(&rrsA, NULL);
		knot_rrset_free(&rrsB, NULL);
		knot_rrset_free(&rrsC, NULL);
		rrsA = rrsB = rrsC = NULL;
		return NULL;
	}

527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
	if (rrsA == NULL) rrsA = tm_rrset(tm_owner("aaaaaaaaaaaaaaaaa", apex), rdA);
	if (rrsB == NULL) rrsB = tm_rrset(tm_owner("bbbbbbbbbbbbbbbbb", apex), rdB);
	if (rrsC == NULL) rrsC = tm_rrset(tm_owner("ccccccccccccccccc", apex), rdC);
	switch ((x % 3 + 3) % 3) {
	case 0: return rrsA;
	case 1: return rrsB;
	case 2: return rrsC;
	}
	assert(0); return NULL;
}

int tm_rrcnt(const changeset_t * ch, int flg)
{
	changeset_iter_t it;
	int i = 0;
	if (flg >= 0) changeset_iter_add(&it, ch);
	else changeset_iter_rem(&it, ch);

	knot_rrset_t rri;
	while (rri = changeset_iter_next(&it), !knot_rrset_empty(&rri)) i++;
547 548

	changeset_iter_clear(&it);
549 550 551 552 553 554 555
	return i;
}

static changeset_t * tm_chs(const knot_dname_t * apex, int x)
{
	static changeset_t * chsI = NULL, * chsX = NULL, * chsY = NULL;
	static uint32_t serial = 0;
556 557 558 559 560 561 562 563 564

	if (apex == NULL) {
		changeset_free(chsI);
		changeset_free(chsX);
		changeset_free(chsY);
		chsI = chsX = chsY = NULL;
		return NULL;
	}

565 566 567
	if (chsI == NULL) {
		chsI = changeset_new(apex);
		assert(chsI != NULL);
Daniel Salzman's avatar
Daniel Salzman committed
568 569
		changeset_add_addition(chsI, tm_rrs(apex, 0), 0);
		changeset_add_addition(chsI, tm_rrs(apex, 1), 0);
570 571 572 573
	}
	if (chsX == NULL) {
		chsX = changeset_new(apex);
		assert(chsX != NULL);
Daniel Salzman's avatar
Daniel Salzman committed
574 575
		changeset_add_removal(chsX, tm_rrs(apex, 1), 0);
		changeset_add_addition(chsX, tm_rrs(apex, 2), 0);
576 577 578 579
	}
	if (chsY == NULL) {
		chsY = changeset_new(apex);
		assert(chsY != NULL);
Daniel Salzman's avatar
Daniel Salzman committed
580 581
		changeset_add_removal(chsY, tm_rrs(apex, 2), 0);
		changeset_add_addition(chsY, tm_rrs(apex, 1), 0);
582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
	}
	assert(x >= 0);
	changeset_t * ret;
	if (x == 0) ret = chsI;
	else if (x % 2 == 1) ret = chsX;
	else ret = chsY;

	changeset_set_soa_serials(ret, serial, serial + 1, apex);
	serial++;

	return ret;
}

static int merged_present(void)
{
	local_txn_t(txn, j);
	txn_begin(txn, 0);
	int res = md_flag(txn, MERGED_SERIAL_VALID);
	txn_abort(txn);
	return res;
}

static void test_merge(void)
{
	int i, ret;
	list_t l;

	// allow merge
610
	set_conf(-1, 512 * 1024);
611 612 613
	ok(journal_merge_allowed(j), "journal: merge allowed");

	ret = drop_journal(j, NULL);
614
	is_int(KNOT_EOK, ret, "journal: drop_journal must be ok");
615 616 617 618

	// insert stuff and check the merge
	for (i = 0; !merged_present(); i++) {
		ret = journal_store_changeset(j, tm_chs(apex, i));
619
		is_int(KNOT_EOK, ret, "journal: journal_store_changeset must be ok");
620 621 622
	}
	init_list(&l);
	ret = journal_load_changesets(j, &l, 0);
623
	is_int(KNOT_EOK, ret, "journal: journal_load_changesets must be ok");
624 625 626 627 628 629 630 631 632 633
	ok(list_size(&l) == 2, "journal: read the merged and one following");
	changeset_t * mch = (changeset_t *)HEAD(l);
	ok(list_size(&l) >= 1 && tm_rrcnt(mch, 1) == 2, "journal: merged additions # = 2");
	ok(list_size(&l) >= 1 && tm_rrcnt(mch, -1) == 1, "journal: merged removals # = 1");
	changesets_free(&l);

	// insert one more and check the #s of results
	journal_store_changeset(j, tm_chs(apex, i));
	init_list(&l);
	ret = journal_load_changesets(j, &l, 0);
634
	is_int(KNOT_EOK, ret, "journal: journal_load_changesets2 must be ok");
635 636 637 638
	ok(list_size(&l) == 3, "journal: read merged together with new changeset");
	changesets_free(&l);
	init_list(&l);
	ret = journal_load_changesets(j, &l, (uint32_t) (i - 3));
639
	is_int(KNOT_EOK, ret, "journal: journal_load_changesets3 must be ok");
640
	ok(list_size(&l) == 4, "journal: read short history of merged/unmerged changesets");
641
	changesets_free(&l);
642 643 644 645 646

	ret = drop_journal(j, NULL);
	assert(ret == KNOT_EOK);

	// disallow merge
647
	unset_conf();
648
	set_conf(1000, 512 * 1024);
649
	ok(!journal_merge_allowed(j), "journal: merge disallowed");
650 651 652 653

	tm_rrs(NULL, 0);
	tm_chs(NULL, 0);
	unset_conf();
654 655 656 657 658 659 660 661
}

static void test_stress_base(journal_t *j, size_t update_size, size_t file_size)
{
	int ret;
	uint32_t serial = 0;

	journal_close(j);
662
	journal_db_close(&db);
663
	db = NULL;
664
	ret = journal_db_init(&db, test_dir_name, file_size, JOURNAL_MODE_ASYNC);
665
	assert(ret == KNOT_EOK);
666
	ret = journal_open_db(&db);
667 668 669 670
	assert(ret == KNOT_EOK);
	ret = journal_open(j, &db, apex);
	assert(ret == KNOT_EOK);

671 672
	set_conf(1000, file_size / 2);

673 674
	changeset_t ch;
	changeset_init(&ch, apex);
675
	init_random_changeset(&ch, serial, serial + 1, update_size, apex, false);
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693

	for (int i = 1; i <= 6; ++i) {
		serial = 0;
		while (true) {
			changeset_set_soa_serials(&ch, serial, serial + 1, apex);
			ret = journal_store_changeset(j, &ch);
			if (ret == KNOT_EOK) {
				serial++;
			} else {
				break;
			}
		}

		int ret = journal_flush(j);
		ok(serial > 0 && ret == KNOT_EOK, "journal: pass #%d fillup run (%d inserts)", i, serial);
	}

	changeset_clear(&ch);
694 695

	unset_conf();
696 697 698 699 700
}

/*! \brief Test behavior when writing to jurnal and flushing it. */
static void test_stress(journal_t *j)
{
701
	diag("stress test: small data");
702
	test_stress_base(j, 40, (1024 + 512) * 1024);
703

704
	diag("stress test: medium data");
705 706
	test_stress_base(j, 400, 3 * 1024 * 1024);

707
	diag("stress test: large data");
708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725
	test_stress_base(j, 4000, 10 * 1024 * 1024);
}

int main(int argc, char *argv[])
{
	plan_lazy();

	test_dir_name = test_mkdtemp();

	test_journal_db();

	test_store_load();

	test_merge();

	test_stress(j);

	journal_close(j);
Daniel Salzman's avatar
Daniel Salzman committed
726
	journal_free(&j);
727
	journal_db_close(&db);
728

Daniel Salzman's avatar
Daniel Salzman committed
729 730 731
	test_rm_rf(test_dir_name);
	free(test_dir_name);

732 733
	return 0;
}