Commit 86125be4 authored by Daniel Salzman's avatar Daniel Salzman

Merge branch 'zone_update-full' into 'master'

zone_update: FULL update + iterators + unittest

This branch implements the FULL update (i.e. building a zone from scratch, not as an update, so no changesets/journal/application mechanism. This will allow the zone_update API be used in zone loading, AXFR and mainly when setting up a new zone using ctl (mainly for jetconf usage).

The second commit adds read-only iterators and remakes the unittest for both FULL update and iterator coverage. However I have to mention the read-only feature of the iterators is not enforced, it merely means a programmer may not add or remove RRs while iterating. Such action will invalidate the underlying hattrie iterators/metadata and most likely will lead to some assert failing. It is the programmer's responsibility to avoid this.

See merge request !544
parents 3ff0e662 b82dd3ea
This diff is collapsed.
......@@ -37,10 +37,20 @@ typedef struct {
zone_contents_t *new_cont; /*!< New zone contents for full updates. */
changeset_t change; /*!< Changes we want to apply. */
apply_ctx_t a_ctx; /*!< Context for applying changesets. */
uint8_t flags; /*!< Zone update flags. */
uint32_t flags; /*!< Zone update flags. */
knot_mm_t mm; /*!< Memory context used for intermediate nodes. */
} zone_update_t;
typedef struct {
zone_update_t *update; /*!< The update we're iterating over. */
hattrie_iter_t *base_it; /*!< Iterator for the original zone in the case of INCREMENTAL update or the new zone in case of FULL update. */
hattrie_iter_t *add_it; /*!< Iterator for the added nodes in the changeset. Available in the INCREMENTAL update only. */
const zone_node_t *base_node; /*!< The original node (INCREMENTAL update) or new node (FULL update). */
const zone_node_t *add_node; /*!< The additions to that node (INCREMENTAL update only). */
const zone_node_t *next_node; /*!< The smaller of t_node and ch_node (INCREMENTAL update) or next new node (FULL update). */
bool nsec3; /*!< Set when we're using the NSEC3 node tree. */
} zone_update_iter_t;
typedef enum {
UPDATE_FULL = 1 << 0, /*!< Replace the old zone by a complete new one. */
UPDATE_INCREMENTAL = 1 << 1, /*!< Apply changes to the old zone. */
......@@ -122,6 +132,10 @@ void zone_update_clear(zone_update_t *update);
/*!
* \brief Adds an RRSet to the zone.
*
* \warning Do not edit the zone_update when any iterator is active. Any
* zone_update modifications will invalidate the trie iterators
* in the zone_update iterator(s).
*
* \param update Zone update.
*
* \return KNOT_E*
......@@ -131,6 +145,10 @@ int zone_update_add(zone_update_t *update, const knot_rrset_t *rrset);
/*!
* \brief Removes an RRSet from the zone.
*
* \warning Do not edit the zone_update when any iterator is active. Any
* zone_update modifications will invalidate the trie iterators
* in the zone_update iterator(s).
*
* \param update Zone update.
*
* \return KNOT_E*
......@@ -147,6 +165,63 @@ int zone_update_remove(zone_update_t *update, const knot_rrset_t *rrset);
*/
int zone_update_commit(conf_t *conf, zone_update_t *update);
/*!
* \brief Setup a zone_update iterator for both FULL and INCREMENTAL updates.
*
* \warning Do not init or use iterators when the zone is edited. Any
* zone_update modifications will invalidate the trie iterators
* in the zone_update iterator.
*
* \param it Iterator.
* \param update Zone update.
*
* \return KNOT_E*
*/
int zone_update_iter(zone_update_iter_t *it, zone_update_t *update);
/*!
* \brief Setup a zone_update iterator for both FULL and INCREMENTAL updates.
* Version for iterating over nsec3 nodes.
*
* \warning Do not init or use iterators when the zone is edited. Any
* zone_update modifications will invalidate the trie iterators
* in the zone_update iterator.
*
*
* \param it Iterator.
* \param update Zone update.
*
* \return KNOT_E*
*/
int zone_update_iter_nsec3(zone_update_iter_t *it, zone_update_t *update);
/*!
* \brief Move the iterator to the next item.
*
* \param it Iterator.
*
* \return KNOT_E*
*/
int zone_update_iter_next(zone_update_iter_t *it);
/*!
* \brief Get the value of the iterator.
*
* \param it Iterator.
*
* \return A (synthesized or added) node with all its current data.
*/
const zone_node_t *zone_update_iter_val(zone_update_iter_t *it);
/*!
* \brief Finish the iterator and clean it up.
*
* \param it Iterator.
*
* \return KNOT_E*
*/
int zone_update_iter_finish(zone_update_iter_t *it);
/*!
* \brief Returns bool whether there are any changes at all.
*
......
......@@ -524,7 +524,7 @@ static zone_node_t *get_nsec3_node(const zone_contents_t *zone,
static int insert_rr(zone_contents_t *z, const knot_rrset_t *rr,
zone_node_t **n, bool nsec3)
{
if (z == NULL || knot_rrset_empty(rr) || n == NULL) {
if (knot_rrset_empty(rr)) {
return KNOT_EINVAL;
}
......@@ -553,6 +553,49 @@ static int insert_rr(zone_contents_t *z, const knot_rrset_t *rr,
return node_add_rrset(*n, rr, NULL);
}
static int remove_rr(zone_contents_t *z, const knot_rrset_t *rr,
zone_node_t **n, bool nsec3)
{
if (knot_rrset_empty(rr)) {
return KNOT_EINVAL;
}
// check if the RRSet belongs to the zone
if (!knot_dname_is_sub(rr->owner, z->apex->owner) &&
!knot_dname_is_equal(rr->owner, z->apex->owner)) {
return KNOT_EOUTOFZONE;
}
zone_node_t *node;
if (*n == NULL) {
node = nsec3 ? get_nsec3_node(z, rr->owner) : get_node(z, rr->owner);
if (node == NULL) {
return KNOT_ENONODE;
}
} else {
node = *n;
}
knot_rdataset_t *node_rrs = node_rdataset(node, rr->type);
// Subtract changeset RRS from node RRS.
int ret = knot_rdataset_subtract(node_rrs, &rr->rrs, NULL);
if (ret != KNOT_EOK) {
return ret;
}
if (node_rrs->rr_count == 0) {
// RRSet is empty now, remove it from node, all data freed.
node_remove_rdataset(node, rr->type);
// If node is empty now, delete it from zone tree.
if (node->rrset_count == 0) {
zone_tree_delete_empty_node(nsec3 ? z->nsec3_nodes : z->nodes, node);
}
}
*n = node;
return KNOT_EOK;
}
static int recreate_normal_tree(const zone_contents_t *z, zone_contents_t *out)
{
out->nodes = hattrie_dup(z->nodes, NULL);
......@@ -662,13 +705,23 @@ static zone_node_t *get_previous(const zone_contents_t *zone,
int zone_contents_add_rr(zone_contents_t *z, const knot_rrset_t *rr,
zone_node_t **n)
{
if (z == NULL || rr == NULL) {
if (z == NULL || rr == NULL || n == NULL) {
return KNOT_EINVAL;
}
return insert_rr(z, rr, n, knot_rrset_is_nsec3rel(rr));
}
int zone_contents_remove_rr(zone_contents_t *z, const knot_rrset_t *rr,
zone_node_t **n)
{
if (z == NULL || rr == NULL || n == NULL) {
return KNOT_EINVAL;
}
return remove_rr(z, rr, n, knot_rrset_is_nsec3rel(rr));
}
zone_node_t *zone_contents_get_node_for_rr(zone_contents_t *zone, const knot_rrset_t *rrset)
{
if (zone == NULL || rrset == NULL) {
......
......@@ -67,6 +67,17 @@ zone_contents_t *zone_contents_new(const knot_dname_t *apex_name);
*/
int zone_contents_add_rr(zone_contents_t *z, const knot_rrset_t *rr, zone_node_t **n);
/*!
* \brief Remove an RR from contents.
*
* \param z Contents to remove from.
* \param rr The RR to remove.
* \param n Node from which the RR to be removed from on success, unchanged otherwise.
*
* \return KNOT_E*
*/
int zone_contents_remove_rr(zone_contents_t *z, const knot_rrset_t *rr, zone_node_t **n);
/*!
* \brief Get the node with this RR (the RR's owner).
*
......
......@@ -17,13 +17,16 @@
#include <assert.h>
#include <tap/basic.h>
#include "test_conf.h"
#include "contrib/macros.h"
#include "contrib/getline.h"
#include "knot/updates/zone-update.h"
#include "knot/zone/node.h"
#include "zscanner/scanner.h"
static const char *zone_str =
"test. 3600 IN SOA a.ns.test. hostmaster.nic.cz. 1406641065 900 300 604800 900 \n"
static const char *zone_str1 =
"test. 3600 IN SOA a.ns.test. hostmaster.nic.cz. 1406641065 900 300 604800 900 \n";
static const char *zone_str2 =
"test. IN TXT \"test\"\n";
static const char *add_str =
......@@ -32,105 +35,214 @@ static const char *add_str =
static const char *del_str =
"test. IN TXT \"test\"\n";
bool to_zone;
knot_rrset_t rrset;
static void process_rr(zs_scanner_t *scanner)
/*!< \brief Returns true if node contains given RR in its RRSets. */
static bool node_contains_rr(const zone_node_t *node,
const knot_rrset_t *rr)
{
// get zone to insert into
zone_contents_t *zc = scanner->process.data;
const knot_rdataset_t *zone_rrs = node_rdataset(node, rr->type);
if (zone_rrs) {
assert(rr->rrs.rr_count == 1);
const bool compare_ttls = false;
return knot_rdataset_member(zone_rrs,
knot_rdataset_at(&rr->rrs, 0),
compare_ttls);
} else {
return false;
}
}
// create data
static void process_rr(zs_scanner_t *scanner)
{
knot_rrset_init(&rrset, scanner->r_owner, scanner->r_type, scanner->r_class);
int ret = knot_rrset_add_rdata(&rrset, scanner->r_data,
scanner->r_data_length,
scanner->r_ttl, NULL);
assert(ret == KNOT_EOK);
if (to_zone) {
// Add initial node to zone
zone_node_t *n = NULL;
ret = zone_contents_add_rr(zc, &rrset, &n);
UNUSED(n);
knot_rdataset_clear(&rrset.rrs, NULL);
assert(ret == KNOT_EOK);
}
}
int main(int argc, char *argv[])
void test_full(zone_t *zone, zs_scanner_t *sc)
{
zone_update_t update;
int ret = zone_update_init(&update, zone, UPDATE_FULL);
ok(ret == KNOT_EOK, "zone update: init full");
plan_lazy();
if (zs_set_input_string(sc, zone_str1, strlen(zone_str1)) != 0 ||
zs_parse_all(sc) != 0) {
assert(0);
}
int ret = KNOT_EOK;
to_zone = true;
ret = zone_update_add(&update, &rrset);
knot_rdataset_clear(&rrset.rrs, NULL);
ok(ret == KNOT_EOK, "full zone update: first addition");
knot_dname_t *apex = knot_dname_from_str_alloc("test");
assert(apex);
zone_contents_t *zc = zone_contents_new(apex);
assert(zc);
zone_t zone = { .contents = zc, .name = apex };
if (zs_set_input_string(sc, zone_str2, strlen(zone_str2)) != 0 ||
zs_parse_all(sc) != 0) {
assert(0);
}
// Parse initial node
zs_scanner_t sc;
if (zs_init(&sc, "test.", KNOT_CLASS_IN, 3600) != 0 ||
zs_set_processing(&sc, process_rr, NULL, zc) != 0) {
ret = zone_update_add(&update, &rrset);
zone_node_t *node = zone_contents_find_node_for_rr(update.new_cont, &rrset);
bool rrset_present = node_contains_rr(node, &rrset);
ok(ret == KNOT_EOK && rrset_present, "full zone update: second addition");
ret = zone_update_remove(&update, &rrset);
node = zone_contents_find_node_for_rr(update.new_cont, &rrset);
rrset_present = node_contains_rr(node, &rrset);
ok(ret == KNOT_EOK && !rrset_present, "full zone update: removal");
ret = zone_update_add(&update, &rrset);
node = zone_contents_find_node_for_rr(update.new_cont, &rrset);
rrset_present = node_contains_rr(node, &rrset);
ok(ret == KNOT_EOK && rrset_present, "full zone update: last addition");
knot_rdataset_clear(&rrset.rrs, NULL);
zone_update_iter_t it;
ret = zone_update_iter(&it, &update);
ok(ret == KNOT_EOK, "full zone update: init iter");
const zone_node_t *iter_node = zone_update_iter_val(&it);
assert(iter_node);
if (zs_set_input_string(sc, zone_str1, strlen(zone_str1)) != 0 ||
zs_parse_all(sc) != 0) {
assert(0);
}
rrset_present = node_contains_rr(iter_node, &rrset);
ok(rrset_present, "full zone update: first iter value check");
knot_rdataset_clear(&rrset.rrs, NULL);
if (zs_set_input_string(&sc, zone_str, strlen(zone_str)) != 0 ||
zs_parse_all(&sc) != 0) {
if (zs_set_input_string(sc, zone_str2, strlen(zone_str2)) != 0 ||
zs_parse_all(sc) != 0) {
assert(0);
}
rrset_present = node_contains_rr(iter_node, &rrset);
ok(rrset_present, "full zone update: second iter value check");
ret = zone_update_iter_next(&it);
ok(ret == KNOT_EOK, "full zone update: iter next");
iter_node = zone_update_iter_val(&it);
ok(iter_node == NULL, "full zone update: iter val past end");
ret = zone_update_iter_finish(&it);
ok(ret == KNOT_EOK, "full zone update: iter finish");
ret = zone_update_commit(conf(), &update);
node = zone_contents_find_node_for_rr(zone->contents, &rrset);
rrset_present = node_contains_rr(node, &rrset);
ok(ret == KNOT_EOK && rrset_present, "full zone update: commit");
// Initial node added, now just parse the RRs
to_zone = false;
knot_rdataset_clear(&rrset.rrs, NULL);
}
void test_incremental(zone_t *zone, zs_scanner_t *sc)
{
int ret = KNOT_EOK;
zone_update_t update;
zone_update_init(&update, &zone, UPDATE_INCREMENTAL);
ok(update.zone == &zone && changeset_empty(&update.change) && update.mm.alloc,
zone_update_init(&update, zone, UPDATE_INCREMENTAL);
ok(update.zone == zone && changeset_empty(&update.change) && update.mm.alloc,
"incremental zone update: init");
// Check that old node is returned without changes
ok(zc->apex == zone_update_get_node(&update, zc->apex->owner) &&
ok(zone->contents->apex == zone_update_get_apex(&update) &&
zone_update_no_change(&update),
"incremental zone update: no change");
// Parse RR for addition and add it
if (zs_set_input_string(&sc, add_str, strlen(add_str)) != 0 ||
zs_parse_all(&sc) != 0) {
if (zs_set_input_string(sc, add_str, strlen(add_str)) != 0 ||
zs_parse_all(sc) != 0) {
assert(0);
}
ret = zone_update_add(&update, &rrset);
knot_rdataset_clear(&rrset.rrs, NULL);
ok(ret == KNOT_EOK, "incremental zone update: addition");
// Check that apex TXT has two RRs now
const zone_node_t *synth_node = zone_update_get_node(&update, zc->apex->owner);
const zone_node_t *synth_node = zone_update_get_apex(&update);
ok(synth_node && node_rdataset(synth_node, KNOT_RRTYPE_TXT)->rr_count == 2,
"incremental zone update: add change");
// Parse RR for removal and remove it
if (zs_set_input_string(&sc, del_str, strlen(del_str)) != 0 ||
zs_parse_all(&sc) != 0) {
if (zs_set_input_string(sc, del_str, strlen(del_str)) != 0 ||
zs_parse_all(sc) != 0) {
assert(0);
}
ret = zone_update_remove(&update, &rrset);
knot_rdataset_clear(&rrset.rrs, NULL);
ok(ret == KNOT_EOK, "incremental zone update: removal");
// Check that apex TXT has one RR again
synth_node = zone_update_get_node(&update, zc->apex->owner);
synth_node = zone_update_get_apex(&update);
ok(synth_node && node_rdataset(synth_node, KNOT_RRTYPE_TXT)->rr_count == 1,
"incremental zone update: del change");
zone_update_clear(&update);
ok(update.zone == NULL && changeset_empty(&update.change), "incremental zone update: cleanup");
zone_update_iter_t it;
ret = zone_update_iter(&it, &update);
ok(ret == KNOT_EOK, "incremental zone update: init iter");
const zone_node_t *iter_node = zone_update_iter_val(&it);
assert(iter_node);
bool rrset_present = node_contains_rr(iter_node, &rrset);
ok(!rrset_present, "incremental zone update: first iter value check");
knot_rdataset_clear(&rrset.rrs, NULL);
if (zs_set_input_string(sc, add_str, strlen(add_str)) != 0 ||
zs_parse_all(sc) != 0) {
assert(0);
}
rrset_present = node_contains_rr(iter_node, &rrset);
ok(rrset_present, "incremental zone update: second iter value check");
ret = zone_update_iter_next(&it);
ok(ret == KNOT_EOK, "incremental zone update: iter next");
iter_node = zone_update_iter_val(&it);
ok(iter_node == NULL, "incremental zone update: iter val past end");
ret = zone_update_iter_finish(&it);
ok(ret == KNOT_EOK, "incremental zone update: iter finish");
ret = zone_update_commit(conf(), &update);
iter_node = zone_contents_find_node_for_rr(zone->contents, &rrset);
rrset_present = node_contains_rr(iter_node, &rrset);
ok(ret == KNOT_EOK && rrset_present, "incremental zone update: commit");
knot_rdataset_clear(&rrset.rrs, NULL);
}
int main(int argc, char *argv[])
{
plan_lazy();
/* Load test configuration. */
const char *conf_str = "zone:\n - domain: test.\n storage: /tmp\n";
int ret = test_conf(conf_str, NULL);
if (ret != KNOT_EOK) {
return ret;
}
// Set up empty zone
knot_dname_t *apex = knot_dname_from_str_alloc("test");
assert(apex);
zone_t *zone = zone_new(apex);
// Parse initial node
zs_scanner_t sc;
if (zs_init(&sc, "test.", KNOT_CLASS_IN, 3600) != 0 ||
zs_set_processing(&sc, process_rr, NULL, NULL) != 0) {
assert(0);
}
// Test FULL update, commit it and use the result to test the INCREMENTAL update
test_full(zone, &sc);
test_incremental(zone, &sc);
zs_deinit(&sc);
zone_contents_deep_free(&zc);
zone_free(&zone);
knot_dname_free(&apex, NULL);
conf_free(conf());
return 0;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment