diff options
-rw-r--r-- | include/asterisk/sorcery.h | 52 | ||||
-rw-r--r-- | main/sorcery.c | 315 | ||||
-rw-r--r-- | res/res_sorcery_astdb.c | 5 | ||||
-rw-r--r-- | tests/test_sorcery.c | 267 |
4 files changed, 633 insertions, 6 deletions
diff --git a/include/asterisk/sorcery.h b/include/asterisk/sorcery.h index 434f5595a..ae97da5cc 100644 --- a/include/asterisk/sorcery.h +++ b/include/asterisk/sorcery.h @@ -232,6 +232,21 @@ struct ast_sorcery_wizard { void (*close)(void *data); }; +/*! \brief Interface for a sorcery object type observer */ +struct ast_sorcery_observer { + /*! \brief Callback for when an object is created */ + void (*created)(const void *object); + + /*! \brief Callback for when an object is updated */ + void (*updated)(const void *object); + + /*! \brief Callback for when an object is deleted */ + void (*deleted)(const void *object); + + /*! \brief Callback for when an object type is loaded/reloaded */ + void (*loaded)(const char *object_type); +}; + /*! \brief Structure which contains details about a sorcery object */ struct ast_sorcery_object_details { /*! \brief Unique identifier of this object */ @@ -472,6 +487,19 @@ void ast_sorcery_ref(struct ast_sorcery *sorcery); struct ast_variable *ast_sorcery_objectset_create(const struct ast_sorcery *sorcery, const void *object); /*! + * \brief Create an object set in JSON format for an object + * + * \param sorcery Pointer to a sorcery structure + * \param object Pointer to a sorcery object + * + * \retval non-NULL success + * \retval NULL if error occurred + * + * \note The returned ast_json object must be unreferenced using ast_json_unref + */ +struct ast_json *ast_sorcery_objectset_json_create(const struct ast_sorcery *sorcery, const void *object); + +/*! * \brief Apply an object set (KVP list) to an object * * \param sorcery Pointer to a sorcery structure @@ -541,6 +569,30 @@ void *ast_sorcery_copy(const struct ast_sorcery *sorcery, const void *object); int ast_sorcery_diff(const struct ast_sorcery *sorcery, const void *original, const void *modified, struct ast_variable **changes); /*! + * \brief Add an observer to a specific object type + * + * \param sorcery Pointer to a sorcery structure + * \param type Type of object that should be observed + * \param callbacks Implementation of the observer interface + * + * \retval 0 success + * \retval -1 failure + */ +int ast_sorcery_observer_add(const struct ast_sorcery *sorcery, const char *type, const struct ast_sorcery_observer *callbacks); + +/*! + * \brief Remove an observer from a specific object type + * + * \param sorcery Pointer to a sorcery structure + * \param type Type of object that should no longer be observed + * \param callbacks Implementation of the observer interface + * + * \retval 0 success + * \retval -1 failure + */ +void ast_sorcery_observer_remove(const struct ast_sorcery *sorcery, const char *type, struct ast_sorcery_observer *callbacks); + +/*! * \brief Create and potentially persist an object using an available wizard * * \param sorcery Pointer to a sorcery structure diff --git a/main/sorcery.c b/main/sorcery.c index d0aee4a92..aa48222dd 100644 --- a/main/sorcery.c +++ b/main/sorcery.c @@ -38,6 +38,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/config_options.h" #include "asterisk/netsock2.h" #include "asterisk/module.h" +#include "asterisk/taskprocessor.h" +#include "asterisk/threadpool.h" +#include "asterisk/json.h" /* To prevent DEBUG_FD_LEAKS from interfering with things we undef open and close */ #undef open @@ -52,6 +55,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") /*! \brief Maximum length of an object field name */ #define MAX_OBJECT_FIELD 128 +/*! \brief Thread pool for observers */ +static struct ast_threadpool *threadpool; + /*! \brief Structure for registered object type */ struct ast_sorcery_object_type { /*! \brief Unique name of the object type */ @@ -83,6 +89,27 @@ struct ast_sorcery_object_type { /*! \brief Type details */ struct aco_type type; + + /*! \brief Observers */ + struct ao2_container *observers; + + /*! \brief Serializer for observers */ + struct ast_taskprocessor *serializer; +}; + +/*! \brief Structure for registered object type observer */ +struct ast_sorcery_object_type_observer { + /*! \brief Pointer to the observer implementation */ + const struct ast_sorcery_observer *callbacks; +}; + +/*! \brief Structure used for observer invocations */ +struct sorcery_observer_invocation { + /*! \brief Pointer to the object type */ + struct ast_sorcery_object_type *object_type; + + /*! \brief Pointer to the object */ + void *object; }; /*! \brief Structure for registered object field */ @@ -213,9 +240,21 @@ static int sorcery_wizard_cmp(void *obj, void *arg, int flags) int ast_sorcery_init(void) { + struct ast_threadpool_options options = { + .version = AST_THREADPOOL_OPTIONS_VERSION, + .auto_increment = 1, + .max_size = 0, + .idle_timeout = 60, + .initial_size = 0, + }; ast_assert(wizards == NULL); + if (!(threadpool = ast_threadpool_create("Sorcery", NULL, &options))) { + return -1; + } + if (!(wizards = ao2_container_alloc(WIZARD_BUCKETS, sorcery_wizard_hash, sorcery_wizard_cmp))) { + ast_threadpool_shutdown(threadpool); return -1; } @@ -317,6 +356,7 @@ static void sorcery_object_type_destructor(void *obj) ao2_cleanup(object_type->wizards); ao2_cleanup(object_type->fields); + ao2_cleanup(object_type->observers); if (object_type->info) { aco_info_destroy(object_type->info); @@ -324,12 +364,15 @@ static void sorcery_object_type_destructor(void *obj) } ast_free(object_type->file); + + ast_taskprocessor_unreference(object_type->serializer); } /*! \brief Internal function which allocates an object type structure */ static struct ast_sorcery_object_type *sorcery_object_type_alloc(const char *type, const char *module) { struct ast_sorcery_object_type *object_type; + char uuid[AST_UUID_STR_LEN]; if (!(object_type = ao2_alloc(sizeof(*object_type), sorcery_object_type_destructor))) { return NULL; @@ -346,6 +389,11 @@ static struct ast_sorcery_object_type *sorcery_object_type_alloc(const char *typ return NULL; } + if (!(object_type->observers = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK, 1, NULL, NULL))) { + ao2_ref(object_type, -1); + return NULL; + } + if (!(object_type->info = ast_calloc(1, sizeof(*object_type->info) + 2 * sizeof(object_type->info->files[0])))) { ao2_ref(object_type, -1); return NULL; @@ -356,6 +404,16 @@ static struct ast_sorcery_object_type *sorcery_object_type_alloc(const char *typ return NULL; } + if (!ast_uuid_generate_str(uuid, sizeof(uuid))) { + ao2_ref(object_type, -1); + return NULL; + } + + if (!(object_type->serializer = ast_threadpool_serializer(uuid, threadpool))) { + ao2_ref(object_type, -1); + return NULL; + } + object_type->info->files[0] = object_type->file; object_type->info->files[1] = NULL; object_type->info->module = module; @@ -597,6 +655,58 @@ static int sorcery_wizard_load(void *obj, void *arg, int flags) return 0; } +/*! \brief Destructor for observer invocation */ +static void sorcery_observer_invocation_destroy(void *obj) +{ + struct sorcery_observer_invocation *invocation = obj; + + ao2_cleanup(invocation->object_type); + ao2_cleanup(invocation->object); +} + +/*! \brief Allocator function for observer invocation */ +static struct sorcery_observer_invocation *sorcery_observer_invocation_alloc(struct ast_sorcery_object_type *object_type, void *object) +{ + struct sorcery_observer_invocation *invocation = ao2_alloc(sizeof(*invocation), sorcery_observer_invocation_destroy); + + if (!invocation) { + return NULL; + } + + ao2_ref(object_type, +1); + invocation->object_type = object_type; + + if (object) { + ao2_ref(object, +1); + invocation->object = object; + } + + return invocation; +} + +/*! \brief Internal callback function which notifies an individual observer that an object type has been loaded */ +static int sorcery_observer_notify_loaded(void *obj, void *arg, int flags) +{ + const struct ast_sorcery_object_type_observer *observer = obj; + + if (observer->callbacks->loaded) { + observer->callbacks->loaded(arg); + } + + return 0; +} + +/*! \brief Internal callback function which notifies observers that an object type has been loaded */ +static int sorcery_observers_notify_loaded(void *data) +{ + struct sorcery_observer_invocation *invocation = data; + + ao2_callback(invocation->object_type->observers, OBJ_NODATA, sorcery_observer_notify_loaded, invocation->object_type->name); + ao2_cleanup(invocation); + + return 0; +} + static int sorcery_object_load(void *obj, void *arg, int flags) { struct ast_sorcery_object_type *type = obj; @@ -605,6 +715,14 @@ static int sorcery_object_load(void *obj, void *arg, int flags) details->type = type->name; ao2_callback(type->wizards, OBJ_NODATA, sorcery_wizard_load, details); + if (ao2_container_count(type->observers)) { + struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(type, NULL); + + if (invocation && ast_taskprocessor_push(type->serializer, sorcery_observers_notify_loaded, invocation)) { + ao2_cleanup(invocation); + } + } + return 0; } @@ -715,6 +833,68 @@ struct ast_variable *ast_sorcery_objectset_create(const struct ast_sorcery *sorc return fields; } +struct ast_json *ast_sorcery_objectset_json_create(const struct ast_sorcery *sorcery, const void *object) +{ + const struct ast_sorcery_object_details *details = object; + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup); + struct ao2_iterator i; + struct ast_sorcery_object_field *object_field; + struct ast_json *json = ast_json_object_create(); + int res = 0; + + if (!object_type || !json) { + return NULL; + } + + i = ao2_iterator_init(object_type->fields, 0); + + for (; (object_field = ao2_iterator_next(&i)) && !res; ao2_ref(object_field, -1)) { + if (object_field->multiple_handler) { + struct ast_variable *tmp = NULL; + struct ast_variable *field; + + if ((res = object_field->multiple_handler(object, &tmp))) { + break; + } + + for (field = tmp; field; field = field->next) { + struct ast_json *value = ast_json_string_create(field->value); + + if (value && ast_json_object_set(json, field->name, value)) { + ast_json_unref(value); + res = -1; + } + } + + ast_variables_destroy(tmp); + } else if (object_field->handler) { + char *buf = NULL; + struct ast_json *value = NULL; + + if ((res = object_field->handler(object, object_field->args, &buf)) || + !(value = ast_json_string_create(buf)) || + ast_json_object_set(json, object_field->name, value)) { + ast_json_unref(value); + res = -1; + } + + ast_free(buf); + } else { + continue; + } + } + + ao2_iterator_destroy(&i); + + /* If any error occurs we destroy the JSON object so a partial objectset is not returned */ + if (res) { + ast_json_unref(json); + json = NULL; + } + + return json; +} + int ast_sorcery_objectset_apply(const struct ast_sorcery *sorcery, void *object, struct ast_variable *objectset) { const struct ast_sorcery_object_details *details = object; @@ -1016,6 +1196,29 @@ static int sorcery_wizard_create(void *obj, void *arg, int flags) return (!object_wizard->caching && !object_wizard->wizard->create(details->sorcery, object_wizard->data, details->obj)) ? CMP_MATCH | CMP_STOP : 0; } +/*! \brief Internal callback function which notifies an individual observer that an object has been created */ +static int sorcery_observer_notify_create(void *obj, void *arg, int flags) +{ + const struct ast_sorcery_object_type_observer *observer = obj; + + if (observer->callbacks->created) { + observer->callbacks->created(arg); + } + + return 0; +} + +/*! \brief Internal callback function which notifies observers that an object has been created */ +static int sorcery_observers_notify_create(void *data) +{ + struct sorcery_observer_invocation *invocation = data; + + ao2_callback(invocation->object_type->observers, OBJ_NODATA, sorcery_observer_notify_create, invocation->object); + ao2_cleanup(invocation); + + return 0; +} + int ast_sorcery_create(const struct ast_sorcery *sorcery, void *object) { const struct ast_sorcery_object_details *details = object; @@ -1030,11 +1233,41 @@ int ast_sorcery_create(const struct ast_sorcery *sorcery, void *object) return -1; } - object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_create, &sdetails); + if ((object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_create, &sdetails)) && + ao2_container_count(object_type->observers)) { + struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(object_type, object); + + if (invocation && ast_taskprocessor_push(object_type->serializer, sorcery_observers_notify_create, invocation)) { + ao2_cleanup(invocation); + } + } return object_wizard ? 0 : -1; } +/*! \brief Internal callback function which notifies an individual observer that an object has been updated */ +static int sorcery_observer_notify_update(void *obj, void *arg, int flags) +{ + const struct ast_sorcery_object_type_observer *observer = obj; + + if (observer->callbacks->updated) { + observer->callbacks->updated(arg); + } + + return 0; +} + +/*! \brief Internal callback function which notifies observers that an object has been updated */ +static int sorcery_observers_notify_update(void *data) +{ + struct sorcery_observer_invocation *invocation = data; + + ao2_callback(invocation->object_type->observers, OBJ_NODATA, sorcery_observer_notify_update, invocation->object); + ao2_cleanup(invocation); + + return 0; +} + /*! \brief Internal function which returns if a wizard has updated the object */ static int sorcery_wizard_update(void *obj, void *arg, int flags) { @@ -1059,11 +1292,41 @@ int ast_sorcery_update(const struct ast_sorcery *sorcery, void *object) return -1; } - object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_update, &sdetails); + if ((object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_update, &sdetails)) && + ao2_container_count(object_type->observers)) { + struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(object_type, object); + + if (invocation && ast_taskprocessor_push(object_type->serializer, sorcery_observers_notify_update, invocation)) { + ao2_cleanup(invocation); + } + } return object_wizard ? 0 : -1; } +/*! \brief Internal callback function which notifies an individual observer that an object has been deleted */ +static int sorcery_observer_notify_delete(void *obj, void *arg, int flags) +{ + const struct ast_sorcery_object_type_observer *observer = obj; + + if (observer->callbacks->deleted) { + observer->callbacks->deleted(arg); + } + + return 0; +} + +/*! \brief Internal callback function which notifies observers that an object has been deleted */ +static int sorcery_observers_notify_delete(void *data) +{ + struct sorcery_observer_invocation *invocation = data; + + ao2_callback(invocation->object_type->observers, OBJ_NODATA, sorcery_observer_notify_delete, invocation->object); + ao2_cleanup(invocation); + + return 0; +} + /*! \brief Internal function which returns if a wizard has deleted the object */ static int sorcery_wizard_delete(void *obj, void *arg, int flags) { @@ -1088,7 +1351,14 @@ int ast_sorcery_delete(const struct ast_sorcery *sorcery, void *object) return -1; } - object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_delete, &sdetails); + if ((object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_delete, &sdetails)) && + ao2_container_count(object_type->observers)) { + struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(object_type, object); + + if (invocation && ast_taskprocessor_push(object_type->serializer, sorcery_observers_notify_delete, invocation)) { + ao2_cleanup(invocation); + } + } return object_wizard ? 0 : -1; } @@ -1109,3 +1379,42 @@ const char *ast_sorcery_object_get_type(const void *object) const struct ast_sorcery_object_details *details = object; return details->type; } + +int ast_sorcery_observer_add(const struct ast_sorcery *sorcery, const char *type, const struct ast_sorcery_observer *callbacks) +{ + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup); + struct ast_sorcery_object_type_observer *observer; + + if (!object_type || !callbacks) { + return -1; + } + + if (!(observer = ao2_alloc(sizeof(*observer), NULL))) { + return -1; + } + + observer->callbacks = callbacks; + ao2_link(object_type->observers, observer); + ao2_ref(observer, -1); + + return 0; +} + +/*! \brief Internal callback function for removing an observer */ +static int sorcery_observer_remove(void *obj, void *arg, int flags) +{ + const struct ast_sorcery_object_type_observer *observer = obj; + + return (observer->callbacks == arg) ? CMP_MATCH | CMP_STOP : 0; +} + +void ast_sorcery_observer_remove(const struct ast_sorcery *sorcery, const char *type, struct ast_sorcery_observer *callbacks) +{ + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup); + + if (!object_type) { + return; + } + + ao2_callback(object_type->observers, OBJ_NODATA | OBJ_UNLINK, sorcery_observer_remove, callbacks); +} diff --git a/res/res_sorcery_astdb.c b/res/res_sorcery_astdb.c index 41f403651..65a6a504e 100644 --- a/res/res_sorcery_astdb.c +++ b/res/res_sorcery_astdb.c @@ -125,13 +125,12 @@ static int sorcery_json_equal(struct ast_json *object, struct ast_json *criteria static int sorcery_astdb_create(const struct ast_sorcery *sorcery, void *data, void *object) { - RAII_VAR(struct ast_variable *, objset, ast_sorcery_objectset_create(sorcery, object), ast_variables_destroy); - RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, objset, ast_sorcery_objectset_json_create(sorcery, object), ast_json_unref); RAII_VAR(char *, value, NULL, ast_free_ptr); const char *prefix = data; char family[strlen(prefix) + strlen(ast_sorcery_object_get_type(object)) + 2]; - if (!objset || !(json = sorcery_objectset_to_json(objset)) || !(value = ast_json_dump_string(json))) { + if (!objset || !(value = ast_json_dump_string(objset))) { return -1; } diff --git a/tests/test_sorcery.c b/tests/test_sorcery.c index 6cfd2de4f..e1d4b7fce 100644 --- a/tests/test_sorcery.c +++ b/tests/test_sorcery.c @@ -37,6 +37,7 @@ ASTERISK_FILE_VERSION(__FILE__, "") #include "asterisk/module.h" #include "asterisk/sorcery.h" #include "asterisk/logger.h" +#include "asterisk/json.h" /*! \brief Dummy sorcery object */ struct test_sorcery_object { @@ -126,6 +127,27 @@ struct sorcery_test_caching { struct test_sorcery_object object; }; +/*! \brief Test structure for observer */ +struct sorcery_test_observer { + /*! \brief Lock for notification */ + ast_mutex_t lock; + + /*! \brief Condition for notification */ + ast_cond_t cond; + + /*! \brief Pointer to the created object */ + const void *created; + + /*! \brief Pointer to the update object */ + const void *updated; + + /*! \brief Pointer to the deleted object */ + const void *deleted; + + /*! \brief Whether the type has been loaded */ + unsigned int loaded:1; +}; + /*! \brief Global scope apply handler integer to make sure it executed */ static int apply_handler_called; @@ -139,6 +161,9 @@ static int test_apply_handler(const struct ast_sorcery *sorcery, void *obj) /*! \brief Global scope caching structure for testing */ static struct sorcery_test_caching cache = { 0, }; +/*! \brief Global scope observer structure for testing */ +static struct sorcery_test_observer observer; + static int sorcery_test_create(const struct ast_sorcery *sorcery, void *data, void *object) { cache.created = 1; @@ -173,6 +198,42 @@ static struct ast_sorcery_wizard test_wizard = { .delete = sorcery_test_delete, }; +static void sorcery_observer_created(const void *object) +{ + SCOPED_MUTEX(lock, &observer.lock); + observer.created = object; + ast_cond_signal(&observer.cond); +} + +static void sorcery_observer_updated(const void *object) +{ + SCOPED_MUTEX(lock, &observer.lock); + observer.updated = object; + ast_cond_signal(&observer.cond); +} + +static void sorcery_observer_deleted(const void *object) +{ + SCOPED_MUTEX(lock, &observer.lock); + observer.deleted = object; + ast_cond_signal(&observer.cond); +} + +static void sorcery_observer_loaded(const char *object_type) +{ + SCOPED_MUTEX(lock, &observer.lock); + observer.loaded = 1; + ast_cond_signal(&observer.cond); +} + +/*! \brief Test sorcery observer implementation */ +static struct ast_sorcery_observer test_observer = { + .created = sorcery_observer_created, + .updated = sorcery_observer_updated, + .deleted = sorcery_observer_deleted, + .loaded = sorcery_observer_loaded, +}; + static struct ast_sorcery *alloc_and_initialize_sorcery(void) { struct ast_sorcery *sorcery; @@ -869,6 +930,63 @@ AST_TEST_DEFINE(objectset_create) return res; } +AST_TEST_DEFINE(objectset_json_create) +{ + int res = AST_TEST_PASS; + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct ast_json *, objset, NULL, ast_json_unref); + struct ast_json_iter *field; + + switch (cmd) { + case TEST_INIT: + info->name = "objectset_json_create"; + info->category = "/main/sorcery/"; + info->summary = "sorcery json object set creation unit test"; + info->description = + "Test object set creation (for JSON format) in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = alloc_and_initialize_sorcery())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + if (!(objset = ast_sorcery_objectset_json_create(sorcery, obj))) { + ast_test_status_update(test, "Failed to create an object set for a known sane object\n"); + return AST_TEST_FAIL; + } + + for (field = ast_json_object_iter(objset); field; field = ast_json_object_iter_next(objset, field)) { + struct ast_json *value = ast_json_object_iter_value(field); + + if (!strcmp(ast_json_object_iter_key(field), "bob")) { + if (strcmp(ast_json_string_get(value), "5")) { + ast_test_status_update(test, "Object set failed to create proper value for 'bob'\n"); + res = AST_TEST_FAIL; + } + } else if (!strcmp(ast_json_object_iter_key(field), "joe")) { + if (strcmp(ast_json_string_get(value), "10")) { + ast_test_status_update(test, "Object set failed to create proper value for 'joe'\n"); + res = AST_TEST_FAIL; + } + } else { + ast_test_status_update(test, "Object set created field '%s' which is unknown\n", ast_json_object_iter_key(field)); + res = AST_TEST_FAIL; + } + } + + return res; +} + AST_TEST_DEFINE(objectset_create_regex) { int res = AST_TEST_PASS; @@ -1950,6 +2068,151 @@ end: return res; } +AST_TEST_DEFINE(object_type_observer) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + int res = AST_TEST_FAIL; + + switch (cmd) { + case TEST_INIT: + info->name = "object_type_observer"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object type observer unit test"; + info->description = + "Test that object type observers get called when they should"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = alloc_and_initialize_sorcery())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!ast_sorcery_observer_add(sorcery, "test", NULL)) { + ast_test_status_update(test, "Successfully added a NULL observer when it should not be possible\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_observer_add(sorcery, "test", &test_observer)) { + ast_test_status_update(test, "Failed to add a proper observer\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + goto end; + } + + ast_mutex_init(&observer.lock); + ast_cond_init(&observer.cond, NULL); + observer.created = NULL; + observer.updated = NULL; + observer.deleted = NULL; + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using in-memory wizard\n"); + goto end; + } + + ast_mutex_lock(&observer.lock); + while (!observer.created) { + struct timeval start = ast_tvnow(); + struct timespec end = { + .tv_sec = start.tv_sec + 10, + .tv_nsec = start.tv_usec * 1000, + }; + if (ast_cond_timedwait(&observer.cond, &observer.lock, &end) == ETIMEDOUT) { + break; + } + } + ast_mutex_unlock(&observer.lock); + + if (!observer.created) { + ast_test_status_update(test, "Failed to receive observer notification for object creation within suitable timeframe\n"); + goto end; + } + + if (ast_sorcery_update(sorcery, obj)) { + ast_test_status_update(test, "Failed to update object using in-memory wizard\n"); + goto end; + } + + ast_mutex_lock(&observer.lock); + while (!observer.updated) { + struct timeval start = ast_tvnow(); + struct timespec end = { + .tv_sec = start.tv_sec + 10, + .tv_nsec = start.tv_usec * 1000, + }; + if (ast_cond_timedwait(&observer.cond, &observer.lock, &end) == ETIMEDOUT) { + break; + } + } + ast_mutex_unlock(&observer.lock); + + if (!observer.updated) { + ast_test_status_update(test, "Failed to receive observer notification for object updating within suitable timeframe\n"); + goto end; + } + + if (ast_sorcery_delete(sorcery, obj)) { + ast_test_status_update(test, "Failed to delete object using in-memory wizard\n"); + goto end; + } + + ast_mutex_lock(&observer.lock); + while (!observer.deleted) { + struct timeval start = ast_tvnow(); + struct timespec end = { + .tv_sec = start.tv_sec + 10, + .tv_nsec = start.tv_usec * 1000, + }; + if (ast_cond_timedwait(&observer.cond, &observer.lock, &end) == ETIMEDOUT) { + break; + } + } + ast_mutex_unlock(&observer.lock); + + if (!observer.deleted) { + ast_test_status_update(test, "Failed to receive observer notification for object deletion within suitable timeframe\n"); + goto end; + } + + ast_sorcery_reload(sorcery); + + ast_mutex_lock(&observer.lock); + while (!observer.loaded) { + struct timeval start = ast_tvnow(); + struct timespec end = { + .tv_sec = start.tv_sec + 10, + .tv_nsec = start.tv_usec * 1000, + }; + if (ast_cond_timedwait(&observer.cond, &observer.lock, &end) == ETIMEDOUT) { + break; + } + } + ast_mutex_unlock(&observer.lock); + + if (!observer.loaded) { + ast_test_status_update(test, "Failed to receive observer notification for object type load within suitable timeframe\n"); + goto end; + } + + res = AST_TEST_PASS; + +end: + observer.created = NULL; + observer.updated = NULL; + observer.deleted = NULL; + ast_mutex_destroy(&observer.lock); + ast_cond_destroy(&observer.cond); + + return res; +} + AST_TEST_DEFINE(configuration_file_wizard) { struct ast_flags flags = { CONFIG_FLAG_NOCACHE }; @@ -2334,6 +2597,7 @@ static int unload_module(void) AST_TEST_UNREGISTER(object_diff); AST_TEST_UNREGISTER(object_diff_native); AST_TEST_UNREGISTER(objectset_create); + AST_TEST_UNREGISTER(objectset_json_create); AST_TEST_UNREGISTER(objectset_create_regex); AST_TEST_UNREGISTER(objectset_apply); AST_TEST_UNREGISTER(objectset_apply_handler); @@ -2353,6 +2617,7 @@ static int unload_module(void) AST_TEST_UNREGISTER(object_delete); AST_TEST_UNREGISTER(object_delete_uncreated); AST_TEST_UNREGISTER(caching_wizard_behavior); + AST_TEST_UNREGISTER(object_type_observer); AST_TEST_UNREGISTER(configuration_file_wizard); AST_TEST_UNREGISTER(configuration_file_wizard_with_file_integrity); AST_TEST_UNREGISTER(configuration_file_wizard_with_criteria); @@ -2379,6 +2644,7 @@ static int load_module(void) AST_TEST_REGISTER(object_diff); AST_TEST_REGISTER(object_diff_native); AST_TEST_REGISTER(objectset_create); + AST_TEST_REGISTER(objectset_json_create); AST_TEST_REGISTER(objectset_create_regex); AST_TEST_REGISTER(objectset_apply); AST_TEST_REGISTER(objectset_apply_handler); @@ -2398,6 +2664,7 @@ static int load_module(void) AST_TEST_REGISTER(object_delete); AST_TEST_REGISTER(object_delete_uncreated); AST_TEST_REGISTER(caching_wizard_behavior); + AST_TEST_REGISTER(object_type_observer); AST_TEST_REGISTER(configuration_file_wizard); AST_TEST_REGISTER(configuration_file_wizard_with_file_integrity); AST_TEST_REGISTER(configuration_file_wizard_with_criteria); |