1290bd95b30b5ac2595c4ab1c145827e510ff122
[asterisk/asterisk.git] / res / res_sorcery_memory_cache.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2015, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*!
20  * \file
21  *
22  * \brief Sorcery Memory Cache Object Wizard
23  *
24  * \author Joshua Colp <jcolp@digium.com>
25  */
26
27 /*** MODULEINFO
28         <support_level>core</support_level>
29  ***/
30
31 #include "asterisk.h"
32
33 ASTERISK_REGISTER_FILE()
34
35 #include "asterisk/module.h"
36 #include "asterisk/sorcery.h"
37 #include "asterisk/astobj2.h"
38 #include "asterisk/sched.h"
39 #include "asterisk/test.h"
40
41 /*! \brief Structure for storing a memory cache */
42 struct sorcery_memory_cache {
43         /*! \brief The name of the memory cache */
44         char *name;
45         /*! \brief Objects in the cache */
46         struct ao2_container *objects;
47         /*! \brief The maximum number of objects permitted in the cache, 0 if no limit */
48         unsigned int maximum_objects;
49         /*! \brief The maximum time (in seconds) an object will stay in the cache, 0 if no limit */
50         unsigned int object_lifetime_maximum;
51         /*! \brief The amount of time (in seconds) before an object is marked as stale, 0 if disabled */
52         unsigned int object_lifetime_stale;
53         /*! \brief Whether objects are prefetched from normal storage at load time, 0 if disabled */
54         unsigned int prefetch;
55         /** \brief Whether all objects are expired when the object type is reloaded, 0 if disabled */
56         unsigned int expire_on_reload;
57 };
58
59 /*! \brief Structure for stored a cached object */
60 struct sorcery_memory_cached_object {
61         /*! \brief The cached object */
62         void *object;
63         /*! \brief The time at which the object was created */
64         struct timeval created;
65 };
66
67 static void *sorcery_memory_cache_open(const char *data);
68 static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object);
69 static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type);
70 static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type);
71 static void *sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type,
72         const char *id);
73 static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object);
74 static void sorcery_memory_cache_close(void *data);
75
76 static struct ast_sorcery_wizard memory_cache_object_wizard = {
77         .name = "memory_cache",
78         .open = sorcery_memory_cache_open,
79         .create = sorcery_memory_cache_create,
80         .update = sorcery_memory_cache_create,
81         .delete = sorcery_memory_cache_delete,
82         .load = sorcery_memory_cache_load,
83         .reload = sorcery_memory_cache_reload,
84         .retrieve_id = sorcery_memory_cache_retrieve_id,
85         .close = sorcery_memory_cache_close,
86 };
87
88 /*! \brief The bucket size for the container of caches */
89 #define CACHES_CONTAINER_BUCKET_SIZE 53
90
91 /*! \brief The default bucket size for the container of objects in the cache */
92 #define CACHE_CONTAINER_BUCKET_SIZE 53
93
94 /*! \brief Container of created caches */
95 static struct ao2_container *caches;
96
97 /*! \brief Scheduler for cache management */
98 static struct ast_sched_context *sched;
99
100 /*!
101  * \internal
102  * \brief Hashing function for the container holding caches
103  *
104  * \param obj A sorcery memory cache or name of one
105  * \param flags Hashing flags
106  *
107  * \return The hash of the memory cache name
108  */
109 static int sorcery_memory_cache_hash(const void *obj, int flags)
110 {
111         const struct sorcery_memory_cache *cache = obj;
112         const char *name = obj;
113         int hash;
114
115         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
116         default:
117         case OBJ_SEARCH_OBJECT:
118                 name = cache->name;
119                 /* Fall through */
120         case OBJ_SEARCH_KEY:
121                 hash = ast_str_hash(name);
122                 break;
123         case OBJ_SEARCH_PARTIAL_KEY:
124                 /* Should never happen in hash callback. */
125                 ast_assert(0);
126                 hash = 0;
127                 break;
128         }
129         return hash;
130 }
131
132 /*!
133  * \internal
134  * \brief Comparison function for the container holding caches
135  *
136  * \param obj A sorcery memory cache
137  * \param arg A sorcery memory cache, or name of one
138  * \param flags Comparison flags
139  *
140  * \retval CMP_MATCH if the name is the same
141  * \retval 0 if the name does not match
142  */
143 static int sorcery_memory_cache_cmp(void *obj, void *arg, int flags)
144 {
145         const struct sorcery_memory_cache *left = obj;
146         const struct sorcery_memory_cache *right = arg;
147         const char *right_name = arg;
148         int cmp;
149
150         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
151         default:
152         case OBJ_SEARCH_OBJECT:
153                 right_name = right->name;
154                 /* Fall through */
155         case OBJ_SEARCH_KEY:
156                 cmp = strcmp(left->name, right_name);
157                 break;
158         case OBJ_SEARCH_PARTIAL_KEY:
159                 cmp = strncmp(left->name, right_name, strlen(right_name));
160                 break;
161         }
162         return cmp ? 0 : CMP_MATCH;
163 }
164
165 /*!
166  * \internal
167  * \brief Hashing function for the container holding cached objects
168  *
169  * \param obj A cached object or id of one
170  * \param flags Hashing flags
171  *
172  * \return The hash of the cached object id
173  */
174 static int sorcery_memory_cached_object_hash(const void *obj, int flags)
175 {
176         const struct sorcery_memory_cached_object *cached = obj;
177         const char *name = obj;
178         int hash;
179
180         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
181         default:
182         case OBJ_SEARCH_OBJECT:
183                 name = ast_sorcery_object_get_id(cached->object);
184                 /* Fall through */
185         case OBJ_SEARCH_KEY:
186                 hash = ast_str_hash(name);
187                 break;
188         case OBJ_SEARCH_PARTIAL_KEY:
189                 /* Should never happen in hash callback. */
190                 ast_assert(0);
191                 hash = 0;
192                 break;
193         }
194         return hash;
195 }
196
197 /*!
198  * \internal
199  * \brief Comparison function for the container holding cached objects
200  *
201  * \param obj A cached object
202  * \param arg A cached object, or id of one
203  * \param flags Comparison flags
204  *
205  * \retval CMP_MATCH if the id is the same
206  * \retval 0 if the id does not match
207  */
208 static int sorcery_memory_cached_object_cmp(void *obj, void *arg, int flags)
209 {
210         struct sorcery_memory_cached_object *left = obj;
211         struct sorcery_memory_cached_object *right = arg;
212         const char *right_name = arg;
213         int cmp;
214
215         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
216         default:
217         case OBJ_SEARCH_OBJECT:
218                 right_name = ast_sorcery_object_get_id(right->object);
219                 /* Fall through */
220         case OBJ_SEARCH_KEY:
221                 cmp = strcmp(ast_sorcery_object_get_id(left->object), right_name);
222                 break;
223         case OBJ_SEARCH_PARTIAL_KEY:
224                 cmp = strncmp(ast_sorcery_object_get_id(left->object), right_name, strlen(right_name));
225                 break;
226         }
227         return cmp ? 0 : CMP_MATCH;
228 }
229
230 /*!
231  * \internal
232  * \brief Destructor function for a sorcery memory cache
233  *
234  * \param obj A sorcery memory cache
235  */
236 static void sorcery_memory_cache_destructor(void *obj)
237 {
238         struct sorcery_memory_cache *cache = obj;
239
240         ast_free(cache->name);
241         ao2_cleanup(cache->objects);
242 }
243
244 /*!
245  * \internal
246  * \brief Destructor function for sorcery memory cached objects
247  *
248  * \param obj A sorcery memory cached object
249  */
250 static void sorcery_memory_cached_object_destructor(void *obj)
251 {
252         struct sorcery_memory_cached_object *cached = obj;
253
254         ao2_cleanup(cached->object);
255 }
256
257 /*!
258  * \internal
259  * \brief Callback function to cache an object in a memory cache
260  *
261  * \param sorcery The sorcery instance
262  * \param data The sorcery memory cache
263  * \param object The object to cache
264  *
265  * \retval 0 success
266  * \retval -1 failure
267  */
268 static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object)
269 {
270         struct sorcery_memory_cache *cache = data;
271         struct sorcery_memory_cached_object *cached;
272
273         cached = ao2_alloc_options(sizeof(*cached), sorcery_memory_cached_object_destructor,
274                 AO2_ALLOC_OPT_LOCK_NOLOCK);
275         if (!cached) {
276                 return -1;
277         }
278         cached->object = ao2_bump(object);
279         cached->created = ast_tvnow();
280
281         /* As there is no guarantee that this won't be called by multiple threads wanting to cache
282          * the same object we remove any old ones, which turns this into a create/update function
283          * in reality. As well since there's no guarantee that the object in the cache is the same
284          * one here we remove any old objects using the object identifier.
285          */
286
287         ao2_wrlock(cache->objects);
288         ao2_find(cache->objects, ast_sorcery_object_get_id(object), OBJ_SEARCH_KEY | OBJ_NODATA | OBJ_UNLINK | OBJ_NOLOCK);
289         ao2_link_flags(cache->objects, cached, OBJ_NOLOCK);
290         ao2_unlock(cache->objects);
291
292         ao2_ref(cached, -1);
293
294         return 0;
295 }
296
297 /*!
298  * \internal
299  * \brief Callback function to retrieve an object from a memory cache
300  *
301  * \param sorcery The sorcery instance
302  * \param data The sorcery memory cache
303  * \param type The type of the object to retrieve
304  * \param id The id of the object to retrieve
305  *
306  * \retval non-NULL success
307  * \retval NULL failure
308  */
309 static void *sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id)
310 {
311         struct sorcery_memory_cache *cache = data;
312         struct sorcery_memory_cached_object *cached;
313         void *object;
314
315         cached = ao2_find(cache->objects, id, OBJ_SEARCH_KEY);
316         if (!cached) {
317                 return NULL;
318         }
319
320         object = ao2_bump(cached->object);
321         ao2_ref(cached, -1);
322
323         return object;
324 }
325
326 /*!
327  * \internal
328  * \brief Callback function to finish configuring the memory cache and to prefetch objects
329  *
330  * \param data The sorcery memory cache
331  * \param sorcery The sorcery instance
332  * \param type The type of object being loaded
333  */
334 static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type)
335 {
336         struct sorcery_memory_cache *cache = data;
337
338         /* If no name was explicitly specified generate one given the sorcery instance and object type */
339         if (ast_strlen_zero(cache->name)) {
340                 ast_asprintf(&cache->name, "%s/%s", ast_sorcery_get_module(sorcery), type);
341         }
342
343         ao2_link(caches, cache);
344         ast_debug(1, "Memory cache '%s' associated with sorcery instance '%p' of module '%s' with object type '%s'\n",
345                 cache->name, sorcery, ast_sorcery_get_module(sorcery), type);
346 }
347
348 /*!
349  * \internal
350  * \brief Callback function to expire objects from the memory cache on reload (if configured)
351  *
352  * \param data The sorcery memory cache
353  * \param sorcery The sorcery instance
354  * \param type The type of object being reloaded
355  */
356 static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type)
357 {
358 }
359
360 /*!
361  * \internal
362  * \brief Function used to take an unsigned integer based configuration option and parse it
363  *
364  * \param value The string value of the configuration option
365  * \param result The unsigned integer to place the result in
366  *
367  * \retval 0 failure
368  * \retval 1 success
369  */
370 static int configuration_parse_unsigned_integer(const char *value, unsigned int *result)
371 {
372         if (ast_strlen_zero(value) || !strncmp(value, "-", 1)) {
373                 return 0;
374         }
375
376         return sscanf(value, "%30u", result);
377 }
378
379 /*!
380  * \internal
381  * \brief Callback function to create a new sorcery memory cache using provided configuration
382  *
383  * \param data A stringified configuration for the memory cache
384  *
385  * \retval non-NULL success
386  * \retval NULL failure
387  */
388 static void *sorcery_memory_cache_open(const char *data)
389 {
390         char *options = ast_strdup(data), *option;
391         RAII_VAR(struct sorcery_memory_cache *, cache, NULL, ao2_cleanup);
392
393         cache = ao2_alloc_options(sizeof(*cache), sorcery_memory_cache_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
394         if (!cache) {
395                 return NULL;
396         }
397
398         /* If no configuration options have been provided this memory cache will operate in a default
399          * configuration.
400          */
401         while (!ast_strlen_zero(options) && (option = strsep(&options, ","))) {
402                 char *name = strsep(&option, "="), *value = option;
403
404                 if (!strcasecmp(name, "name")) {
405                         if (ast_strlen_zero(value)) {
406                                 ast_log(LOG_ERROR, "A name must be specified for the memory cache\n");
407                                 return NULL;
408                         }
409                         ast_free(cache->name);
410                         cache->name = ast_strdup(value);
411                 } else if (!strcasecmp(name, "maximum_objects")) {
412                         if (configuration_parse_unsigned_integer(value, &cache->maximum_objects) != 1) {
413                                 ast_log(LOG_ERROR, "Unsupported maximum objects value of '%s' used for memory cache\n",
414                                         value);
415                                 return NULL;
416                         }
417                 } else if (!strcasecmp(name, "object_lifetime_maximum")) {
418                         if (configuration_parse_unsigned_integer(value, &cache->object_lifetime_maximum) != 1) {
419                                 ast_log(LOG_ERROR, "Unsupported object maximum lifetime value of '%s' used for memory cache\n",
420                                         value);
421                                 return NULL;
422                         }
423                 } else if (!strcasecmp(name, "object_lifetime_stale")) {
424                         if (configuration_parse_unsigned_integer(value, &cache->object_lifetime_stale) != 1) {
425                                 ast_log(LOG_ERROR, "Unsupported object stale lifetime value of '%s' used for memory cache\n",
426                                         value);
427                                 return NULL;
428                         }
429                 } else if (!strcasecmp(name, "prefetch")) {
430                         cache->prefetch = ast_true(value);
431                 } else if (!strcasecmp(name, "expire_on_reload")) {
432                         cache->expire_on_reload = ast_true(value);
433                 } else {
434                         ast_log(LOG_ERROR, "Unsupported option '%s' used for memory cache\n", name);
435                         return NULL;
436                 }
437         }
438
439         cache->objects = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK,
440                 cache->maximum_objects ? cache->maximum_objects : CACHE_CONTAINER_BUCKET_SIZE,
441                 sorcery_memory_cached_object_hash, sorcery_memory_cached_object_cmp);
442         if (!cache->objects) {
443                 ast_log(LOG_ERROR, "Could not create a container to hold cached objects for memory cache\n");
444                 return NULL;
445         }
446
447         /* The memory cache is not linked to the caches container until the load callback is invoked.
448          * Linking occurs there so an intelligent cache name can be constructed using the module of
449          * the sorcery instance and the specific object type if no cache name was specified as part
450          * of the configuration.
451          */
452
453         /* This is done as RAII_VAR will drop the reference */
454         return ao2_bump(cache);
455 }
456
457 /*!
458  * \internal
459  * \brief Callback function to delete an object from a memory cache
460  *
461  * \param sorcery The sorcery instance
462  * \param data The sorcery memory cache
463  * \param object The object to cache
464  *
465  * \retval 0 success
466  * \retval -1 failure
467  */
468 static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object)
469 {
470         struct sorcery_memory_cache *cache = data;
471
472         /* There is no guarantee that the object we have cached is the one we will be provided
473          * with in this callback function. As a result of this we remove the cached object based on
474          * the identifier and not the object itself.
475          */
476         ao2_find(cache->objects, ast_sorcery_object_get_id(object), OBJ_SEARCH_KEY | OBJ_NODATA | OBJ_UNLINK);
477
478         return 0;
479 }
480
481 /*!
482  * \internal
483  * \brief Callback function to terminate a memory cache
484  *
485  * \param data The sorcery memory cache
486  */
487 static void sorcery_memory_cache_close(void *data)
488 {
489         struct sorcery_memory_cache *cache = data;
490
491         /* This can occur if a cache is created but never loaded */
492         if (!ast_strlen_zero(cache->name)) {
493                 ao2_unlink(caches, cache);
494         }
495
496         ao2_ref(cache, -1);
497 }
498
499 #ifdef TEST_FRAMEWORK
500
501 /*! \brief Dummy sorcery object */
502 struct test_sorcery_object {
503         SORCERY_OBJECT(details);
504 };
505
506 /*!
507  * \internal
508  * \brief Allocator for test object
509  *
510  * \param id The identifier for the object
511  *
512  * \retval non-NULL success
513  * \retval NULL failure
514  */
515 static void *test_sorcery_object_alloc(const char *id)
516 {
517         return ast_sorcery_generic_alloc(sizeof(struct test_sorcery_object), NULL);
518 }
519
520 /*!
521  * \internal
522  * \brief Allocator for test sorcery instance
523  *
524  * \retval non-NULL success
525  * \retval NULL failure
526  */
527 static struct ast_sorcery *alloc_and_initialize_sorcery(void)
528 {
529         struct ast_sorcery *sorcery;
530
531         if (!(sorcery = ast_sorcery_open())) {
532                 return NULL;
533         }
534
535         if ((ast_sorcery_apply_default(sorcery, "test", "memory", NULL) != AST_SORCERY_APPLY_SUCCESS) ||
536                 ast_sorcery_internal_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) {
537                 ast_sorcery_unref(sorcery);
538                 return NULL;
539         }
540
541         return sorcery;
542 }
543
544 AST_TEST_DEFINE(open_with_valid_options)
545 {
546         int res = AST_TEST_PASS;
547         struct sorcery_memory_cache *cache;
548
549         switch (cmd) {
550         case TEST_INIT:
551                 info->name = "open_with_valid_options";
552                 info->category = "/res/res_sorcery_memory_cache/";
553                 info->summary = "Attempt to create sorcery memory caches using valid options";
554                 info->description = "This test performs the following:\n"
555                         "\t* Creates a memory cache with default configuration\n"
556                         "\t* Creates a memory cache with a maximum object count of 10 and verifies it\n"
557                         "\t* Creates a memory cache with a maximum object lifetime of 60 and verifies it\n"
558                         "\t* Creates a memory cache with a stale object lifetime of 90 and verifies it\n";
559                 return AST_TEST_NOT_RUN;
560         case TEST_EXECUTE:
561                 break;
562         }
563
564         cache = sorcery_memory_cache_open("");
565         if (!cache) {
566                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default configuration\n");
567                 res = AST_TEST_FAIL;
568         } else {
569                 sorcery_memory_cache_close(cache);
570         }
571
572         cache = sorcery_memory_cache_open("maximum_objects=10");
573         if (!cache) {
574                 ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object count of 10\n");
575                 res = AST_TEST_FAIL;
576         } else {
577                 if (cache->maximum_objects != 10) {
578                         ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of 10 but it has '%u'\n",
579                                 cache->maximum_objects);
580                 }
581                 sorcery_memory_cache_close(cache);
582         }
583
584         cache = sorcery_memory_cache_open("object_lifetime_maximum=60");
585         if (!cache) {
586                 ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object lifetime of 60\n");
587                 res = AST_TEST_FAIL;
588         } else {
589                 if (cache->object_lifetime_maximum != 60) {
590                         ast_test_status_update(test, "Created a sorcery memory cache with a maximum object lifetime of 60 but it has '%u'\n",
591                                 cache->object_lifetime_maximum);
592                 }
593                 sorcery_memory_cache_close(cache);
594         }
595
596         cache = sorcery_memory_cache_open("object_lifetime_stale=90");
597         if (!cache) {
598                 ast_test_status_update(test, "Failed to create a sorcery memory cache with a stale object lifetime of 90\n");
599                 res = AST_TEST_FAIL;
600         } else {
601                 if (cache->object_lifetime_stale != 90) {
602                         ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of 90 but it has '%u'\n",
603                                 cache->object_lifetime_stale);
604                 }
605                 sorcery_memory_cache_close(cache);
606         }
607
608
609         return res;
610 }
611
612 AST_TEST_DEFINE(open_with_invalid_options)
613 {
614         int res = AST_TEST_PASS;
615         struct sorcery_memory_cache *cache;
616
617         switch (cmd) {
618         case TEST_INIT:
619                 info->name = "open_with_invalid_options";
620                 info->category = "/res/res_sorcery_memory_cache/";
621                 info->summary = "Attempt to create sorcery memory caches using invalid options";
622                 info->description = "This test attempts to perform the following:\n"
623                         "\t* Create a memory cache with an empty name\n"
624                         "\t* Create a memory cache with a maximum object count of -1\n"
625                         "\t* Create a memory cache with a maximum object count of toast\n"
626                         "\t* Create a memory cache with a maximum object lifetime of -1\n"
627                         "\t* Create a memory cache with a maximum object lifetime of toast\n"
628                         "\t* Create a memory cache with a stale object lifetime of -1\n"
629                         "\t* Create a memory cache with a stale object lifetime of toast\n";
630                 return AST_TEST_NOT_RUN;
631         case TEST_EXECUTE:
632                 break;
633         }
634
635         cache = sorcery_memory_cache_open("name=");
636         if (cache) {
637                 ast_test_status_update(test, "Created a sorcery memory cache with an empty name\n");
638                 sorcery_memory_cache_close(cache);
639                 res = AST_TEST_FAIL;
640         }
641
642         cache = sorcery_memory_cache_open("maximum_objects=-1");
643         if (cache) {
644                 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of -1\n");
645                 sorcery_memory_cache_close(cache);
646                 res = AST_TEST_FAIL;
647         }
648
649         cache = sorcery_memory_cache_open("maximum_objects=toast");
650         if (cache) {
651                 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of toast\n");
652                 sorcery_memory_cache_close(cache);
653                 res = AST_TEST_FAIL;
654         }
655
656         cache = sorcery_memory_cache_open("object_lifetime_maximum=-1");
657         if (cache) {
658                 ast_test_status_update(test, "Created a sorcery memory cache with an object lifetime maximum of -1\n");
659                 sorcery_memory_cache_close(cache);
660                 res = AST_TEST_FAIL;
661         }
662
663         cache = sorcery_memory_cache_open("object_lifetime_maximum=toast");
664         if (cache) {
665                 ast_test_status_update(test, "Created a sorcery memory cache with an object lifetime maximum of toast\n");
666                 sorcery_memory_cache_close(cache);
667                 res = AST_TEST_FAIL;
668         }
669
670         cache = sorcery_memory_cache_open("object_lifetime_stale=-1");
671         if (cache) {
672                 ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of -1\n");
673                 sorcery_memory_cache_close(cache);
674                 res = AST_TEST_FAIL;
675         }
676
677         cache = sorcery_memory_cache_open("object_lifetime_stale=toast");
678         if (cache) {
679                 ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of toast\n");
680                 sorcery_memory_cache_close(cache);
681                 res = AST_TEST_FAIL;
682         }
683
684         cache = sorcery_memory_cache_open("tacos");
685         if (cache) {
686                 ast_test_status_update(test, "Created a sorcery memory cache with an invalid configuration option 'tacos'\n");
687                 sorcery_memory_cache_close(cache);
688                 res = AST_TEST_FAIL;
689         }
690
691         return res;
692 }
693
694 AST_TEST_DEFINE(create_and_retrieve)
695 {
696         int res = AST_TEST_FAIL;
697         struct ast_sorcery *sorcery = NULL;
698         struct sorcery_memory_cache *cache = NULL;
699         RAII_VAR(void *, object, NULL, ao2_cleanup);
700         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
701
702         switch (cmd) {
703         case TEST_INIT:
704                 info->name = "create";
705                 info->category = "/res/res_sorcery_memory_cache/";
706                 info->summary = "Attempt to create an object in the cache";
707                 info->description = "This test performs the following:\n"
708                         "\t* Creates a memory cache with default options\n"
709                         "\t* Creates a sorcery instance with a test object\n"
710                         "\t* Creates a test object with an id of test\n"
711                         "\t* Pushes the test object into the memory cache\n"
712                         "\t* Confirms that the test object is in the cache\n";
713                 return AST_TEST_NOT_RUN;
714         case TEST_EXECUTE:
715                 break;
716         }
717
718         cache = sorcery_memory_cache_open("");
719         if (!cache) {
720                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
721                 goto cleanup;
722         }
723
724         if (ao2_container_count(cache->objects)) {
725                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
726                 goto cleanup;
727         }
728
729         sorcery = alloc_and_initialize_sorcery();
730         if (!sorcery) {
731                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
732                 goto cleanup;
733         }
734
735         object = ast_sorcery_alloc(sorcery, "test", "test");
736         if (!object) {
737                 ast_test_status_update(test, "Failed to allocate a test object\n");
738                 goto cleanup;
739         }
740
741         sorcery_memory_cache_create(sorcery, cache, object);
742
743         if (!ao2_container_count(cache->objects)) {
744                 ast_test_status_update(test, "Added test object to memory cache but cache remains empty\n");
745                 goto cleanup;
746         }
747
748         cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
749         if (!cached_object) {
750                 ast_test_status_update(test, "Object placed into memory cache could not be retrieved\n");
751                 goto cleanup;
752         }
753
754         if (cached_object != object) {
755                 ast_test_status_update(test, "Object retrieved from memory cached is not the one we cached\n");
756                 goto cleanup;
757         }
758
759         res = AST_TEST_PASS;
760
761 cleanup:
762         if (cache) {
763                 sorcery_memory_cache_close(cache);
764         }
765         if (sorcery) {
766                 ast_sorcery_unref(sorcery);
767         }
768
769         return res;
770 }
771
772 AST_TEST_DEFINE(update)
773 {
774         int res = AST_TEST_FAIL;
775         struct ast_sorcery *sorcery = NULL;
776         struct sorcery_memory_cache *cache = NULL;
777         RAII_VAR(void *, original_object, NULL, ao2_cleanup);
778         RAII_VAR(void *, updated_object, NULL, ao2_cleanup);
779         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
780
781         switch (cmd) {
782         case TEST_INIT:
783                 info->name = "create";
784                 info->category = "/res/res_sorcery_memory_cache/";
785                 info->summary = "Attempt to create and then update an object in the cache";
786                 info->description = "This test performs the following:\n"
787                         "\t* Creates a memory cache with default options\n"
788                         "\t* Creates a sorcery instance with a test object\n"
789                         "\t* Creates a test object with an id of test\n"
790                         "\t* Pushes the test object into the memory cache\n"
791                         "\t* Confirms that the test object is in the cache\n"
792                         "\t* Creates a new test object with the same id of test\n"
793                         "\t* Pushes the new test object into the memory cache\n"
794                         "\t* Confirms that the new test object has replaced the old one\n";
795                 return AST_TEST_NOT_RUN;
796         case TEST_EXECUTE:
797                 break;
798         }
799
800         cache = sorcery_memory_cache_open("");
801         if (!cache) {
802                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
803                 goto cleanup;
804         }
805
806         if (ao2_container_count(cache->objects)) {
807                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
808                 goto cleanup;
809         }
810
811         sorcery = alloc_and_initialize_sorcery();
812         if (!sorcery) {
813                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
814                 goto cleanup;
815         }
816
817         original_object = ast_sorcery_alloc(sorcery, "test", "test");
818         if (!original_object) {
819                 ast_test_status_update(test, "Failed to allocate a test object\n");
820                 goto cleanup;
821         }
822
823         sorcery_memory_cache_create(sorcery, cache, original_object);
824
825         updated_object = ast_sorcery_alloc(sorcery, "test", "test");
826         if (!updated_object) {
827                 ast_test_status_update(test, "Failed to allocate an updated test object\n");
828                 goto cleanup;
829         }
830
831         sorcery_memory_cache_create(sorcery, cache, updated_object);
832
833         if (ao2_container_count(cache->objects) != 1) {
834                 ast_test_status_update(test, "Added updated test object to memory cache but cache now contains %d objects instead of 1\n",
835                         ao2_container_count(cache->objects));
836                 goto cleanup;
837         }
838
839         cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
840         if (!cached_object) {
841                 ast_test_status_update(test, "Updated object placed into memory cache could not be retrieved\n");
842                 goto cleanup;
843         }
844
845         if (cached_object == original_object) {
846                 ast_test_status_update(test, "Updated object placed into memory cache but old one is being retrieved\n");
847                 goto cleanup;
848         } else if (cached_object != updated_object) {
849                 ast_test_status_update(test, "Updated object placed into memory cache but different one is being retrieved\n");
850                 goto cleanup;
851         }
852
853         res = AST_TEST_PASS;
854
855 cleanup:
856         if (cache) {
857                 sorcery_memory_cache_close(cache);
858         }
859         if (sorcery) {
860                 ast_sorcery_unref(sorcery);
861         }
862
863         return res;
864 }
865
866 AST_TEST_DEFINE(delete)
867 {
868         int res = AST_TEST_FAIL;
869         struct ast_sorcery *sorcery = NULL;
870         struct sorcery_memory_cache *cache = NULL;
871         RAII_VAR(void *, object, NULL, ao2_cleanup);
872         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
873
874         switch (cmd) {
875         case TEST_INIT:
876                 info->name = "delete";
877                 info->category = "/res/res_sorcery_memory_cache/";
878                 info->summary = "Attempt to create and then delete an object in the cache";
879                 info->description = "This test performs the following:\n"
880                         "\t* Creates a memory cache with default options\n"
881                         "\t* Creates a sorcery instance with a test object\n"
882                         "\t* Creates a test object with an id of test\n"
883                         "\t* Pushes the test object into the memory cache\n"
884                         "\t* Confirms that the test object is in the cache\n"
885                         "\t* Deletes the test object from the cache\n"
886                         "\t* Confirms that the test object is no longer in the cache\n";
887                 return AST_TEST_NOT_RUN;
888         case TEST_EXECUTE:
889                 break;
890         }
891
892         cache = sorcery_memory_cache_open("");
893         if (!cache) {
894                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
895                 goto cleanup;
896         }
897
898         if (ao2_container_count(cache->objects)) {
899                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
900                 goto cleanup;
901         }
902
903         sorcery = alloc_and_initialize_sorcery();
904         if (!sorcery) {
905                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
906                 goto cleanup;
907         }
908
909         object = ast_sorcery_alloc(sorcery, "test", "test");
910         if (!object) {
911                 ast_test_status_update(test, "Failed to allocate a test object\n");
912                 goto cleanup;
913         }
914
915         sorcery_memory_cache_create(sorcery, cache, object);
916
917         if (!ao2_container_count(cache->objects)) {
918                 ast_test_status_update(test, "Added test object to memory cache but cache contains no objects\n");
919                 goto cleanup;
920         }
921
922         cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
923         if (!cached_object) {
924                 ast_test_status_update(test, "Test object placed into memory cache could not be retrieved\n");
925                 goto cleanup;
926         }
927
928         ao2_ref(cached_object, -1);
929         cached_object = NULL;
930
931         sorcery_memory_cache_delete(sorcery, cache, object);
932
933         cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
934         if (cached_object) {
935                 ast_test_status_update(test, "Test object deleted from memory cache can still be retrieved\n");
936                 goto cleanup;
937         }
938
939         res = AST_TEST_PASS;
940
941 cleanup:
942         if (cache) {
943                 sorcery_memory_cache_close(cache);
944         }
945         if (sorcery) {
946                 ast_sorcery_unref(sorcery);
947         }
948
949         return res;
950 }
951
952 #endif
953
954 static int unload_module(void)
955 {
956         if (sched) {
957                 ast_sched_context_destroy(sched);
958                 sched = NULL;
959         }
960
961         ao2_cleanup(caches);
962
963         ast_sorcery_wizard_unregister(&memory_cache_object_wizard);
964
965         AST_TEST_UNREGISTER(open_with_valid_options);
966         AST_TEST_UNREGISTER(open_with_invalid_options);
967         AST_TEST_UNREGISTER(create_and_retrieve);
968         AST_TEST_UNREGISTER(update);
969         AST_TEST_UNREGISTER(delete);
970
971         return 0;
972 }
973
974 static int load_module(void)
975 {
976         sched = ast_sched_context_create();
977         if (!sched) {
978                 ast_log(LOG_ERROR, "Failed to create scheduler for cache management\n");
979                 unload_module();
980                 return AST_MODULE_LOAD_DECLINE;
981         }
982
983         if (ast_sched_start_thread(sched)) {
984                 ast_log(LOG_ERROR, "Failed to create scheduler thread for cache management\n");
985                 unload_module();
986                 return AST_MODULE_LOAD_DECLINE;
987         }
988
989         caches = ao2_container_alloc(CACHES_CONTAINER_BUCKET_SIZE, sorcery_memory_cache_hash,
990                 sorcery_memory_cache_cmp);
991         if (!caches) {
992                 ast_log(LOG_ERROR, "Failed to create container for configured caches\n");
993                 unload_module();
994                 return AST_MODULE_LOAD_DECLINE;
995         }
996
997         if (ast_sorcery_wizard_register(&memory_cache_object_wizard)) {
998                 unload_module();
999                 return AST_MODULE_LOAD_DECLINE;
1000         }
1001
1002         AST_TEST_REGISTER(open_with_valid_options);
1003         AST_TEST_REGISTER(open_with_invalid_options);
1004         AST_TEST_REGISTER(create_and_retrieve);
1005         AST_TEST_REGISTER(update);
1006         AST_TEST_REGISTER(delete);
1007
1008         return AST_MODULE_LOAD_SUCCESS;
1009 }
1010
1011 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Sorcery Memory Cache Object Wizard",
1012         .support_level = AST_MODULE_SUPPORT_CORE,
1013         .load = load_module,
1014         .unload = unload_module,
1015         .load_pri = AST_MODPRI_REALTIME_DRIVER,
1016 );