From: Jorge Pereira Date: Thu, 21 Oct 2021 14:17:55 +0000 (-0300) Subject: Add methods cache.{store,load,status,clear,ttl} for rlm_cache (#3013) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c1ca4142ff78c48cf839e5d79443846d55bb54b9;p=thirdparty%2Ffreeradius-server.git Add methods cache.{store,load,status,clear,ttl} for rlm_cache (#3013) * Add methods cache.{store,load,status,clear,ttl} for rlm_cache Now the rlm_cache has the capability of handling the cache entries using methods. * Add tests for the methods It adds tests for the new rlm_cache methods. * Update the raddb/mods-available/cache Add references related to the new rlm_cache methods. --- diff --git a/raddb/mods-available/cache b/raddb/mods-available/cache index 1e3e5a279eb..19f40199be2 100644 --- a/raddb/mods-available/cache +++ b/raddb/mods-available/cache @@ -209,6 +209,10 @@ cache { &reply.Class := "%{randstr:ssssssssssssssssssssssssssssssss}" } + # + # ## How to use + # + # ### Configuration # # This module supports a number of runtime configuration parameters # represented by attributes in the `&control.` list. @@ -253,7 +257,107 @@ cache { # cache entries into the current request. Useful if results of execs or # expansions are stored directly in the cache. # + # # NOTE: All runtime configuration attributes will be removed from the # `&control.` list after the cache module is called. # + # ### Methods + # + # The cache module also allows handling the cache using the methods. + # + # cache.status:: Verify if an entry already exists without load the entries. + # + # [options="header,autowidth"] + # |=== + # | Return | Description + # | `ok` | if a cache entry was found. + # | `notfound` | if no cache entry was found. + # |=== + # + # cache.load:: Load an existing cache entry and merge it into the request. + # + # [options="header,autowidth"] + # |=== + # | Return | Description + # | `updated` | if a cache entry was found and loaded. + # | `notfound` | if no cache entry was found. + # |=== + # + # cache.store:: Perform an upset against the data store. (Not affect the existing + # request). + # + # [options="header,autowidth"] + # |=== + # | Return | Description + # | `updated` | if we added cache entry. + # | `noop` | if a cache entry ready exists. + # |=== + # + # cache.clear:: Delete cache entry from the data store without checking if the entry + # already exists. + # + # [options="header,autowidth"] + # |=== + # | Return | Description + # | `ok` | if we found and remove a entry. + # | `notfound` | if no cache entry was found. + # |=== + # + # cache.ttl:: Change the TTL on an existing entry. + # + # [options="header,autowidth"] + # |=== + # | Return | Description + # | `updated` | if we found entry and updated the ttl. + # | `notfound` | if no cache entry was found. + # |=== + # + # ### Examples + # + # ``` + # # Add a cache entry + # update { + # &control.Cache-TTL := 3600 # 1h + # } + # cache.store + # if (updated) { + # ..keys stored + # } + # + # # Get the cache status + # cache.status + # if (ok) { + # ..Exist a cache entry + # } + # + # # Load the cache entry + # cache.load + # if (updated) { + # ..loaded + # } + # + # # Change the entries TTL + # update { + # &control.Cache-TTL := 1600 # 30m + # } + # cache.ttl + # if (updated) { + # ..ttl changed + # } + # + # # Clear the cache + # cache.clear + # if (ok) { + # ..cache is empty + # } + # ``` + # + # [NOTE] + # ==== + # * This is evaluated before `Cache-TTL`, so entries being expired + # may first be merged. + # * All runtime configuration attributes will be removed from the + # `&control:` list after any cache method is called. + # ==== + # } diff --git a/src/modules/rlm_cache/rlm_cache.c b/src/modules/rlm_cache/rlm_cache.c index e9d1d28a74f..5d3f680c04a 100644 --- a/src/modules/rlm_cache/rlm_cache.c +++ b/src/modules/rlm_cache/rlm_cache.c @@ -897,6 +897,45 @@ static xlat_action_t cache_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, request_t *r return XLAT_ACTION_DONE; } +/** Release the allocated resources and cleanup the avps + */ +static void cache_unref(request_t *request, rlm_cache_t const *inst, rlm_cache_entry_t *entry, + rlm_cache_handle_t *handle) +{ + fr_dcursor_t cursor; + fr_pair_t *vp; + + /* + * Release the driver calls + */ + cache_free(inst, &entry); + cache_release(inst, request, &handle); + + /* + * Clear control attributes + */ + for (vp = fr_dcursor_init(&cursor, &request->control_pairs); + vp; + vp = fr_dcursor_next(&cursor)) { + again: + if (!fr_dict_attr_is_top_level(vp->da)) continue; + + switch (vp->da->attr) { + case FR_CACHE_TTL: + case FR_CACHE_STATUS_ONLY: + case FR_CACHE_ALLOW_MERGE: + case FR_CACHE_ALLOW_INSERT: + case FR_CACHE_MERGE_NEW: + RDEBUG2("Removing &control:%s", vp->da->name); + vp = fr_dcursor_remove(&cursor); + TALLOC_FREE(vp); + vp = fr_dcursor_current(&cursor); + if (!vp) break; + goto again; + } + } +} + /** Free any memory allocated under the instance * */ @@ -1033,6 +1072,334 @@ static int mod_instantiate(void *instance, CONF_SECTION *conf) return 0; } +/** Get the status by ${key} (without load) + * + * @return + * - #RLM_MODULE_OK on success. + * - #RLM_MODULE_NOTFOUND on cache miss. + * - #RLM_MODULE_FAIL on failure. + */ +static unlang_action_t CC_HINT(nonnull) mod_method_status(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +{ + rlm_cache_t const *inst = talloc_get_type_abort(mctx->instance, rlm_cache_t); + rlm_rcode_t rcode = RLM_MODULE_NOOP; + uint8_t buffer[1024]; + uint8_t const *key; + ssize_t key_len; + rlm_cache_entry_t *entry = NULL; + rlm_cache_handle_t *handle = NULL; + + DEBUG3("Calling %s.status", inst->config.name); + + key_len = tmpl_expand((char const **)&key, (char *)buffer, sizeof(buffer), + request, inst->config.key, NULL, NULL); + if (key_len < 0) { + RETURN_MODULE_FAIL; + } + + if (key_len == 0) { + REDEBUG("Zero length key string is invalid"); + RETURN_MODULE_FAIL; + } + + /* Good to go? */ + if (cache_acquire(&handle, inst, request) < 0) { + RETURN_MODULE_FAIL; + } + + fr_assert(!inst->driver->acquire || handle); + + cache_find(&rcode, &entry, inst, request, &handle, key, key_len); + if (rcode == RLM_MODULE_FAIL) goto finish; + + rcode = (entry) ? RLM_MODULE_OK : RLM_MODULE_NOTFOUND; + +finish: + cache_unref(request, inst, entry, handle); + + RETURN_MODULE_RCODE(rcode); +} + +/** Load the avps by ${key}. + * + * @return + * - #RLM_MODULE_UPDATED on success. + * - #RLM_MODULE_NOTFOUND on cache miss. + * - #RLM_MODULE_FAIL on failure. + */ +static unlang_action_t CC_HINT(nonnull) mod_method_load(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +{ + rlm_cache_t const *inst = talloc_get_type_abort(mctx->instance, rlm_cache_t); + rlm_rcode_t rcode = RLM_MODULE_NOOP; + uint8_t buffer[1024]; + uint8_t const *key; + ssize_t key_len; + rlm_cache_entry_t *entry = NULL; + rlm_cache_handle_t *handle = NULL; + + DEBUG3("Calling %s.load", inst->config.name); + + key_len = tmpl_expand((char const **)&key, (char *)buffer, sizeof(buffer), + request, inst->config.key, NULL, NULL); + if (key_len < 0) { + RETURN_MODULE_FAIL; + } + + if (key_len == 0) { + REDEBUG("Zero length key string is invalid"); + RETURN_MODULE_FAIL; + } + + /* Good to go? */ + if (cache_acquire(&handle, inst, request) < 0) { + RETURN_MODULE_FAIL; + } + + cache_find(&rcode, &entry, inst, request, &handle, key, key_len); + if (rcode == RLM_MODULE_FAIL) goto finish; + + if (!entry) { + WARN("Entry not found to be load"); + rcode = RLM_MODULE_NOTFOUND; + goto finish; + } + + rcode = cache_merge(inst, request, entry); + +finish: + cache_unref(request, inst, entry, handle); + + RETURN_MODULE_RCODE(rcode); +} + +/** Create and insert a cache entry + * + * @return + * - #RLM_MODULE_OK on success. + * - #RLM_MODULE_UPDATED if we merged the cache entry. + * - #RLM_MODULE_FAIL on failure. + */ +static unlang_action_t CC_HINT(nonnull) mod_method_store(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +{ + rlm_cache_t const *inst = talloc_get_type_abort(mctx->instance, rlm_cache_t); + rlm_rcode_t rcode = RLM_MODULE_NOOP; + uint8_t buffer[1024]; + uint8_t const *key; + ssize_t key_len; + uint32_t ttl = 0; + bool expire = false; + rlm_cache_entry_t *entry = NULL; + rlm_cache_handle_t *handle = NULL; + fr_pair_t *vp; + + DEBUG3("Calling %s.store", inst->config.name); + + key_len = tmpl_expand((char const **)&key, (char *)buffer, sizeof(buffer), + request, inst->config.key, NULL, NULL); + if (key_len < 0) { + RETURN_MODULE_FAIL; + } + + if (key_len == 0) { + REDEBUG("Zero length key string is invalid"); + RETURN_MODULE_FAIL; + } + + /* Good to go? */ + if (cache_acquire(&handle, inst, request) < 0) { + RETURN_MODULE_FAIL; + } + + /* Process the TTL */ + ttl = inst->config.ttl; /* Set the default value from cache { ttl=... } */ + vp = fr_pair_find_by_da(&request->control_pairs, attr_cache_ttl, 0); + if (vp) { + if (vp->vp_int32 == 0) { + expire = true; + } else if (vp->vp_int32 < 0) { + ttl = -(vp->vp_int32); + /* Updating the TTL */ + } else { + ttl = vp->vp_int32; + } + + DEBUG3("Overwriting the default TTL %d -> %d", inst->config.ttl, vp->vp_int32); + } + + /* + * We can only alter the TTL on an entry if it exists. + */ + cache_find(&rcode, &entry, inst, request, &handle, key, key_len); + if (rcode == RLM_MODULE_FAIL) goto finish; + + if (rcode == RLM_MODULE_OK) { + fr_assert(entry != NULL); + + DEBUG3("Updating the TTL -> %d", ttl); + + entry->expires = fr_time_to_sec(request->packet->timestamp) + ttl; + + cache_set_ttl(&rcode, inst, request, &handle, entry); + if (rcode == RLM_MODULE_FAIL) goto finish; + } + + /* + * Expire the entry if told to, and we either don't know whether + * it exists, or we know it does. + * + * We only expire if we're not inserting, as driver insert methods + * should perform upserts. + */ + if (expire) { + DEBUG4("Set the cache expire"); + + cache_expire(&rcode, inst, request, &handle, key, key_len); + if (rcode == RLM_MODULE_FAIL) goto finish; + } + + /* + * Inserts are upserts, so we don't care about the + * entry state, just that we're not meant to be + * setting the TTL, which precludes performing an + * insert. + */ + cache_insert(&rcode, inst, request, &handle, key, key_len, ttl); + if (rcode == RLM_MODULE_OK) rcode = RLM_MODULE_UPDATED; + +finish: + cache_unref(request, inst, entry, handle); + + RETURN_MODULE_RCODE(rcode); +} + +/** Delete the entries by ${key} + * + * @return + * - #RLM_MODULE_OK on success. + * - #RLM_MODULE_NOTFOUND on cache miss. + * - #RLM_MODULE_FAIL on failure. + */ +static unlang_action_t CC_HINT(nonnull) mod_method_clear(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +{ + rlm_cache_t const *inst = talloc_get_type_abort(mctx->instance, rlm_cache_t); + rlm_rcode_t rcode = RLM_MODULE_NOOP; + uint8_t buffer[1024]; + uint8_t const *key; + ssize_t key_len; + rlm_cache_entry_t *entry = NULL; + rlm_cache_handle_t *handle = NULL; + + DEBUG3("Calling %s.clear", inst->config.name); + + key_len = tmpl_expand((char const **)&key, (char *)buffer, sizeof(buffer), + request, inst->config.key, NULL, NULL); + if (key_len < 0) { + RETURN_MODULE_FAIL; + } + + if (key_len == 0) { + REDEBUG("Zero length key string is invalid"); + RETURN_MODULE_FAIL; + } + + /* Good to go? */ + if (cache_acquire(&handle, inst, request) < 0) { + RETURN_MODULE_FAIL; + } + + cache_find(&rcode, &entry, inst, request, &handle, key, key_len); + if (rcode == RLM_MODULE_FAIL) goto finish; + + if (!entry) { + WARN("Entry not found to be deleted"); + rcode = RLM_MODULE_NOTFOUND; + goto finish; + } + + cache_expire(&rcode, inst, request, &handle, key, key_len); + +finish: + cache_unref(request, inst, entry, handle); + + RETURN_MODULE_RCODE(rcode); +} + +/** Change the TTL on an existing entry. + * + * @return + * - #RLM_MODULE_UPDATED on success. + * - #RLM_MODULE_NOTFOUND on cache miss. + * - #RLM_MODULE_FAIL on failure. + */ +static unlang_action_t CC_HINT(nonnull) mod_method_ttl(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +{ + rlm_cache_t const *inst = talloc_get_type_abort(mctx->instance, rlm_cache_t); + rlm_rcode_t rcode = RLM_MODULE_NOOP; + uint8_t buffer[1024]; + uint8_t const *key; + ssize_t key_len; + uint32_t ttl = 0; + rlm_cache_entry_t *entry = NULL; + rlm_cache_handle_t *handle = NULL; + fr_pair_t *vp; + + DEBUG3("Calling %s.ttl", inst->config.name); + + key_len = tmpl_expand((char const **)&key, (char *)buffer, sizeof(buffer), + request, inst->config.key, NULL, NULL); + if (key_len < 0) { + RETURN_MODULE_FAIL; + } + + if (key_len == 0) { + REDEBUG("Zero length key string is invalid"); + RETURN_MODULE_FAIL; + } + + /* Good to go? */ + if (cache_acquire(&handle, inst, request) < 0) { + RETURN_MODULE_FAIL; + } + + /* Process the TTL */ + ttl = inst->config.ttl; /* Set the default value from cache { ttl=... } */ + vp = fr_pair_find_by_da(&request->control_pairs, attr_cache_ttl, 0); + if (vp) { + if (vp->vp_int32 < 0) { + ttl = -(vp->vp_int32); + /* Updating the TTL */ + } else { + ttl = vp->vp_int32; + } + + DEBUG3("Overwriting the default TTL %d -> %d", inst->config.ttl, vp->vp_int32); + } + + /* + * We can only alter the TTL on an entry if it exists. + */ + cache_find(&rcode, &entry, inst, request, &handle, key, key_len); + if (rcode == RLM_MODULE_FAIL) goto finish; + + if (rcode == RLM_MODULE_OK) { + fr_assert(entry != NULL); + + DEBUG3("Updating the TTL -> %d", ttl); + + entry->expires = fr_time_to_sec(request->packet->timestamp) + ttl; + + cache_set_ttl(&rcode, inst, request, &handle, entry); + if (rcode == RLM_MODULE_FAIL) goto finish; + + rcode = RLM_MODULE_UPDATED; + } + +finish: + cache_unref(request, inst, entry, handle); + + RETURN_MODULE_RCODE(rcode); +} + /* * The module name should be the only globally exported symbol. * That is, everything else should be 'static'. @@ -1056,4 +1423,13 @@ module_t rlm_cache = { [MOD_ACCOUNTING] = mod_cache_it, [MOD_POST_AUTH] = mod_cache_it }, + .method_names = (module_method_names_t[]){ + { .name1 = "status", .name2 = CF_IDENT_ANY, .method = mod_method_status }, + { .name1 = "load", .name2 = CF_IDENT_ANY, .method = mod_method_load }, + { .name1 = "store", .name2 = CF_IDENT_ANY, .method = mod_method_store }, + { .name1 = "clear", .name2 = CF_IDENT_ANY, .method = mod_method_clear }, + { .name1 = "ttl", .name2 = CF_IDENT_ANY, .method = mod_method_ttl }, + + MODULE_NAME_TERMINATOR + } }; diff --git a/src/tests/modules/cache_rbtree/cache-bin.unlang b/src/tests/modules/cache_rbtree/cache-bin.unlang index d4a407a5fe9..1d7a64fa833 100644 --- a/src/tests/modules/cache_rbtree/cache-bin.unlang +++ b/src/tests/modules/cache_rbtree/cache-bin.unlang @@ -17,8 +17,8 @@ if (&Tmp-String-1 != "foo\000bar\000baz") { } # 1. Store the entry -cache_bin_key_octets -if (!ok) { +cache_bin_key_octets.store +if (!updated) { test_fail } @@ -29,8 +29,8 @@ update { } # 2. Should create a *new* entry and not update the existing one -cache_bin_key_octets -if (!ok) { +cache_bin_key_octets.store +if (!updated) { test_fail } diff --git a/src/tests/modules/cache_rbtree/cache-method-bin.attrs b/src/tests/modules/cache_rbtree/cache-method-bin.attrs new file mode 100644 index 00000000000..2c3f3ac57f0 --- /dev/null +++ b/src/tests/modules/cache_rbtree/cache-method-bin.attrs @@ -0,0 +1,11 @@ +# +# Input packet +# +Packet-Type = Access-Request +User-Name = "bob" +User-Password = "olobobob" + +# +# Expected answer +# +Packet-Type == Access-Accept diff --git a/src/tests/modules/cache_rbtree/cache-method-bin.unlang b/src/tests/modules/cache_rbtree/cache-method-bin.unlang new file mode 100644 index 00000000000..3ffc913ee66 --- /dev/null +++ b/src/tests/modules/cache_rbtree/cache-method-bin.unlang @@ -0,0 +1,160 @@ +# +# PRE: cache-logic +# + +# +# Series of tests to check for binary safe operation of the cache module +# both keys and values should be binary safe. +# +update { + &Tmp-Octets-0 := 0xaa00bb00cc00dd00 + &Tmp-String-1 := "foo\000bar\000baz" +} + +# 0. Sanity check +if (&Tmp-String-1 != "foo\000bar\000baz") { + test_fail +} + +# 1. Store the entry +cache_bin_key_octets.store +if (!updated) { + test_fail +} + +# Now add a second entry, with the value diverging after the first null byte +update { + &Tmp-Octets-0 := 0xaa00bb00cc00ee00 + &Tmp-String-1 := "bar\000baz" +} + +# 2. Should create a *new* entry and not update the existing one +cache_bin_key_octets.store +if (!updated) { + test_fail +} + +update { + &Tmp-String-1 !* ANY +} + +# If the key is binary safe, we should now be able to retrieve the first entry +# if it's not, the above test will likely fail, or we'll get the second entry. +update { + &Tmp-Octets-0 := 0xaa00bb00cc00dd00 +} + +cache_bin_key_octets.load +if (!updated) { + test_fail +} + +if ("%(length:%{Tmp-String-1})" != 11) { + test_fail +} + +if (&Tmp-String-1 != "foo\000bar\000baz") { + test_fail +} + +update { + &Tmp-String-1 !* ANY +} + +# Now try and get the second entry +update { + &Tmp-Octets-0 := 0xaa00bb00cc00ee00 +} + +cache_bin_key_octets.load +if (!updated) { + test_fail +} + +if ("%(length:%{Tmp-String-1})" != 7) { + test_fail +} + +if (&Tmp-String-1 != "bar\000baz") { + test_fail +} + +update { + &Tmp-String-1 !* ANY +} + +# +# We should also be able to use any fixed length data type as a key +# though there are no guarantees this will be portable. +# +update { + &Tmp-IP-Address-0 := 192.168.0.1 + &Tmp-String-1 := "foo\000bar\000baz" +} + +cache_bin_key_ipaddr.store +if (!updated) { + test_fail +} + + +# Now add a second entry +update { + &Tmp-IP-Address-0:= 192.168.0.2 + &Tmp-String-1 := "bar\000baz" +} + +cache_bin_key_ipaddr.store +if (!updated) { + test_fail +} + +update { + &Tmp-String-1 !* ANY +} + +# Now retrieve the first entry +update { + &Tmp-IP-Address-0 := 192.168.0.1 +} + +cache_bin_key_ipaddr.load +if (!updated) { + test_fail +} + +if ("%(length:%{Tmp-String-1})" != 11) { + test_fail +} + +if (&Tmp-String-1 != "foo\000bar\000baz") { + test_fail +} + +update { + &Tmp-String-1 !* ANY +} + +# Now try and get the second entry +update { + &Tmp-IP-Address-0 := 192.168.0.2 +} + +cache_bin_key_ipaddr.load +if (!updated) { + test_fail +} + +if ("%(length:%{Tmp-String-1})" != 7) { + test_fail +} + +if (&Tmp-String-1 != "bar\000baz") { + test_fail +} + +update { + &Tmp-String-1 !* ANY +} + +test_pass diff --git a/src/tests/modules/cache_rbtree/cache-method-logic.attrs b/src/tests/modules/cache_rbtree/cache-method-logic.attrs new file mode 100644 index 00000000000..2c3f3ac57f0 --- /dev/null +++ b/src/tests/modules/cache_rbtree/cache-method-logic.attrs @@ -0,0 +1,11 @@ +# +# Input packet +# +Packet-Type = Access-Request +User-Name = "bob" +User-Password = "olobobob" + +# +# Expected answer +# +Packet-Type == Access-Accept diff --git a/src/tests/modules/cache_rbtree/cache-method-logic.unlang b/src/tests/modules/cache_rbtree/cache-method-logic.unlang new file mode 100644 index 00000000000..d95ff75fcee --- /dev/null +++ b/src/tests/modules/cache_rbtree/cache-method-logic.unlang @@ -0,0 +1,163 @@ +# +# PRE: +# +update { + &request.Tmp-String-0 := 'testkey' +} + +# +# 0. Basic store and retrieve +# +update control { + &control.Tmp-String-1 := 'cache me' +} + +cache.store +if (!updated) { + test_fail +} + +# 1. Check the module didn't perform a merge +if (&request.Tmp-String-1) { + test_fail +} + +# 2. Check status-only works correctly (should return ok and consume attribute) +cache.status +if (!ok) { + test_fail +} + +# 3. Retrieve the entry (should be copied to request list) +cache.load +if (!updated) { + test_fail +} + +# 4. +if (&request.Tmp-String-1 != &control.Tmp-String-1) { + test_fail +} + +# 5. Retrieving the entry should not expire it +update request { + &Tmp-String-1 !* ANY +} + +cache.load +if (!updated) { + test_fail +} + +# 6. +if (&request.Tmp-String-1 != &control.Tmp-String-1) { + test_fail +} + +# 8. Remove the entry +cache.clear +if (!ok) { + test_fail +} + +# 8. Check status-only works correctly (should return notfound and consume attribute) +cache.status +if (!notfound) { + test_fail +} + +# 14. This should still allow the creation of a new entry +update control { + &Cache-TTL := -1 +} +cache.store +if (!updated) { + test_fail +} + +# 12. We have nothing to do if it is ready added. +cache.store +if (!updated) { + test_fail +} + +# 13. +if (&Cache-TTL) { + test_fail +} + +# 14. +if (&request.Tmp-String-1 != &control.Tmp-String-1) { + test_fail +} + +update control { + &Tmp-String-1 := 'cache me2' +} + +# 18. Updating the Cache-TTL shouldn't make things go boom (we can't really check if it works) +update control { + &Cache-TTL := 666 +} +cache.ttl +if (!updated) { + test_fail +} + +# 19. Request Tmp-String-1 shouldn't have been updated yet +if (&request.Tmp-String-1 == &control.Tmp-String-1) { + test_fail +} + +# 20. Check that a new entry is created +update control { + &Cache-TTL := -1 +} +cache.store +if (!updated) { + test_fail +} + +# 21. Request Tmp-String-1 still shouldn't have been updated yet +if (&request.Tmp-String-1 == &control.Tmp-String-1) { + test_fail +} + +# 22. +cache.load +if (!updated) { + test_fail +} + +# 23. Request Tmp-String-1 should now have been updated +if (&request.Tmp-String-1 != &control.Tmp-String-1) { + test_fail +} + +# 24. Check Cache-Merge = yes works as expected (should update current request) +update control { + &Tmp-String-1 := 'cache me3' + &Cache-TTL := -1 + &Cache-Merge-New := yes +} +cache.store +if (!updated) { + test_fail +} + +# 25. Request Tmp-String-1 should now have been updated +if (&request.Tmp-String-1 != &control.Tmp-String-1) { + test_fail +} + +# 26. Check Cache-Entry-Hits is updated as we expect +if (&request.Cache-Entry-Hits != 0) { + test_fail +} + +cache.load +if (&request.Cache-Entry-Hits != 1) { + test_fail +} + +test_pass diff --git a/src/tests/modules/cache_rbtree/cache-method-update.attrs b/src/tests/modules/cache_rbtree/cache-method-update.attrs new file mode 100644 index 00000000000..2c3f3ac57f0 --- /dev/null +++ b/src/tests/modules/cache_rbtree/cache-method-update.attrs @@ -0,0 +1,11 @@ +# +# Input packet +# +Packet-Type = Access-Request +User-Name = "bob" +User-Password = "olobobob" + +# +# Expected answer +# +Packet-Type == Access-Accept diff --git a/src/tests/modules/cache_rbtree/cache-method-update.unlang b/src/tests/modules/cache_rbtree/cache-method-update.unlang new file mode 100644 index 00000000000..57f8d315502 --- /dev/null +++ b/src/tests/modules/cache_rbtree/cache-method-update.unlang @@ -0,0 +1,83 @@ +# +# PRE: cache-logic +# +update { + &request.Tmp-String-0 := 'testkey' + + # Reply attributes + &reply.Reply-Message := 'hello' + &reply.Reply-Message += 'goodbye' + + # Request attributes + &Tmp-Integer-0 += 10 + &Tmp-Integer-0 += 20 + &Tmp-Integer-0 += 30 +} + +# +# Basic store and retrieve +# +update control { + &control.Tmp-String-1 := 'cache me' +} + +cache_update.store +if (!updated) { + test_fail +} + +# Merge +cache_update.store +if (!updated) { + test_fail +} + +# Load +cache_update.load +if (!updated) { + test_fail +} + +# session-state should now contain all the reply attributes +if ("%{session-state[#]}" != 2) { + test_fail +} + +if (&session-state.Reply-Message[0] != 'hello') { + test_fail +} + +if (&session-state.Reply-Message[1] != 'goodbye') { + test_fail +} + +# Tmp-String-1 should hold the result of the exec +if (&Tmp-String-1 != 'echo test') { + test_pass +} + +# Literal values should be foo, rad, baz +if ("%{Tmp-String-2[#]}" != 3) { + test_fail +} + +if (&Tmp-String-2[0] != 'foo') { + test_fail +} + +debug_request + +if (&Tmp-String-2[1] != 'rab') { + test_fail +} + +if (&Tmp-String-2[2] != 'baz') { + test_fail +} + +# Clear out the reply list +update { + &reply !* ANY +} + +test_pass