summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua Colp <jcolp@digium.com>2013-05-06 13:04:08 +0000
committerJoshua Colp <jcolp@digium.com>2013-05-06 13:04:08 +0000
commit40074542bf517a751061d7cf6f9ff83431e35ff8 (patch)
tree8e7d52a4c65363e0d4cbd8effd83995ac3bfa892
parent6e2fe0c9ab1ec9cdd0d45e48fd03217d06cc17ae (diff)
Add support for observers and JSON objectset creation to sorcery.
This change adds the ability for modules to add themselves as observers to sorcery object types. Observers can be notified when objects are created, updated, or deleted as well as when the object type is loaded or reloaded. Observer notifications are done using a thread pool in a serialized fashion so the caller of the sorcery API calls is minimally impacted. This also adds the ability to create JSON changesets of a sorcery object. Tests are also present to confirm all of the above functionality. Review: https://reviewboard.asterisk.org/r/2477/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@387662 65c4cc65-6c06-0410-ace0-fbb531ad65f3
-rw-r--r--include/asterisk/sorcery.h52
-rw-r--r--main/sorcery.c315
-rw-r--r--res/res_sorcery_astdb.c5
-rw-r--r--tests/test_sorcery.c267
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);