diff options
Diffstat (limited to '')
-rw-r--r-- | daemon/bindings/cache.c | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/daemon/bindings/cache.c b/daemon/bindings/cache.c new file mode 100644 index 0000000..d42ff62 --- /dev/null +++ b/daemon/bindings/cache.c @@ -0,0 +1,382 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "daemon/bindings/impl.h" + +/** @internal return cache, or throw lua error if not open */ +static struct kr_cache * cache_assert_open(lua_State *L) +{ + struct kr_cache *cache = &the_worker->engine->resolver.cache; + if (kr_fails_assert(cache) || !kr_cache_is_open(cache)) + lua_error_p(L, "no cache is open yet, use cache.open() or cache.size, etc."); + return cache; +} + +/** Return available cached backends. */ +static int cache_backends(lua_State *L) +{ + struct engine *engine = the_worker->engine; + + lua_newtable(L); + for (unsigned i = 0; i < engine->backends.len; ++i) { + const struct kr_cdb_api *api = engine->backends.at[i]; + lua_pushboolean(L, api == engine->resolver.cache.api); + lua_setfield(L, -2, api->name); + } + return 1; +} + +/** Return number of cached records. */ +static int cache_count(lua_State *L) +{ + struct kr_cache *cache = cache_assert_open(L); + + int count = cache->api->count(cache->db, &cache->stats); + if (count >= 0) { + /* First key is a version counter, omit it if nonempty. */ + lua_pushinteger(L, count ? count - 1 : 0); + return 1; + } + return 0; +} + +/** Return time of last checkpoint, or re-set it if passed `true`. */ +static int cache_checkpoint(lua_State *L) +{ + struct kr_cache *cache = cache_assert_open(L); + + if (lua_gettop(L) == 0) { /* Return the current value. */ + lua_newtable(L); + lua_pushnumber(L, cache->checkpoint_monotime); + lua_setfield(L, -2, "monotime"); + lua_newtable(L); + lua_pushnumber(L, cache->checkpoint_walltime.tv_sec); + lua_setfield(L, -2, "sec"); + lua_pushnumber(L, cache->checkpoint_walltime.tv_usec); + lua_setfield(L, -2, "usec"); + lua_setfield(L, -2, "walltime"); + return 1; + } + + if (lua_gettop(L) != 1 || !lua_isboolean(L, 1) || !lua_toboolean(L, 1)) + lua_error_p(L, "cache.checkpoint() takes no parameters or a true value"); + + kr_cache_make_checkpoint(cache); + return 1; +} + +/** Return cache statistics. */ +static int cache_stats(lua_State *L) +{ + struct kr_cache *cache = cache_assert_open(L); + lua_newtable(L); +#define add_stat(name) \ + lua_pushinteger(L, (cache->stats.name)); \ + lua_setfield(L, -2, #name) + add_stat(open); + add_stat(close); + add_stat(count); + cache->stats.count_entries = cache->api->count(cache->db, &cache->stats); + add_stat(count_entries); + add_stat(clear); + add_stat(commit); + add_stat(read); + add_stat(read_miss); + add_stat(write); + add_stat(remove); + add_stat(remove_miss); + add_stat(match); + add_stat(match_miss); + add_stat(read_leq); + add_stat(read_leq_miss); + /* usage_percent statistics special case - double */ + cache->stats.usage_percent = cache->api->usage_percent(cache->db); + lua_pushnumber(L, cache->stats.usage_percent); + lua_setfield(L, -2, "usage_percent"); +#undef add_stat + + return 1; +} + +static const struct kr_cdb_api *cache_select(struct engine *engine, const char **conf) +{ + /* Return default backend */ + if (*conf == NULL || !strstr(*conf, "://")) { + return engine->backends.at[0]; + } + + /* Find storage backend from config prefix */ + for (unsigned i = 0; i < engine->backends.len; ++i) { + const struct kr_cdb_api *api = engine->backends.at[i]; + if (strncmp(*conf, api->name, strlen(api->name)) == 0) { + *conf += strlen(api->name) + strlen("://"); + return api; + } + } + + return NULL; +} + +static int cache_max_ttl(lua_State *L) +{ + struct kr_cache *cache = cache_assert_open(L); + + int n = lua_gettop(L); + if (n > 0) { + if (!lua_isnumber(L, 1) || n > 1) + lua_error_p(L, "expected 'max_ttl(number ttl)'"); + uint32_t min = cache->ttl_min; + int64_t ttl = lua_tointeger(L, 1); + if (ttl < 1 || ttl < min || ttl > TTL_MAX_MAX) { + lua_error_p(L, + "max_ttl must be larger than minimum TTL, and in range <1, " + STR(TTL_MAX_MAX) ">'"); + } + cache->ttl_max = ttl; + } + lua_pushinteger(L, cache->ttl_max); + return 1; +} + + +static int cache_min_ttl(lua_State *L) +{ + struct kr_cache *cache = cache_assert_open(L); + + int n = lua_gettop(L); + if (n > 0) { + if (!lua_isnumber(L, 1)) + lua_error_p(L, "expected 'min_ttl(number ttl)'"); + uint32_t max = cache->ttl_max; + int64_t ttl = lua_tointeger(L, 1); + if (ttl < 0 || ttl > max || ttl > TTL_MAX_MAX) { + lua_error_p(L, + "min_ttl must be smaller than maximum TTL, and in range <0, " + STR(TTL_MAX_MAX) ">'"); + } + cache->ttl_min = ttl; + } + lua_pushinteger(L, cache->ttl_min); + return 1; +} + +/** Open cache */ +static int cache_open(lua_State *L) +{ + /* Check parameters */ + int n = lua_gettop(L); + if (n < 1 || !lua_isnumber(L, 1)) + lua_error_p(L, "expected 'open(number max_size, string config = \"\")'"); + + /* Select cache storage backend */ + struct engine *engine = the_worker->engine; + + lua_Integer csize_lua = lua_tointeger(L, 1); + if (!(csize_lua >= 8192 && csize_lua < SIZE_MAX)) { /* min. is basically arbitrary */ + lua_error_p(L, "invalid cache size specified, it must be in range <8192, " + STR(SIZE_MAX) ">"); + } + size_t cache_size = csize_lua; + + const char *conf = n > 1 ? lua_tostring(L, 2) : NULL; + const char *uri = conf; + const struct kr_cdb_api *api = cache_select(engine, &conf); + if (!api) + lua_error_p(L, "unsupported cache backend"); + + /* Close if already open */ + kr_cache_close(&engine->resolver.cache); + + /* Reopen cache */ + struct kr_cdb_opts opts = { + (conf && strlen(conf)) ? conf : ".", + cache_size + }; + int ret = kr_cache_open(&engine->resolver.cache, api, &opts, engine->pool); + if (ret != 0) { + char cwd[PATH_MAX]; + get_workdir(cwd, sizeof(cwd)); + return luaL_error(L, "can't open cache path '%s'; working directory '%s'; %s", + opts.path, cwd, kr_strerror(ret)); + } + /* Let's check_health() every five seconds to avoid keeping old cache alive + * even in case of not having any work to do. */ + ret = kr_cache_check_health(&engine->resolver.cache, 5000); + if (ret != 0) { + kr_log_error(CACHE, "periodic health check failed (ignored): %s\n", + kr_strerror(ret)); + } + + /* Store current configuration */ + lua_getglobal(L, "cache"); + lua_pushstring(L, "current_size"); + lua_pushnumber(L, cache_size); + lua_rawset(L, -3); + lua_pushstring(L, "current_storage"); + lua_pushstring(L, uri); + lua_rawset(L, -3); + lua_pop(L, 1); + + lua_pushboolean(L, 1); + return 1; +} + +static int cache_close(lua_State *L) +{ + struct kr_cache *cache = &the_worker->engine->resolver.cache; + if (!kr_cache_is_open(cache)) { + return 0; + } + + kr_cache_close(cache); + lua_getglobal(L, "cache"); + lua_pushstring(L, "current_size"); + lua_pushnumber(L, 0); + lua_rawset(L, -3); + lua_pop(L, 1); + lua_pushboolean(L, 1); + return 1; +} + +#if 0 +/** @internal Prefix walk. */ +static int cache_prefixed(struct kr_cache *cache, const char *prefix, bool exact_name, + knot_db_val_t keyval[][2], int maxcount) +{ + /* Convert to domain name */ + uint8_t buf[KNOT_DNAME_MAXLEN]; + if (!knot_dname_from_str(buf, prefix, sizeof(buf))) { + return kr_error(EINVAL); + } + /* Start prefix search */ + return kr_cache_match(cache, buf, exact_name, keyval, maxcount); +} +#endif + +/** Clear everything. */ +static int cache_clear_everything(lua_State *L) +{ + struct kr_cache *cache = cache_assert_open(L); + + /* Clear records and packets. */ + int ret = kr_cache_clear(cache); + lua_error_maybe(L, ret); + + /* Clear reputation tables */ + struct kr_context *ctx = &the_worker->engine->resolver; + lru_reset(ctx->cache_cookie); + lua_pushboolean(L, true); + return 1; +} + +#if 0 +/** @internal Dump cache key into table on Lua stack. */ +static void cache_dump(lua_State *L, knot_db_val_t keyval[]) +{ + knot_dname_t dname[KNOT_DNAME_MAXLEN]; + char name[KNOT_DNAME_TXT_MAXLEN]; + uint16_t type; + + int ret = kr_unpack_cache_key(keyval[0], dname, &type); + if (ret < 0) { + return; + } + + ret = !knot_dname_to_str(name, dname, sizeof(name)); + if (kr_fails_assert(!ret)) return; + + /* If name typemap doesn't exist yet, create it */ + lua_getfield(L, -1, name); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + lua_newtable(L); + } + /* Append to typemap */ + char type_buf[KR_RRTYPE_STR_MAXLEN] = { '\0' }; + knot_rrtype_to_string(type, type_buf, sizeof(type_buf)); + lua_pushboolean(L, true); + lua_setfield(L, -2, type_buf); + /* Set name typemap */ + lua_setfield(L, -2, name); +} + +/** Query cached records. TODO: fix caveats in ./README.rst documentation? */ +static int cache_get(lua_State *L) +{ + //struct kr_cache *cache = cache_assert_open(L); // to be fixed soon + + /* Check parameters */ + int n = lua_gettop(L); + if (n < 1 || !lua_isstring(L, 1)) + lua_error_p(L, "expected 'cache.get(string key)'"); + + /* Retrieve set of keys */ + const char *prefix = lua_tostring(L, 1); + knot_db_val_t keyval[100][2]; + int ret = cache_prefixed(cache, prefix, false/*FIXME*/, keyval, 100); + lua_error_maybe(L, ret); + /* Format output */ + lua_newtable(L); + for (int i = 0; i < ret; ++i) { + cache_dump(L, keyval[i]); + } + return 1; +} +#endif +static int cache_get(lua_State *L) +{ + lua_error_maybe(L, ENOSYS); + return kr_error(ENOSYS); /* doesn't happen */ +} + +/** Set time interval for cleaning rtt cache. + * Servers with score >= KR_NS_TIMEOUT will be cleaned after + * this interval ended up, so that they will be able to participate + * in NS elections again. */ +static int cache_ns_tout(lua_State *L) +{ + struct kr_context *ctx = &the_worker->engine->resolver; + + /* Check parameters */ + int n = lua_gettop(L); + if (n < 1) { + lua_pushinteger(L, ctx->cache_rtt_tout_retry_interval); + return 1; + } + + if (!lua_isnumber(L, 1)) + lua_error_p(L, "expected 'cache.ns_tout(interval in ms)'"); + + lua_Integer interval_lua = lua_tointeger(L, 1); + if (!(interval_lua > 0 && interval_lua < UINT_MAX)) { + lua_error_p(L, "invalid interval specified, it must be in range > 0, < " + STR(UINT_MAX)); + } + + ctx->cache_rtt_tout_retry_interval = interval_lua; + lua_pushinteger(L, ctx->cache_rtt_tout_retry_interval); + return 1; +} + +int kr_bindings_cache(lua_State *L) +{ + static const luaL_Reg lib[] = { + { "backends", cache_backends }, + { "count", cache_count }, + { "stats", cache_stats }, + { "checkpoint", cache_checkpoint }, + { "open", cache_open }, + { "close", cache_close }, + { "clear_everything", cache_clear_everything }, + { "get", cache_get }, + { "max_ttl", cache_max_ttl }, + { "min_ttl", cache_min_ttl }, + { "ns_tout", cache_ns_tout }, + { NULL, NULL } + }; + + luaL_register(L, "cache", lib); + return 1; +} + |