[asterisk-commits] file: trunk r387662 - in /trunk: include/asterisk/ main/ res/ tests/

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Mon May 6 08:04:13 CDT 2013


Author: file
Date: Mon May  6 08:04:08 2013
New Revision: 387662

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=387662
Log:
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/

Modified:
    trunk/include/asterisk/sorcery.h
    trunk/main/sorcery.c
    trunk/res/res_sorcery_astdb.c
    trunk/tests/test_sorcery.c

Modified: trunk/include/asterisk/sorcery.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/sorcery.h?view=diff&rev=387662&r1=387661&r2=387662
==============================================================================
--- trunk/include/asterisk/sorcery.h (original)
+++ trunk/include/asterisk/sorcery.h Mon May  6 08:04:08 2013
@@ -232,6 +232,21 @@
 	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 @@
 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 @@
 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

Modified: trunk/main/sorcery.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/sorcery.c?view=diff&rev=387662&r1=387661&r2=387662
==============================================================================
--- trunk/main/sorcery.c (original)
+++ trunk/main/sorcery.c Mon May  6 08:04:08 2013
@@ -38,6 +38,9 @@
 #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
@@ -51,6 +54,9 @@
 
 /*! \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 {
@@ -83,6 +89,27 @@
 
 	/*! \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 @@
 
 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 @@
 
 	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 @@
 	}
 
 	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,12 +389,27 @@
 		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;
 	}
 
 	if (!(object_type->file = ast_calloc(1, sizeof(*object_type->file) + 2 * sizeof(object_type->file->types[0])))) {
+		ao2_ref(object_type, -1);
+		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;
 	}
@@ -597,6 +655,58 @@
 	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;
@@ -604,6 +714,14 @@
 
 	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;
 }
@@ -713,6 +831,68 @@
 	}
 
 	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)
@@ -1016,6 +1196,29 @@
 	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,9 +1233,39 @@
 		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 */
@@ -1059,9 +1292,39 @@
 		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 */
@@ -1088,7 +1351,14 @@
 		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 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);
+}

Modified: trunk/res/res_sorcery_astdb.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_sorcery_astdb.c?view=diff&rev=387662&r1=387661&r2=387662
==============================================================================
--- trunk/res/res_sorcery_astdb.c (original)
+++ trunk/res/res_sorcery_astdb.c Mon May  6 08:04:08 2013
@@ -125,13 +125,12 @@
 
 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;
 	}
 

Modified: trunk/tests/test_sorcery.c
URL: http://svnview.digium.com/svn/asterisk/trunk/tests/test_sorcery.c?view=diff&rev=387662&r1=387661&r2=387662
==============================================================================
--- trunk/tests/test_sorcery.c (original)
+++ trunk/tests/test_sorcery.c Mon May  6 08:04:08 2013
@@ -37,6 +37,7 @@
 #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 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;
 
@@ -138,6 +160,9 @@
 
 /*! \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)
 {
@@ -171,6 +196,42 @@
 	.retrieve_id = sorcery_test_retrieve_id,
 	.update = sorcery_test_update,
 	.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)
@@ -862,6 +923,63 @@
 			}
 		} else {
 			ast_test_status_update(test, "Object set created field '%s' which is unknown\n", field->name);
+			res = AST_TEST_FAIL;
+		}
+	}
+
+	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;
 		}
 	}
@@ -1950,6 +2068,151 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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);




More information about the asterisk-commits mailing list