Commit 3b5bd4ef authored by Libor Peltan's avatar Libor Peltan Committed by Daniel Salzman

zone: option for journal: none/changes/all

mostly refactoring of zone load event
parent f2c6f6c5
......@@ -85,6 +85,13 @@ static const knot_lookup_t serial_policies[] = {
{ 0, NULL }
};
static const knot_lookup_t journal_content[] = {
{ JOURNAL_CONTENT_NONE, "none" },
{ JOURNAL_CONTENT_CHANGES, "changes" },
{ JOURNAL_CONTENT_ALL, "all" },
{ 0, NULL }
};
static const knot_lookup_t log_severities[] = {
{ LOG_UPTO(LOG_CRIT), "critical" },
{ LOG_UPTO(LOG_ERR), "error" },
......@@ -260,7 +267,7 @@ static const yp_item_t desc_policy[] = {
{ C_SEM_CHECKS, YP_TBOOL, YP_VNONE, FLAGS }, \
{ C_DISABLE_ANY, YP_TBOOL, YP_VNONE }, \
{ C_ZONEFILE_SYNC, YP_TINT, YP_VINT = { -1, INT32_MAX, 0, YP_STIME } }, \
{ C_ZONE_IN_JOURNAL, YP_TBOOL, YP_VNONE }, \
{ C_JOURNAL_CONTENT, YP_TOPT, YP_VOPT = { journal_content, JOURNAL_CONTENT_CHANGES } }, \
{ C_IXFR_DIFF, YP_TBOOL, YP_VNONE }, \
{ C_MAX_ZONE_SIZE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, SSIZE_MAX, YP_SSIZE }, FLAGS }, \
{ C_MAX_JOURNAL_USAGE, YP_TINT, YP_VINT = { KILO(40), SSIZE_MAX, MEGA(100), YP_SSIZE } }, \
......
......@@ -53,8 +53,9 @@
#define C_ID "\x02""id"
#define C_IDENT "\x08""identity"
#define C_INCL "\x07""include"
#define C_IXFR_DIFF "\x15""ixfr-from-differences"
#define C_IXFR_DIFF "\x15""ixfr-from-differences" /* obsolete */
#define C_JOURNAL "\x07""journal" /* obsolete, old journal compat */
#define C_JOURNAL_CONTENT "\x0F""journal-content"
#define C_JOURNAL_DB "\x0A""journal-db"
#define C_JOURNAL_DB_MODE "\x0F""journal-db-mode"
#define C_KASP_DB "\x07""kasp-db"
......@@ -124,7 +125,6 @@
#define C_VERSION "\x07""version"
#define C_VIA "\x03""via"
#define C_ZONE "\x04""zone"
#define C_ZONE_IN_JOURNAL "\x0F""zone-in-journal"
#define C_ZONEFILE_SYNC "\x0D""zonefile-sync"
#define C_ZSK_LIFETIME "\x0C""zsk-lifetime"
#define C_ZSK_SIZE "\x08""zsk-size"
......@@ -139,6 +139,12 @@ enum {
SERIAL_POLICY_UNIXTIME = 2
};
enum {
JOURNAL_CONTENT_NONE = 0,
JOURNAL_CONTENT_CHANGES = 1,
JOURNAL_CONTENT_ALL = 2,
};
extern const knot_lookup_t acl_actions[];
extern const yp_item_t conf_schema[];
......
......@@ -53,150 +53,156 @@ static int post_load_dnssec_actions(conf_t *conf, zone_t *zone)
int event_load(conf_t *conf, zone_t *zone)
{
assert(zone);
zone_contents_t *journal_conts = NULL, *zf_conts = NULL;
bool old_contents_exist = (zone->contents != NULL);
uint32_t old_serial = (old_contents_exist ? zone_contents_serial(zone->contents) : 0);
conf_val_t val;
zone_contents_t *contents = NULL;
bool contents_in_update = true;
zone_sign_reschedule_t dnssec_refresh = { 0 };
dnssec_refresh.allow_rollover = true;
conf_val_t val = conf_zone_get(conf, C_JOURNAL_CONTENT, zone->name);
unsigned load_from = conf_opt(&val);
// if configured, load journal contents
if (load_from == JOURNAL_CONTENT_ALL && !old_contents_exist) {
int ret = zone_load_from_journal(conf, zone, &journal_conts);
if (ret != KNOT_EOK) {
journal_conts = NULL;
}
old_serial = zone_contents_serial(journal_conts);
}
// always attempt to load zonefile
time_t mtime;
char *filename = conf_zonefile(conf, zone->name);
int ret = zonefile_exists(filename, &mtime);
bool load_from_journal = (ret != KNOT_EOK);
bool zonefile_unchanged = (zone->zonefile.exists && zone->zonefile.mtime == mtime);
free(filename);
bool load_journal_first = false;
bool loaded_from_journal = false;
if (zone->contents == NULL) {
conf_val_t val = conf_zone_get(conf, C_ZONE_IN_JOURNAL, zone->name);
load_journal_first = conf_bool(&val);
}
if (load_from_journal) {
ret = zone_load_from_journal(conf, zone, &contents);
if (ret == KNOT_EOK) {
ret = zone_load_contents(conf, zone->name, &zf_conts);
if (ret != KNOT_EOK) {
if (zone_load_can_bootstrap(conf, zone->name)) {
log_zone_info(zone->name, "zone will be bootstrapped");
} else {
log_zone_info(zone->name, "zone not found");
}
goto fail;
}
goto load_post;
} else if (load_journal_first) {
ret = zone_load_from_journal(conf, zone, &contents);
if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
goto fail;
}
loaded_from_journal = (ret == KNOT_EOK);
if (loaded_from_journal) {
goto load_post;
zf_conts = NULL;
log_zone_warning(zone->name, "failed to parse zonefile (%s)",
knot_strerror(ret));
}
}
ret = zone_load_contents(conf, zone->name, &contents);
if (ret != KNOT_EOK) {
goto fail;
// if configured and appliable to zonefile, load journal changes
if (ret == KNOT_EOK) {
zone->zonefile.serial = zone_contents_serial(zf_conts);
zone->zonefile.exists = (zf_conts != NULL);
zone->zonefile.mtime = mtime;
bool journal_load_configured1 = (load_from == JOURNAL_CONTENT_CHANGES);
bool journal_load_configured2 = (load_from == JOURNAL_CONTENT_ALL);
if ((journal_load_configured1 || journal_load_configured2) &&
(!old_contents_exist || zonefile_unchanged)) {
ret = zone_load_journal(conf, zone, zf_conts);
if (ret != KNOT_EOK) {
zone_contents_deep_free(&zf_conts);
log_zone_warning(zone->name, "failed to load journal (%s)",
knot_strerror(ret));
}
}
}
if (load_journal_first && !loaded_from_journal) {
ret = zone_in_journal_store(conf, zone, contents);
// if configured contents=all, but not present, store zonefile
if (load_from == JOURNAL_CONTENT_ALL &&
journal_conts == NULL && zf_conts != NULL) {
ret = zone_in_journal_store(conf, zone, zf_conts);
if (ret != KNOT_EOK) {
log_zone_warning(zone->name, "failed to write zone-in-journal (%s)",
knot_strerror(ret));
}
}
/* Store the zonefile SOA serial. */
zone->zonefile.serial = zone_contents_serial(contents);
/* Apply journal if first load or reload with original zonefile. */
if (zone->contents == NULL ||
(zone->zonefile.exists && zone->zonefile.mtime == mtime)) {
ret = zone_load_journal(conf, zone, contents);
if (ret != KNOT_EOK) {
goto fail;
}
}
/* Store the zonefile mtime. */
zone->zonefile.mtime = mtime;
load_post:
val = conf_zone_get(conf, C_DNSSEC_SIGNING, zone->name);
bool dnssec_enable = conf_bool(&val);
val = conf_zone_get(conf, C_IXFR_DIFF, zone->name);
bool build_diffs = conf_bool(&val);
if (!build_diffs) {
val = conf_zone_get(conf, C_ZONE_IN_JOURNAL, zone->name);
build_diffs = conf_bool(&val);
}
bool old_contents = (zone->contents != NULL);
const bool contents_changed = old_contents && (contents != zone->contents);
/* Build the post-load update structure */
zone_update_t post_load = { 0 };
if (old_contents) {
if (build_diffs && contents_changed) {
ret = zone_update_from_differences(&post_load, zone, contents, UPDATE_INCREMENTAL);
if (ret == KNOT_ERANGE || ret == KNOT_ESEMCHECK) {
log_zone_warning(zone->name, (ret == KNOT_ESEMCHECK ?
"failed to create journal entry, zone file changed without "
"SOA serial update" : "IXFR history will be lost, "
"zone file changed, but SOA serial decreased"));
ret = zone_update_from_contents(&post_load, zone, contents, UPDATE_FULL);
zone_update_t up = { 0 };
// create zone_update structure according to current state
if (old_contents_exist) {
if (zf_conts == NULL) {
if (load_from == JOURNAL_CONTENT_ALL) {
// reload does nothing if we use purely journal
ret = KNOT_EOK;
goto cleanup;
} else {
ret = KNOT_ENOENT;
}
} else if (load_from == JOURNAL_CONTENT_NONE) {
ret = zone_update_from_contents(&up, zone, zf_conts, UPDATE_FULL);
} else {
ret = zone_update_from_contents(&post_load, zone, contents, UPDATE_FULL);
ret = zone_update_from_differences(&up, zone, zone->contents, zf_conts, UPDATE_INCREMENTAL);
if (ret == KNOT_ERANGE || ret == KNOT_ESEMCHECK) {
// when reload invoked, we force new zonefile if IXFR from diff fails
if (load_from != JOURNAL_CONTENT_ALL) {
log_zone_warning(zone->name, (ret == KNOT_ESEMCHECK ?
"failed to create journal entry, zone file changed without "
"SOA serial update" : "IXFR history will be lost, "
"zone file changed, but SOA serial decreased"));
ret = zone_update_from_contents(&up, zone, zf_conts, UPDATE_FULL);
}
}
}
} else {
ret = zone_update_from_contents(&post_load, zone, contents, UPDATE_INCREMENTAL);
if (journal_conts != NULL) {
if (zf_conts == NULL) {
ret = zone_update_from_contents(&up, zone, journal_conts, UPDATE_INCREMENTAL);
} else {
ret = zone_update_from_differences(&up, zone, journal_conts, zf_conts, UPDATE_INCREMENTAL);
zone_contents_deep_free(&journal_conts);
}
} else {
if (zf_conts == NULL) {
ret = KNOT_ENOENT;
} else {
ret = zone_update_from_contents(&up, zone, zf_conts, (load_from == JOURNAL_CONTENT_NONE ?
UPDATE_FULL : UPDATE_INCREMENTAL));
}
}
}
if (ret != KNOT_EOK) {
contents_in_update = false;
goto fail;
// TODO do we need some logging ?
goto cleanup;
}
/* Sign zone using DNSSEC (if configured). */
// the contents are already part of zone_update
zf_conts = NULL;
journal_conts = NULL;
// Sign zone using DNSSEC (if configured).
zone_sign_reschedule_t dnssec_refresh = { 0 };
dnssec_refresh.allow_rollover = true;
if (dnssec_enable) {
ret = post_load_dnssec_actions(conf, zone);
if (ret == KNOT_EOK) {
ret = knot_dnssec_zone_sign(&post_load, 0, &dnssec_refresh);
ret = knot_dnssec_zone_sign(&up, 0, &dnssec_refresh);
}
if (ret != KNOT_EOK) {
zone_update_clear(&post_load);
goto fail;
zone_update_clear(&up);
goto cleanup;
}
}
/* Everything went alright, switch the contents. */
zone->zonefile.exists = !load_from_journal;
uint32_t old_serial = zone_contents_serial(zone->contents);
ret = zone_update_commit(conf, &post_load);
zone_update_clear(&post_load);
// commit zone_update back to zone. This includes updating journal, rcu, ...
ret = zone_update_commit(conf, &up);
zone_update_clear(&up);
if (ret != KNOT_EOK) {
goto fail;
}
uint32_t current_serial = zone_contents_serial(zone->contents);
if (old_contents) {
log_zone_info(zone->name, "loaded, serial %u -> %u",
old_serial, current_serial);
} else {
log_zone_info(zone->name, "loaded, serial %u", current_serial);
goto cleanup;
}
uint32_t new_serial = zone_contents_serial(zone->contents);
if (zone->control_update != NULL) {
log_zone_warning(zone->name, "control transaction aborted");
zone_control_clear(zone);
}
if (old_contents_exist) {
log_zone_info(zone->name, "loaded, serial %u -> %u",
old_serial, new_serial);
} else {
log_zone_info(zone->name, "loaded, serial %u", new_serial);
}
/* Schedule depedent events. */
if (zone->control_update != NULL) {
log_zone_warning(zone->name, "control transaction aborted");
zone_control_clear(zone);
}
// Schedule depedent events.
const knot_rdataset_t *soa = zone_soa(zone);
zone->timers.soa_expire = knot_soa_expire(soa);
replan_from_timers(conf, zone);
......@@ -208,31 +214,18 @@ load_post:
event_dnssec_reschedule(conf, zone, &dnssec_refresh, false); // false since we handle NOTIFY below
}
// TODO: track serial across restart and avoid unnecessary notify
if (!load_from_journal && (!old_contents || old_serial != current_serial)) {
if (old_serial != new_serial) {
zone_events_schedule_now(zone, ZONE_EVENT_NOTIFY);
}
if (loaded_from_journal) {
// this enforces further load from zone file and applying ixfr from diff
zone_events_schedule_now(zone, ZONE_EVENT_LOAD);
}
return KNOT_EOK;
fail:
zone->zonefile.exists = false;
if (!contents_in_update) {
zone_contents_deep_free(&contents);
}
/* Try to bootstrap the zone if local error. */
cleanup:
// Try to bootstrap the zone if local error.
replan_from_timers(conf, zone);
if (load_from_journal && ret == KNOT_ENOENT) {
// attempted zone-in-journal, not present = normal state
ret = KNOT_EOK;
}
zone_contents_deep_free(&zf_conts);
zone_contents_deep_free(&journal_conts);
return ret;
}
......@@ -999,6 +999,8 @@ static int delete_merged_changeset(journal_t *j, txn_t *t)
return txn->ret;
}
static int delete_bootstrap_changeset(journal_t *j, txn_t *_txn);
static int drop_journal(journal_t *j, txn_t *_txn)
{
reuse_txn(txn, j, _txn, true);
......@@ -1009,6 +1011,7 @@ static int drop_journal(journal_t *j, txn_t *_txn)
if (md_flag(txn, SERIAL_TO_VALID) && !md_flag(txn, FIRST_SERIAL_INVALID)) {
delete_upto(j, txn, txn->shadow_md.first_serial, txn->shadow_md.last_serial);
}
delete_bootstrap_changeset(j, txn);
md_del_last_inserter_zone(txn, j->zone);
md_set(txn, j->zone, MDKEY_PERZONE_OCCUPIED, 0);
unreuse_txn(txn, _txn);
......@@ -1150,6 +1153,19 @@ static int delete_dirty_serial(journal_t *j, txn_t *_txn)
return txn->ret;
}
static int delete_bootstrap_changeset(journal_t *j, txn_t *_txn)
{
reuse_txn(txn, j, _txn, false);
uint32_t chunk = 0;
txn_key_str_u32(txn, j->zone, KEY_BOOTSTRAP_CHANGESET, chunk);
while (txn_find(txn)) {
txn_del(txn);
txn_key_str_u32(txn, j->zone, KEY_BOOTSTRAP_CHANGESET, ++chunk);
}
unreuse_txn(txn, _txn);
return txn->ret;
}
/*
* ***************************** PART VI ******************************
*
......
......@@ -113,7 +113,7 @@ int zone_update_init(zone_update_t *update, zone_t *zone, zone_update_flags_t fl
}
}
int zone_update_from_differences(zone_update_t *update, zone_t *zone,
int zone_update_from_differences(zone_update_t *update, zone_t *zone, zone_contents_t *old_cont,
zone_contents_t *new_cont, zone_update_flags_t flags)
{
if (update == NULL || zone == NULL || new_cont == NULL ||
......@@ -135,8 +135,8 @@ int zone_update_from_differences(zone_update_t *update, zone_t *zone,
return ret;
}
ret = zone_contents_diff(zone->contents, new_cont, &update->change);
if (ret != KNOT_EOK) {
ret = zone_contents_diff(old_cont, new_cont, &update->change);
if (ret != KNOT_EOK && ret != KNOT_ENODIFF) {
changeset_clear(&update->change);
return ret;
}
......@@ -540,10 +540,10 @@ static int commit_incremental(conf_t *conf, zone_update_t *update,
}
/* Write changes to journal if all went well. */
if (update->zone->journal_db != NULL) { // TODO this should be better
conf_val_t val = conf_zone_get(conf, C_JOURNAL_CONTENT, update->zone->name);
if (conf_opt(&val) != JOURNAL_CONTENT_NONE) {
ret = zone_change_store(conf, update->zone, &update->change);
if (ret != KNOT_EOK) {
/* Recoverable error. */
return ret;
}
}
......@@ -571,8 +571,9 @@ static int commit_full(conf_t *conf, zone_update_t *update, zone_contents_t **co
return ret;
}
conf_val_t val = conf_zone_get(conf, C_ZONE_IN_JOURNAL, update->zone->name);
if (conf_bool(&val)) {
/* Store new zone contents in journal. */
conf_val_t val = conf_zone_get(conf, C_JOURNAL_CONTENT, update->zone->name);
if (conf_opt(&val) == JOURNAL_CONTENT_ALL) {
ret = zone_in_journal_store(conf, update->zone, update->new_cont);
if (ret != KNOT_EOK) {
return ret;
......
......@@ -69,12 +69,13 @@ int zone_update_init(zone_update_t *update, zone_t *zone, zone_update_flags_t fl
*
* \param update Zone update structure to init.
* \param zone Init with this zone.
* \param old_cont The current zone contents the diff will be against. Probably zone->contents.
* \param new_cont New zone contents. Will be taken over (and later freed) by zone update.
* \param flags Flags for update. Must be UPDATE_INCREMENTAL.
*
* \return KNOT_E*
*/
int zone_update_from_differences(zone_update_t *update, zone_t *zone,
int zone_update_from_differences(zone_update_t *update, zone_t *zone, zone_contents_t *old_cont,
zone_contents_t *new_cont, zone_update_flags_t flags);
/*!
......
......@@ -387,6 +387,25 @@ int zone_flush_journal(conf_t *conf, zone_t *zone)
return ret;
}
int zone_journal_serial(conf_t *conf, zone_t *zone, bool *is_empty, uint32_t *serial_to)
{
if (conf == NULL || zone == NULL || is_empty == NULL || serial_to == NULL) {
return KNOT_EINVAL;
}
int ret = open_journal(zone);
kserial_t ks;
if (ret == KNOT_EOK) {
journal_metadata_info(zone->journal, is_empty, NULL, NULL, NULL, &ks, NULL);
}
*serial_to = (ks.valid ? ks.serial : 0);
return ret;
}
zone_contents_t *zone_switch_contents(zone_t *zone, zone_contents_t *new_contents)
{
if (zone == NULL) {
......
......@@ -134,6 +134,7 @@ int zone_changes_store(conf_t *conf, zone_t *zone, list_t *chgs);
int zone_changes_load(conf_t *conf, zone_t *zone, list_t *dst, uint32_t from);
int zone_in_journal_load(conf_t *conf, zone_t *zone, list_t *dst);
int zone_in_journal_store(conf_t *conf, zone_t *zone, zone_contents_t *new_contents);
int zone_journal_serial(conf_t *conf, zone_t *zone, bool *is_empty, uint32_t *serial_to);
/*! \brief Synchronize zone file with journal. */
int zone_flush_journal(conf_t *conf, zone_t *zone);
......
......@@ -31,6 +31,13 @@ static void discard_zone(zone_t *zone)
// Flush if bootstrapped or if the journal doesn't exist.
if (!zone->zonefile.exists || !journal_exists(zone->journal_db, zone->name)) {
zone_flush_journal(conf(), zone);
} else {
bool empty;
uint32_t journal_serial, zone_serial = zone_contents_serial(zone->contents);
int ret = zone_journal_serial(conf(), zone, &empty, &journal_serial);
if (ret != KNOT_EOK || empty || journal_serial != zone_serial) {
zone_flush_journal(conf(), zone);
}
}
}
......
......@@ -11,7 +11,7 @@ slave = t.server("knot")
zone = t.zone("example.com.")
t.link(zone, master, slave)
t.link(zone, master, slave, journal_content="all")
slave.zonefile_sync = "-1"
t.start()
......
......@@ -11,7 +11,7 @@ master = t.server("knot")
slave = t.server("knot")
zone = t.zone("xfr", storage=".")
t.link(zone, master, slave)
t.link(zone, master, slave, journal_content="none")
t.start()
......
......@@ -59,12 +59,13 @@ class ZoneDnssec(object):
class Zone(object):
'''DNS zone description'''
def __init__(self, zone_file, ddns=False, ixfr=False):
def __init__(self, zone_file, ddns=False, ixfr=False, journal_content="changes"):
self.zfile = zone_file
self.masters = set()
self.slaves = set()
self.ddns = ddns
self.ixfr = ixfr # ixfr from differences
self.ixfr = ixfr
self.journal_content = journal_content # journal contents
self.modules = []
self.dnssec = ZoneDnssec()
......@@ -170,12 +171,12 @@ class Server(object):
return False
def set_master(self, zone, slave=None, ddns=False, ixfr=False):
def set_master(self, zone, slave=None, ddns=False, ixfr=False, journal_content="changes"):
'''Set the server as a master for the zone'''
if zone.name not in self.zones:
master_file = zone.clone(self.dir + "/master")
z = Zone(master_file, ddns, ixfr)
z = Zone(master_file, ddns, ixfr, journal_content)
self.zones[zone.name] = z
else:
z = self.zones[zone.name]
......@@ -183,13 +184,13 @@ class Server(object):
if slave:
z.slaves.add(slave)
def set_slave(self, zone, master, ddns=False, ixfr=False):
def set_slave(self, zone, master, ddns=False, ixfr=False, journal_content="changes"):
'''Set the server as a slave for the zone'''
slave_file = zone.clone(self.dir + "/slave", exists=False)
if zone.name not in self.zones:
z = Zone(slave_file, ddns, ixfr)
z = Zone(slave_file, ddns, ixfr, journal_content)
self.zones[zone.name] = z
else:
z = self.zones[zone.name]
......@@ -1199,8 +1200,7 @@ class Knot(Server):
acl += "acl_local, acl_test"
s.item("acl", "[%s]" % acl)
if z.ixfr and not z.masters:
s.item_str("ixfr-from-differences", "on")
s.item_str("journal-content", z.journal_content)
if z.dnssec.enable:
s.item_str("dnssec-signing", "on")
......
......@@ -297,16 +297,16 @@ class Test(object):
return zones
def link(self, zones, master, slave=None, ddns=False, ixfr=False):
def link(self, zones, master, slave=None, ddns=False, ixfr=False, journal_content="changes"):
for zone in zones:
if master not in self.servers:
raise Failed("Server is out of testing scope")
master.set_master(zone, slave, ddns, ixfr)
master.set_master(zone, slave, ddns, ixfr, journal_content)
if slave:
if slave not in self.servers:
raise Failed("Server is out of testing scope")
slave.set_slave(zone, master, ddns, ixfr)
slave.set_slave(zone, master, ddns, ixfr, journal_content)
def _canonize_record(self, rtype, record):
''':-(('''
......
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