diff --git a/lib/isc/cfgmgr.c b/lib/isc/cfgmgr.c index 75b6a9ff4c..d63ff17e7c 100644 --- a/lib/isc/cfgmgr.c +++ b/lib/isc/cfgmgr.c @@ -45,11 +45,44 @@ #define NODEINDEXFLG 'i' #define PROPERTYFLG 'p' +/* + * Used to avoid repeated hashing to figure out if we're in those special nodes + * + * - root options is special as it gets "materialized", if on running mode, from + * options node in builtin and user nodes. + * + * - root views is special as each of each sub-nodes is considered as a view + * instance + * + * - a zones node inside a view instance is special as each sub-node is + * considered a zone instance + * + * - a zone instance gets "materialized" from root options and the view instance + * it belongs. + */ +#define OPTIONS "options" +#define VIEWS "views" +#define ZONES "zones" + +static uint32_t isc__cfgmgr_hashoptions; +static uint32_t isc__cfgmgr_hashviews; +static uint32_t isc__cfgmgr_hashzones; + +#define NODE_FLG_OPTIONS 1 << 0 +#define NODE_FLG_VIEWS 1 << 1 +#define NODE_FLG_VIEW 1 << 2 +#define NODE_FLG_ZONES 1 << 3 +#define NODE_FLG_ZONE 1 << 4 + +/* + * Defines an opened node. + */ typedef struct isc__cfgmgr_node isc__cfgmgr_node_t; struct isc__cfgmgr_node { char *name; char *prefix; uint32_t hash; + uint32_t flags; ISC_LINK(isc__cfgmgr_node_t) link; }; typedef ISC_LIST(isc__cfgmgr_node_t) isc__cfgmgr_nodes_t; @@ -238,6 +271,10 @@ isc_cfgmgr_init(isc_mem_t *mctx, const char *dbpath) { goto cleanup; } + isc__cfgmgr_hashoptions = isc_hash32(OPTIONS, strlen(OPTIONS), false); + isc__cfgmgr_hashviews = isc_hash32(VIEWS, strlen(VIEWS), false); + isc__cfgmgr_hashzones = isc_hash32(ZONES, strlen(ZONES), false); + goto out; cleanup: @@ -452,7 +489,7 @@ isc__cfgmgr_curhash(void) { if (node == NULL) { const char *rootprefix = isc__cfgmgr_curprefix(); - uint64_t roothash = isc_hash64(rootprefix, strlen(rootprefix), + uint64_t roothash = isc_hash32(rootprefix, strlen(rootprefix), false); INSIST(roothash > 0); @@ -514,6 +551,56 @@ isc__cfgmgr_addindex(MDB_cursor *cursor, const char *name) { REQUIRE(mdb_cursor_put(cursor, &dbkey, &dbval, 0) == 0); } +static void +isc__cfgmgr_setnodeflags(isc__cfgmgr_node_t *node) { + isc__cfgmgr_node_t *curnode = isc__cfgmgr_curnode(); + uint32_t hash = isc_hash32(node->name, strlen(node->name), false); + + /* + * At root level, can be options or views special nodes + */ + if (curnode == NULL) { + if (hash == isc__cfgmgr_hashoptions) { + node->flags |= NODE_FLG_OPTIONS; + } else if (hash == isc__cfgmgr_hashviews) { + node->flags |= NODE_FLG_VIEWS; + } + } + + /* + * Inside the views special node, marking the new node as a view + * instance. + */ + else if (curnode->flags & NODE_FLG_VIEWS) + { + node->flags |= NODE_FLG_VIEW; + } + + /* + * Knowing that a node is a zone instance doesn't seems needed right + * now... I'm leaving the code below for now, just in case, but likely + * to be removed later. + */ + + /* + * Inside a view instance node, looking for the zones special node. + */ + else if (curnode->flags & NODE_FLG_VIEW && + hash == isc__cfgmgr_hashzones) + { + node->flags |= NODE_FLG_ZONES; + } + + /* + * Inside a view zones special instance node, marking the new node as a + * zone instance node + */ + else if (curnode->flags & NODE_FLG_ZONES) + { + node->flags |= NODE_FLG_ZONE; + } +} + void isc_cfgmgr_open(const char *name) { isc__cfgmgr_node_t *node = NULL; @@ -526,14 +613,17 @@ isc_cfgmgr_open(const char *name) { .name = isc_mem_allocate(isc__cfgmgr_mctx, strlen(name) + 1), .prefix = isc_mem_get(isc__cfgmgr_mctx, BUFLEN), .link = ISC_LINK_INITIALIZER, - .hash = isc_hash64(name, strlen(name), true) }; strcpy(node->name, name); - INSIST(node->hash > 0); REQUIRE(snprintf(node->prefix, BUFLEN, "%s%s/", isc__cfgmgr_curprefix(), name) < BUFLEN); + node->hash = isc_hash32(node->prefix, strlen(node->prefix), true); + INSIST(node->hash > 0); + + isc__cfgmgr_setnodeflags(node); + if (isc__cfgmgr_ctx.readonly == false) { isc__cfgmgr_addindex(isc__cfgmgr_ctx.builtincursor, name); isc__cfgmgr_addindex(isc__cfgmgr_ctx.usercursor, name); @@ -769,35 +859,95 @@ static isc_result_t isc__cfgmgr_write(MDB_cursor *cursor, const char *name, const isc_cfgmgr_val_t *value, bool materialize); +typedef struct isc__cfgmgr_materializeprop isc__cfgmgr_materializeprop_t; +struct isc__cfgmgr_materializeprop { + const char *name; + const isc_cfgmgr_val_t *value; +}; + static void -isc__cfgmgr_materialize(const char *name, const isc_cfgmgr_val_t *value) { +isc__cfgmgr_materialize_apply(void *state, const char *nodename) { isc_cfgmgr_mode_t valmode; isc_cfgmgr_val_t val; isc_result_t result; + isc__cfgmgr_materializeprop_t *property = state; + + UNUSED(nodename); + + result = isc__cfgmgr_read(isc__cfgmgr_ctx.runningcursor, property->name, + &val, &valmode); + INSIST(result == ISC_R_NOTFOUND || result == ISC_R_SUCCESS); + + if ((result == ISC_R_NOTFOUND && property->value != NULL) || + (result == ISC_R_SUCCESS && valmode <= isc__cfgmgr_ctx.mode)) + { + REQUIRE(isc__cfgmgr_write(isc__cfgmgr_ctx.runningcursor, + property->name, property->value, + false) == ISC_R_SUCCESS); + } +} + +static void +isc__cfgmgr_materialize_foreachview(void *state, const char *nodename) { + UNUSED(nodename); + + isc_cfgmgr_open("zones"); + isc_cfgmgr_foreachnodes(isc__cfgmgr_materialize_apply, state); + isc_cfgmgr_close(); +} + +static void +isc__cfgmgr_materialize_zone(isc__cfgmgr_materializeprop_t *property, + bool inview) { + if (inview) { + isc_cfgmgr_open("zones"); + isc_cfgmgr_foreachnodes(isc__cfgmgr_materialize_apply, property); + isc_cfgmgr_close(); + } else { + isc_cfgmgr_open("views"); + isc_cfgmgr_foreachnodes(isc__cfgmgr_materialize_foreachview, + property); + isc_cfgmgr_close(); + } +} + +/* + * There is an issue with the materialization on zone: it will copy _all_ + * options properties/sub-nodes into each zones. While this is not a problem for + * reading data (nobody will attempt to read data not expected to be in a zone) + * this will be a problem when dumping the zone. + * + * This is probably something to figure out with the validation of the + * configuration, which could be used as a filter in the copy callbacks during + * the materialization. + * + * isc_cfgmgr_delnode doest not supports materialization (i.e. it won't remove + * deleted properties if they hasn't been overriden). Not sure the _delnode API + * is actually useful. If it is (and we keep it) then it needs to be fixed. + */ +static void +isc__cfgmgr_materialize(const char *name, const isc_cfgmgr_val_t *value) { + isc__cfgmgr_node_t *node = isc__cfgmgr_curnode(); + isc__cfgmgr_materializeprop_t property = { .name = name, + .value = value }; if (isc__cfgmgr_ctx.mode == ISC_CFGMGR_MODERUNNING) { return; } - /* - * The only nodes which "accepts" materialization - * (view/zones cases TBD) - * (value == NULL TDB) - * (delnode flow TBD) - */ - if (strcmp(isc__cfgmgr_curprefix(), "/options/") != 0) { - return; - } - - result = isc__cfgmgr_read(isc__cfgmgr_ctx.runningcursor, name, &val, - &valmode); - INSIST(result == ISC_R_NOTFOUND || result == ISC_R_SUCCESS); - - if (result == ISC_R_NOTFOUND || - (result == ISC_R_SUCCESS && valmode < ISC_CFGMGR_MODERUNNING)) - { - REQUIRE(isc__cfgmgr_write(isc__cfgmgr_ctx.runningcursor, name, - value, false) == ISC_R_SUCCESS); + if (node->flags & NODE_FLG_OPTIONS) { + /* + * Materialization of the changed options property in the + * running options node as well as all the running zones. + */ + isc__cfgmgr_materialize_apply(&property, NULL); + isc__cfgmgr_materialize_zone(&property, false); + } else if (node->flags & NODE_FLG_VIEW) { + /* + * Materialization of the changed view property in all its + * running zones + */ + isc__cfgmgr_materialize_zone(&property, true); } } diff --git a/tests/isc/cfgmgr_test.c b/tests/isc/cfgmgr_test.c index 9101753196..2b9027f138 100644 --- a/tests/isc/cfgmgr_test.c +++ b/tests/isc/cfgmgr_test.c @@ -1459,7 +1459,7 @@ struct isc__cfgmgr_dump { size_t indent; size_t lvl; isc_buffer_t buffer; - char data[512]; + char data[1024]; }; static void @@ -1506,6 +1506,21 @@ isc__cfgmgr_dumptestnode(void *state, const char *name) { isc_buffer_putstr(&dump->buffer, " "); } isc_buffer_printf(&dump->buffer, "};\n"); + +} + +static void +cmpdump(isc_cfgmgr_mode_t mode, const char *expected) { + isc__cfgmgr_dump_t dump = { + .lvl = 0, + .indent = 2, + }; + isc_buffer_init(&dump.buffer, dump.data, sizeof(dump.data)); + dump.data[0] = 0; + + isc_cfgmgr_setmode(mode); + isc_cfgmgr_foreachnodes(isc__cfgmgr_dumptestnode, &dump); + assert_string_equal(dump.buffer.base, expected); } ISC_RUN_TEST_IMPL(isc_cfgmgr_dumptests) { @@ -1566,41 +1581,32 @@ ISC_RUN_TEST_IMPL(isc_cfgmgr_dumptests) { isc_cfgmgr_close(); /* foo1 */ isc_cfgmgr_close(); /* foo */ - isc__cfgmgr_dump_t dump = { - .lvl = 0, - .indent = 8, - }; - isc_buffer_init(&dump.buffer, dump.data, sizeof(dump.data)); - dump.data[0] = 0; - isc_cfgmgr_commit(); isc_cfgmgr_transaction(); - isc_cfgmgr_foreachnodes(isc__cfgmgr_dumptestnode, &dump); - - const char expected[] = "bar {\n" - " another-stuff barstr;\n" - " propbar1 42;\n" - "};\n" - "foo {\n" - " foo1 {\n" - " foo11 {\n" - " fooprop 123;\n" - " };\n" - " foo12 {\n" - " fooprop 456;\n" - " prop 123;\n" - " };\n" - " foo13 {\n" - " fooprop 789;\n" - " prop 123;\n" - " };\n" - " };\n" - " foo2 {\n" - " another-foo-stuff foo2str;\n" - " };\n" - "};\n"; - assert_string_equal(dump.buffer.base, expected); + cmpdump(ISC_CFGMGR_MODERUNNING, + "bar {\n" + " another-stuff barstr;\n" + " propbar1 42;\n" + "};\n" + "foo {\n" + " foo1 {\n" + " foo11 {\n" + " fooprop 123;\n" + " };\n" + " foo12 {\n" + " fooprop 456;\n" + " prop 123;\n" + " };\n" + " foo13 {\n" + " fooprop 789;\n" + " prop 123;\n" + " };\n" + " };\n" + " foo2 {\n" + " another-foo-stuff foo2str;\n" + " };\n" + "};\n"); isc_cfgmgr_rollback(); result = isc_cfgmgr_rwtransaction(); @@ -1629,20 +1635,17 @@ ISC_RUN_TEST_IMPL(isc_cfgmgr_dumptests) { isc_cfgmgr_close(); isc_cfgmgr_close(); - isc_buffer_init(&dump.buffer, dump.data, sizeof(dump.data)); - isc_cfgmgr_foreachnodes(isc__cfgmgr_dumptestnode, &dump); - - const char expected2[] = "bar {\n" - " another-stuff barstr;\n" - " propbar1 42;\n" - "};\n" - "foo {\n" - " propfoo 987;\n" - " foo14 {\n" - " anotherpropfoo14 654;\n" - " };\n" - "};\n"; - assert_string_equal(dump.buffer.base, expected2); + cmpdump(ISC_CFGMGR_MODERUNNING, + "bar {\n" + " another-stuff barstr;\n" + " propbar1 42;\n" + "};\n" + "foo {\n" + " propfoo 987;\n" + " foo14 {\n" + " anotherpropfoo14 654;\n" + " };\n" + "};\n"); isc_cfgmgr_rollback(); isc_cfgmgr_deinit(); @@ -1658,6 +1661,38 @@ ISC_RUN_TEST_IMPL(isc_cfgmgr_materialization) { result = isc_cfgmgr_rwtransaction(); assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_open("views"); + + isc_cfgmgr_open("fooview"); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 991 }; + result = isc_cfgmgr_write("prop2", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_open("zones"); + isc_cfgmgr_open("foo.org"); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 661 }; + result = isc_cfgmgr_write("prop1", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_close(); + isc_cfgmgr_close(); + isc_cfgmgr_close(); + + isc_cfgmgr_open("barview"); + isc_cfgmgr_open("zones"); + isc_cfgmgr_open("bar.org"); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 662 }; + result = isc_cfgmgr_write("prop1", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_close(); + isc_cfgmgr_open("baz.org"); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 663 }; + result = isc_cfgmgr_write("prop1", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_close(); + isc_cfgmgr_close(); + isc_cfgmgr_close(); + + isc_cfgmgr_close(); /* views */ + isc_cfgmgr_open("options"); isc_cfgmgr_setmode(ISC_CFGMGR_MODERUNNING); @@ -1690,44 +1725,115 @@ ISC_RUN_TEST_IMPL(isc_cfgmgr_materialization) { isc_cfgmgr_close(); - isc__cfgmgr_dump_t dump = { - .lvl = 0, - .indent = 8, - }; - isc_buffer_init(&dump.buffer, dump.data, sizeof(dump.data)); - dump.data[0] = 0; + cmpdump(ISC_CFGMGR_MODEBUILTIN, "options {\n" + " prop1 1;\n" + " prop2 2;\n" + " prop3 3;\n" + "};\n"); - isc_cfgmgr_setmode(ISC_CFGMGR_MODEBUILTIN); - isc_cfgmgr_foreachnodes(isc__cfgmgr_dumptestnode, &dump); - const char expectedbuiltin[] = "options {\n" - " prop1 1;\n" - " prop2 2;\n" - " prop3 3;\n" - "};\n"; - assert_string_equal(dump.buffer.base, expectedbuiltin); + cmpdump(ISC_CFGMGR_MODEUSER, "options {\n" + " prop2 21;\n" + "};\n"); - isc_cfgmgr_setmode(ISC_CFGMGR_MODEUSER); - isc_buffer_init(&dump.buffer, dump.data, sizeof(dump.data)); - isc_cfgmgr_foreachnodes(isc__cfgmgr_dumptestnode, &dump); - const char expecteduser[] = "options {\n" - " prop2 21;\n" - "};\n"; - assert_string_equal(dump.buffer.base, expecteduser); - - isc_cfgmgr_setmode(ISC_CFGMGR_MODERUNNING); - isc_buffer_init(&dump.buffer, dump.data, sizeof(dump.data)); - isc_cfgmgr_foreachnodes(isc__cfgmgr_dumptestnode, &dump); - const char expectedrunning[] = "options {\n" - " prop1 31;\n" - " prop2 21;\n" - " prop3 3;\n" - " prop4 34;\n" - "};\n"; - assert_string_equal(dump.buffer.base, expectedrunning); + cmpdump(ISC_CFGMGR_MODERUNNING, "options {\n" + " prop1 31;\n" + " prop2 21;\n" + " prop3 3;\n" + " prop4 34;\n" + "};\n" + "views {\n" + " barview {\n" + " zones {\n" + " bar.org {\n" + " prop1 662;\n" + " prop2 21;\n" + " prop3 3;\n" + " prop4 34;\n" + " };\n" + " baz.org {\n" + " prop1 663;\n" + " prop2 21;\n" + " prop3 3;\n" + " prop4 34;\n" + " };\n" + " };\n" + " };\n" + " fooview {\n" + " prop2 991;\n" + " zones {\n" + " foo.org {\n" + " prop1 661;\n" + " prop2 991;\n" + " prop3 3;\n" + " prop4 34;\n" + " };\n" + " };\n" + " };\n" + "};\n"); result = isc_cfgmgr_commit(); assert_int_equal(result, ISC_R_SUCCESS); + /* + * deleting prop3 and prop2 from builtin: only prop3 will be removed + * from options, as it's unmodified. + */ + result = isc_cfgmgr_rwtransaction(); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_cfgmgr_setmode(ISC_CFGMGR_MODEBUILTIN); + isc_cfgmgr_open("options"); + + result = isc_cfgmgr_write("prop2", NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_cfgmgr_write("prop3", NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_cfgmgr_close(); + + cmpdump(ISC_CFGMGR_MODEBUILTIN, "options {\n" + " prop1 1;\n" + "};\n"); + + cmpdump(ISC_CFGMGR_MODEUSER, "options {\n" + " prop2 21;\n" + "};\n"); + + cmpdump(ISC_CFGMGR_MODERUNNING, "options {\n" + " prop1 31;\n" + " prop2 21;\n" + " prop4 34;\n" + "};\n" + "views {\n" + " barview {\n" + " zones {\n" + " bar.org {\n" + " prop1 662;\n" + " };\n" + " baz.org {\n" + " prop1 663;\n" + " };\n" + " };\n" + " };\n" + " fooview {\n" + " prop2 991;\n" + " zones {\n" + " foo.org {\n" + " prop1 661;\n" + " };\n" + " };\n" + " };\n" + "};\n"); + + isc_cfgmgr_rollback(); + + /* + * TODO + * Then a zone is added _after_ all of those options changes. It must + * contains all of those as well + */ + isc_cfgmgr_deinit(); }