]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add methods cache.{store,load,status,clear,ttl} for rlm_cache (#3013)
authorJorge Pereira <jpereira@users.noreply.github.com>
Thu, 21 Oct 2021 14:17:55 +0000 (11:17 -0300)
committerGitHub <noreply@github.com>
Thu, 21 Oct 2021 14:17:55 +0000 (10:17 -0400)
* 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.

raddb/mods-available/cache
src/modules/rlm_cache/rlm_cache.c
src/tests/modules/cache_rbtree/cache-bin.unlang
src/tests/modules/cache_rbtree/cache-method-bin.attrs [new file with mode: 0644]
src/tests/modules/cache_rbtree/cache-method-bin.unlang [new file with mode: 0644]
src/tests/modules/cache_rbtree/cache-method-logic.attrs [new file with mode: 0644]
src/tests/modules/cache_rbtree/cache-method-logic.unlang [new file with mode: 0644]
src/tests/modules/cache_rbtree/cache-method-update.attrs [new file with mode: 0644]
src/tests/modules/cache_rbtree/cache-method-update.unlang [new file with mode: 0644]

index 1e3e5a279ebdab61bf9e3ab49c642334867e03c5..19f40199be205c5565a79e6742fdcdcab8628d2f 100644 (file)
@@ -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.
+       #  ====
+       #
 }
index e9d1d28a74f07539e1114d8f108c5c8d94bfd426..5d3f680c04abfd7e41022ace4e0fa4ca23361b65 100644 (file)
@@ -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
+       }
 };
index d4a407a5fe910b3b12b351fb57b89689da12a6c8..1d7a64fa8331d030dd4559e50593ca113bb3500b 100644 (file)
@@ -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 (file)
index 0000000..2c3f3ac
--- /dev/null
@@ -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 (file)
index 0000000..3ffc913
--- /dev/null
@@ -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 (file)
index 0000000..2c3f3ac
--- /dev/null
@@ -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 (file)
index 0000000..d95ff75
--- /dev/null
@@ -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 (file)
index 0000000..2c3f3ac
--- /dev/null
@@ -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 (file)
index 0000000..57f8d31
--- /dev/null
@@ -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