Merge "res_sorcery_memory_cache: Add CLI commands and AMI actions."

This commit is contained in:
Mark Michelson 2015-06-01 13:04:10 -05:00 committed by Gerrit Code Review
commit 3906175426
1 changed files with 649 additions and 0 deletions

View File

@ -38,6 +38,73 @@ ASTERISK_REGISTER_FILE()
#include "asterisk/sched.h"
#include "asterisk/test.h"
#include "asterisk/heap.h"
#include "asterisk/cli.h"
#include "asterisk/manager.h"
/*** DOCUMENTATION
<manager name="SorceryMemoryCacheExpireObject" language="en_US">
<synopsis>
Expire (remove) an object from a sorcery memory cache.
</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
<parameter name="Cache" required="true">
<para>The name of the cache to expire the object from.</para>
</parameter>
<parameter name="Object" required="true">
<para>The name of the object to expire.</para>
</parameter>
</syntax>
<description>
<para>Expires (removes) an object from a sorcery memory cache.</para>
</description>
</manager>
<manager name="SorceryMemoryCacheExpire" language="en_US">
<synopsis>
Expire (remove) ALL objects from a sorcery memory cache.
</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
<parameter name="Cache" required="true">
<para>The name of the cache to expire all objects from.</para>
</parameter>
</syntax>
<description>
<para>Expires (removes) ALL objects from a sorcery memory cache.</para>
</description>
</manager>
<manager name="SorceryMemoryCacheStaleObject" language="en_US">
<synopsis>
Mark an object in a sorcery memory cache as stale.
</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
<parameter name="Cache" required="true">
<para>The name of the cache to mark the object as stale in.</para>
</parameter>
<parameter name="Object" required="true">
<para>The name of the object to mark as stale.</para>
</parameter>
</syntax>
<description>
<para>Marks an object as stale within a sorcery memory cache.</para>
</description>
</manager>
<manager name="SorceryMemoryCacheStale" language="en_US">
<synopsis>
Marks ALL objects in a sorcery memory cache as stale.
</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
<parameter name="Cache" required="true">
<para>The name of the cache to mark all object as stale in.</para>
</parameter>
</syntax>
<description>
<para>Marks ALL objects in a sorcery memory cache as stale.</para>
</description>
</manager>
***/
/*! \brief Structure for storing a memory cache */
struct sorcery_memory_cache {
@ -403,6 +470,94 @@ static int expire_objects_from_cache(const void *data)
return 0;
}
/*!
* \internal
* \brief Remove all objects from the cache.
*
* This removes ALL objects from both the hash table and heap.
*
* \pre cache->objects is write-locked
*
* \param cache The cache to empty.
*/
static void remove_all_from_cache(struct sorcery_memory_cache *cache)
{
while (ast_heap_pop(cache->object_heap));
ao2_callback(cache->objects, OBJ_UNLINK | OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE,
NULL, NULL);
AST_SCHED_DEL_UNREF(sched, cache->expire_id, ao2_ref(cache, -1));
}
/*!
* \internal
* \brief AO2 callback function for making an object stale immediately
*
* This changes the creation time of an object so it appears as though it is stale immediately.
*
* \param obj The cached object
* \param arg The cache itself
* \param flags Unused flags
*/
static int object_stale_callback(void *obj, void *arg, int flags)
{
struct sorcery_memory_cached_object *cached = obj;
struct sorcery_memory_cache *cache = arg;
/* Since our granularity is seconds it's possible for something to retrieve us within a window
* where we wouldn't be treated as stale. To ensure that doesn't happen we use the configured stale
* time plus a second.
*/
cached->created = ast_tvsub(cached->created, ast_samp2tv(cache->object_lifetime_stale + 1, 1));
return CMP_MATCH;
}
/*!
* \internal
* \brief Mark an object as stale explicitly.
*
* This changes the creation time of an object so it appears as though it is stale immediately.
*
* \pre cache->objects is read-locked
*
* \param cache The cache the object is in
* \param id The unique identifier of the object
*
* \retval 0 success
* \retval -1 failure
*/
static int mark_object_as_stale_in_cache(struct sorcery_memory_cache *cache, const char *id)
{
struct sorcery_memory_cached_object *cached;
cached = ao2_find(cache->objects, id, OBJ_SEARCH_KEY | OBJ_NOLOCK);
if (!cached) {
return -1;
}
object_stale_callback(cached, cache, 0);
ao2_ref(cached, -1);
return 0;
}
/*!
* \internal
* \brief Mark all objects as stale within a cache.
*
* This changes the creation time of ALL objects so they appear as though they are stale.
*
* \pre cache->objects is read-locked
*
* \param cache
*/
static void mark_all_as_stale_in_cache(struct sorcery_memory_cache *cache)
{
ao2_callback(cache->objects, OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE, object_stale_callback, cache);
}
/*!
* \internal
* \brief Schedule a callback for cached object expiration.
@ -900,6 +1055,480 @@ static void sorcery_memory_cache_close(void *data)
ao2_ref(cache, -1);
}
/*!
* \internal
* \brief CLI tab completion for cache names
*/
static char *sorcery_memory_cache_complete_name(const char *word, int state)
{
struct sorcery_memory_cache *cache;
struct ao2_iterator it_caches;
int wordlen = strlen(word);
int which = 0;
char *result = NULL;
it_caches = ao2_iterator_init(caches, 0);
while ((cache = ao2_iterator_next(&it_caches))) {
if (!strncasecmp(word, cache->name, wordlen)
&& ++which > state) {
result = ast_strdup(cache->name);
}
ao2_ref(cache, -1);
if (result) {
break;
}
}
ao2_iterator_destroy(&it_caches);
return result;
}
/*!
* \internal
* \brief CLI command implementation for 'sorcery memory cache show'
*/
static char *sorcery_memory_cache_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct sorcery_memory_cache *cache;
switch (cmd) {
case CLI_INIT:
e->command = "sorcery memory cache show";
e->usage =
"Usage: sorcery memory cache show <name>\n"
" Show sorcery memory cache configuration and statistics.\n";
return NULL;
case CLI_GENERATE:
if (a->pos == 4) {
return sorcery_memory_cache_complete_name(a->word, a->n);
} else {
return NULL;
}
}
if (a->argc != 5) {
return CLI_SHOWUSAGE;
}
cache = ao2_find(caches, a->argv[4], OBJ_SEARCH_KEY);
if (!cache) {
ast_cli(a->fd, "Specified sorcery memory cache '%s' does not exist\n", a->argv[4]);
return CLI_FAILURE;
}
ast_cli(a->fd, "Sorcery memory cache: %s\n", cache->name);
ast_cli(a->fd, "Number of objects within cache: %d\n", ao2_container_count(cache->objects));
if (cache->maximum_objects) {
ast_cli(a->fd, "Maximum allowed objects: %d\n", cache->maximum_objects);
} else {
ast_cli(a->fd, "There is no limit on the maximum number of objects in the cache\n");
}
if (cache->object_lifetime_maximum) {
ast_cli(a->fd, "Number of seconds before object expires: %d\n", cache->object_lifetime_maximum);
} else {
ast_cli(a->fd, "Object expiration is not enabled - cached objects will not expire\n");
}
if (cache->object_lifetime_stale) {
ast_cli(a->fd, "Number of seconds before object becomes stale: %d\n", cache->object_lifetime_stale);
} else {
ast_cli(a->fd, "Object staleness is not enabled - cached objects will not go stale\n");
}
ast_cli(a->fd, "Prefetch: %s\n", AST_CLI_ONOFF(cache->prefetch));
ast_cli(a->fd, "Expire all objects on reload: %s\n", AST_CLI_ONOFF(cache->expire_on_reload));
ao2_ref(cache, -1);
return CLI_SUCCESS;
}
/*! \brief Structure used to pass data for printing cached object information */
struct print_object_details {
/*! \brief The sorcery memory cache */
struct sorcery_memory_cache *cache;
/*! \brief The CLI arguments */
struct ast_cli_args *a;
};
/*!
* \internal
* \brief Callback function for displaying object within the cache
*/
static int sorcery_memory_cache_print_object(void *obj, void *arg, int flags)
{
#define FORMAT "%-25.25s %-15u %-15u \n"
struct sorcery_memory_cached_object *cached = obj;
struct print_object_details *details = arg;
int seconds_until_expire = 0, seconds_until_stale = 0;
if (details->cache->object_lifetime_maximum) {
seconds_until_expire = ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(details->cache->object_lifetime_maximum, 1)), ast_tvnow()) / 1000;
}
if (details->cache->object_lifetime_stale) {
seconds_until_stale = ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(details->cache->object_lifetime_stale, 1)), ast_tvnow()) / 1000;
}
ast_cli(details->a->fd, FORMAT, ast_sorcery_object_get_id(cached->object), MAX(seconds_until_stale, 0), MAX(seconds_until_expire, 0));
return CMP_MATCH;
#undef FORMAT
}
/*!
* \internal
* \brief CLI command implementation for 'sorcery memory cache dump'
*/
static char *sorcery_memory_cache_dump(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
#define FORMAT "%-25.25s %-15.15s %-15.15s \n"
struct sorcery_memory_cache *cache;
struct print_object_details details;
switch (cmd) {
case CLI_INIT:
e->command = "sorcery memory cache dump";
e->usage =
"Usage: sorcery memory cache dump <name>\n"
" Dump a list of the objects within the cache, listed by object identifier.\n";
return NULL;
case CLI_GENERATE:
if (a->pos == 4) {
return sorcery_memory_cache_complete_name(a->word, a->n);
} else {
return NULL;
}
}
if (a->argc != 5) {
return CLI_SHOWUSAGE;
}
cache = ao2_find(caches, a->argv[4], OBJ_SEARCH_KEY);
if (!cache) {
ast_cli(a->fd, "Specified sorcery memory cache '%s' does not exist\n", a->argv[4]);
return CLI_FAILURE;
}
details.cache = cache;
details.a = a;
ast_cli(a->fd, "Dumping sorcery memory cache '%s':\n", cache->name);
if (!cache->object_lifetime_stale) {
ast_cli(a->fd, " * Staleness is not enabled - objects will not go stale\n");
}
if (!cache->object_lifetime_maximum) {
ast_cli(a->fd, " * Object lifetime is not enabled - objects will not expire\n");
}
ast_cli(a->fd, FORMAT, "Object Name", "Stale In", "Expires In");
ast_cli(a->fd, FORMAT, "-------------------------", "---------------", "---------------");
ao2_callback(cache->objects, OBJ_NODATA | OBJ_MULTIPLE, sorcery_memory_cache_print_object, &details);
ast_cli(a->fd, FORMAT, "-------------------------", "---------------", "---------------");
ast_cli(a->fd, "Total number of objects cached: %d\n", ao2_container_count(cache->objects));
ao2_ref(cache, -1);
return CLI_SUCCESS;
#undef FORMAT
}
/*!
* \internal
* \brief CLI tab completion for cached object names
*/
static char *sorcery_memory_cache_complete_object_name(const char *cache_name, const char *word, int state)
{
struct sorcery_memory_cache *cache;
struct sorcery_memory_cached_object *cached;
struct ao2_iterator it_cached;
int wordlen = strlen(word);
int which = 0;
char *result = NULL;
cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
if (!cache) {
return NULL;
}
it_cached = ao2_iterator_init(cache->objects, 0);
while ((cached = ao2_iterator_next(&it_cached))) {
if (!strncasecmp(word, ast_sorcery_object_get_id(cached->object), wordlen)
&& ++which > state) {
result = ast_strdup(ast_sorcery_object_get_id(cached->object));
}
ao2_ref(cached, -1);
if (result) {
break;
}
}
ao2_iterator_destroy(&it_cached);
ao2_ref(cache, -1);
return result;
}
/*!
* \internal
* \brief CLI command implementation for 'sorcery memory cache expire'
*/
static char *sorcery_memory_cache_expire(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct sorcery_memory_cache *cache;
switch (cmd) {
case CLI_INIT:
e->command = "sorcery memory cache expire";
e->usage =
"Usage: sorcery memory cache expire <cache name> [object name]\n"
" Expire a specific object or ALL objects within a sorcery memory cache.\n";
return NULL;
case CLI_GENERATE:
if (a->pos == 4) {
return sorcery_memory_cache_complete_name(a->word, a->n);
} else if (a->pos == 5) {
return sorcery_memory_cache_complete_object_name(a->argv[4], a->word, a->n);
} else {
return NULL;
}
}
if (a->argc > 6) {
return CLI_SHOWUSAGE;
}
cache = ao2_find(caches, a->argv[4], OBJ_SEARCH_KEY);
if (!cache) {
ast_cli(a->fd, "Specified sorcery memory cache '%s' does not exist\n", a->argv[4]);
return CLI_FAILURE;
}
ao2_wrlock(cache->objects);
if (a->argc == 5) {
remove_all_from_cache(cache);
ast_cli(a->fd, "All objects have been removed from cache '%s'\n", a->argv[4]);
} else {
if (!remove_from_cache(cache, a->argv[5], 1)) {
ast_cli(a->fd, "Successfully expired object '%s' from cache '%s'\n", a->argv[5], a->argv[4]);
} else {
ast_cli(a->fd, "Object '%s' was not expired from cache '%s' as it was not found\n", a->argv[5],
a->argv[4]);
}
}
ao2_unlock(cache->objects);
ao2_ref(cache, -1);
return CLI_SUCCESS;
}
/*!
* \internal
* \brief CLI command implementation for 'sorcery memory cache stale'
*/
static char *sorcery_memory_cache_stale(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct sorcery_memory_cache *cache;
switch (cmd) {
case CLI_INIT:
e->command = "sorcery memory cache stale";
e->usage =
"Usage: sorcery memory cache stale <cache name> [object name]\n"
" Mark a specific object or ALL objects as stale in a sorcery memory cache.\n";
return NULL;
case CLI_GENERATE:
if (a->pos == 4) {
return sorcery_memory_cache_complete_name(a->word, a->n);
} else if (a->pos == 5) {
return sorcery_memory_cache_complete_object_name(a->argv[4], a->word, a->n);
} else {
return NULL;
}
}
if (a->argc > 6) {
return CLI_SHOWUSAGE;
}
cache = ao2_find(caches, a->argv[4], OBJ_SEARCH_KEY);
if (!cache) {
ast_cli(a->fd, "Specified sorcery memory cache '%s' does not exist\n", a->argv[4]);
return CLI_FAILURE;
}
if (!cache->object_lifetime_stale) {
ast_cli(a->fd, "Specified sorcery memory cache '%s' does not have staleness enabled\n", a->argv[4]);
ao2_ref(cache, -1);
return CLI_FAILURE;
}
ao2_rdlock(cache->objects);
if (a->argc == 5) {
mark_all_as_stale_in_cache(cache);
ast_cli(a->fd, "Marked all objects in sorcery memory cache '%s' as stale\n", a->argv[4]);
} else {
if (!mark_object_as_stale_in_cache(cache, a->argv[5])) {
ast_cli(a->fd, "Successfully marked object '%s' in memory cache '%s' as stale\n",
a->argv[5], a->argv[4]);
} else {
ast_cli(a->fd, "Object '%s' in sorcery memory cache '%s' could not be marked as stale as it was not found\n",
a->argv[5], a->argv[4]);
}
}
ao2_unlock(cache->objects);
ao2_ref(cache, -1);
return CLI_SUCCESS;
}
static struct ast_cli_entry cli_memory_cache[] = {
AST_CLI_DEFINE(sorcery_memory_cache_show, "Show sorcery memory cache information"),
AST_CLI_DEFINE(sorcery_memory_cache_dump, "Dump all objects within a sorcery memory cache"),
AST_CLI_DEFINE(sorcery_memory_cache_expire, "Expire a specific object or ALL objects within a sorcery memory cache"),
AST_CLI_DEFINE(sorcery_memory_cache_stale, "Mark a specific object or ALL objects as stale within a sorcery memory cache"),
};
/*!
* \internal
* \brief AMI command implementation for 'SorceryMemoryCacheExpireObject'
*/
static int sorcery_memory_cache_ami_expire_object(struct mansession *s, const struct message *m)
{
const char *cache_name = astman_get_header(m, "Cache");
const char *object_name = astman_get_header(m, "Object");
struct sorcery_memory_cache *cache;
int res;
if (ast_strlen_zero(cache_name)) {
astman_send_error(s, m, "SorceryMemoryCacheExpireObject requires that a cache name be provided.\n");
return 0;
} else if (ast_strlen_zero(object_name)) {
astman_send_error(s, m, "SorceryMemoryCacheExpireObject requires that an object name be provided\n");
return 0;
}
cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
if (!cache) {
astman_send_error(s, m, "The provided cache does not exist\n");
return 0;
}
ao2_wrlock(cache->objects);
res = remove_from_cache(cache, object_name, 1);
ao2_unlock(cache->objects);
ao2_ref(cache, -1);
if (!res) {
astman_send_ack(s, m, "The provided object was expired from the cache\n");
} else {
astman_send_error(s, m, "The provided object could not be expired from the cache\n");
}
return 0;
}
/*!
* \internal
* \brief AMI command implementation for 'SorceryMemoryCacheExpire'
*/
static int sorcery_memory_cache_ami_expire(struct mansession *s, const struct message *m)
{
const char *cache_name = astman_get_header(m, "Cache");
struct sorcery_memory_cache *cache;
if (ast_strlen_zero(cache_name)) {
astman_send_error(s, m, "SorceryMemoryCacheExpire requires that a cache name be provided.\n");
return 0;
}
cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
if (!cache) {
astman_send_error(s, m, "The provided cache does not exist\n");
return 0;
}
ao2_wrlock(cache->objects);
remove_all_from_cache(cache);
ao2_unlock(cache->objects);
ao2_ref(cache, -1);
astman_send_ack(s, m, "All objects were expired from the cache\n");
return 0;
}
/*!
* \internal
* \brief AMI command implementation for 'SorceryMemoryCacheStaleObject'
*/
static int sorcery_memory_cache_ami_stale_object(struct mansession *s, const struct message *m)
{
const char *cache_name = astman_get_header(m, "Cache");
const char *object_name = astman_get_header(m, "Object");
struct sorcery_memory_cache *cache;
int res;
if (ast_strlen_zero(cache_name)) {
astman_send_error(s, m, "SorceryMemoryCacheStaleObject requires that a cache name be provided.\n");
return 0;
} else if (ast_strlen_zero(object_name)) {
astman_send_error(s, m, "SorceryMemoryCacheStaleObject requires that an object name be provided\n");
return 0;
}
cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
if (!cache) {
astman_send_error(s, m, "The provided cache does not exist\n");
return 0;
}
ao2_rdlock(cache->objects);
res = mark_object_as_stale_in_cache(cache, object_name);
ao2_unlock(cache->objects);
ao2_ref(cache, -1);
if (!res) {
astman_send_ack(s, m, "The provided object was marked as stale in the cache\n");
} else {
astman_send_error(s, m, "The provided object could not be marked as stale in the cache\n");
}
return 0;
}
/*!
* \internal
* \brief AMI command implementation for 'SorceryMemoryCacheStale'
*/
static int sorcery_memory_cache_ami_stale(struct mansession *s, const struct message *m)
{
const char *cache_name = astman_get_header(m, "Cache");
struct sorcery_memory_cache *cache;
if (ast_strlen_zero(cache_name)) {
astman_send_error(s, m, "SorceryMemoryCacheStale requires that a cache name be provided.\n");
return 0;
}
cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
if (!cache) {
astman_send_error(s, m, "The provided cache does not exist\n");
return 0;
}
ao2_rdlock(cache->objects);
mark_all_as_stale_in_cache(cache);
ao2_unlock(cache->objects);
ao2_ref(cache, -1);
astman_send_ack(s, m, "All objects were marked as stale in the cache\n");
return 0;
}
#ifdef TEST_FRAMEWORK
/*! \brief Dummy sorcery object */
@ -1846,6 +2475,13 @@ static int unload_module(void)
ast_sorcery_wizard_unregister(&memory_cache_object_wizard);
ast_cli_unregister_multiple(cli_memory_cache, ARRAY_LEN(cli_memory_cache));
ast_manager_unregister("SorceryMemoryCacheExpireObject");
ast_manager_unregister("SorceryMemoryCacheExpire");
ast_manager_unregister("SorceryMemoryCacheStaleObject");
ast_manager_unregister("SorceryMemoryCacheStale");
AST_TEST_UNREGISTER(open_with_valid_options);
AST_TEST_UNREGISTER(open_with_invalid_options);
AST_TEST_UNREGISTER(create_and_retrieve);
@ -1860,6 +2496,8 @@ static int unload_module(void)
static int load_module(void)
{
int res;
sched = ast_sched_context_create();
if (!sched) {
ast_log(LOG_ERROR, "Failed to create scheduler for cache management\n");
@ -1886,6 +2524,17 @@ static int load_module(void)
return AST_MODULE_LOAD_DECLINE;
}
res = ast_cli_register_multiple(cli_memory_cache, ARRAY_LEN(cli_memory_cache));
res |= ast_manager_register_xml("SorceryMemoryCacheExpireObject", EVENT_FLAG_SYSTEM, sorcery_memory_cache_ami_expire_object);
res |= ast_manager_register_xml("SorceryMemoryCacheExpire", EVENT_FLAG_SYSTEM, sorcery_memory_cache_ami_expire);
res |= ast_manager_register_xml("SorceryMemoryCacheStaleObject", EVENT_FLAG_SYSTEM, sorcery_memory_cache_ami_stale_object);
res |= ast_manager_register_xml("SorceryMemoryCacheStale", EVENT_FLAG_SYSTEM, sorcery_memory_cache_ami_stale);
if (res) {
unload_module();
return AST_MODULE_LOAD_DECLINE;
}
AST_TEST_REGISTER(open_with_valid_options);
AST_TEST_REGISTER(open_with_invalid_options);
AST_TEST_REGISTER(create_and_retrieve);