diff options
-rw-r--r-- | configs/sorcery.conf.sample | 50 | ||||
-rw-r--r-- | configs/test_sorcery.conf.sample | 14 | ||||
-rw-r--r-- | include/asterisk/sorcery.h | 537 | ||||
-rw-r--r-- | main/asterisk.c | 6 | ||||
-rw-r--r-- | main/sorcery.c | 965 | ||||
-rw-r--r-- | res/res_sorcery_config.c | 346 | ||||
-rw-r--r-- | res/res_sorcery_memory.c | 211 | ||||
-rw-r--r-- | tests/test_sorcery.c | 1950 |
8 files changed, 4079 insertions, 0 deletions
diff --git a/configs/sorcery.conf.sample b/configs/sorcery.conf.sample new file mode 100644 index 000000000..899467a4e --- /dev/null +++ b/configs/sorcery.conf.sample @@ -0,0 +1,50 @@ +; Sample configuration file for Sorcery Data Access Layer + +; +; Wizards +; +; Wizards are the persistence mechanism for objects. They are loaded as Asterisk modules and register +; themselves with the sorcery core. All implementation specific details of how objects are persisted is isolated +; within wizards. +; + +; +; Caching +; +; A wizard can optionally be marked as an object cache by adding "/cache" to the object type within the mapping. +; If an object is returned from a non-object cache it is immediately given to the cache to be created. Multiple +; object caches can be configured for a single object type. +; + +; +; Object Type Mappings +; +; To allow configuration of where and how an object is persisted object mappings can be defined within this file +; on a per-module basis. The mapping consists of the object type, options, wizard name, and wizard configuration +; data. This has the following format: +; +; object type [/options] = wizard name, wizard configuration data +; +; For example to configure an in-memory wizard for the 'bob' object type: +; +; bob = memory +; +; Or to configure the object type 'joe' from a configuration file: +; +; joe = config,joe.conf +; +; Note that an object type can have multiple mappings defined. Each mapping will be consulted in the order in which +; it appears within the configuration file. This means that if you are configuring a wizard as a cache it should +; appear as the first mapping so the cache is consulted before all other mappings. +; + +; +; The following object mappings are used by the unit test to test certain functionality of sorcery. +; +[test_sorcery] +test=memory + +[test_sorcery_cache] +test/cache=test +test=memory + diff --git a/configs/test_sorcery.conf.sample b/configs/test_sorcery.conf.sample new file mode 100644 index 000000000..c465dbf74 --- /dev/null +++ b/configs/test_sorcery.conf.sample @@ -0,0 +1,14 @@ +; This is a res_sorcery_config compatible file for the sorcery unit tests + +[hey] +bob=98 +joe=41 + +[hey2] +type=zombies +bob=97 +joe=40 + +[hey3] +bob=96 +joe=39 diff --git a/include/asterisk/sorcery.h b/include/asterisk/sorcery.h new file mode 100644 index 000000000..1a1042fbd --- /dev/null +++ b/include/asterisk/sorcery.h @@ -0,0 +1,537 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012 - 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * \brief Sorcery Data Access Layer API + * \author Joshua Colp <jcolp@digium.com> + * \ref AstSorcery + */ + +/*! + * \page AstSorcery Data Access Layer API + * + * Sorcery is a unifying data access layer which utilizes the configuration framework, + * realtime, and astdb to allow object creation, retrieval, updating, and deletion. + * + * \par Initialization + * + * Usage of sorcery is accomplished by first opening a sorcery structure. This structure holds + * all information about the object types, object fields, and object mappings. All API functions + * require the sorcery structure to operate. When sorcery is no longer needed the structure can + * be unreferenced using \ref ast_sorcery_unref + * + * Once opened the sorcery structure must have object mappings applied to it. This maps the + * object types to their respective wizards (object storage modules). If the developer would like + * to allow the user to configure this using the sorcery.conf configuration file the + * \ref ast_sorcery_apply_config API call can be used to read in the configuration file and apply the + * mappings. If the storage of the object types are such that a default wizard can be used this can + * be applied using the \ref ast_sorcery_apply_default API call. Note that the default mappings will not + * override configured mappings. They are only used in the case where no configured mapping exists. + * + * Configuring object mappings implicitly creates a basic version of an object type. The object type + * must be fully registered, however, using the \ref ast_sorcery_object_type_register API call before any + * objects of the type can be allocated, created, or retrieved. + * + * Once the object type itself has been fully registered the individual fields within the object must + * be registered using the \ref ast_sorcery_object_field_register API call. Note that not all fields *need* + * be registered. Only fields that should be accessible using the sorcery API have to be registered. + * + * \par Creating Objects + * + * Before an object can be created within the sorcery API it must first be allocated using the + * \ref ast_sorcery_alloc API call. This allocates a new instance of the object, sets sorcery specific + * details, and applies default values to the object. A unique identifier can optionally be specified + * when allocating an object. If it is not provided one will be automatically generated. Allocating + * an object does not create it within any object storage mechanisms that are configured for the + * object type. Creation must explicitly be done using the \ref ast_sorcery_create API call. This API call + * passes the object to each configured object storage mechanism for the object type until one + * successfully persists the object. + * + * \par Retrieving Objects + * + * To retrieve a single object using its unique identifier the \ref ast_sorcery_retrieve_by_id API call + * can be used. + * + * To retrieve potentially multiple objects using specific fields the \ref ast_sorcery_retrieve_by_fields + * API call can be used. The behavior of this API call is controlled using different flags. If the + * AST_RETRIEVE_FLAG_MULTIPLE flag is used a container will be returned which contains all matching objects. + * To retrieve all objects the AST_RETRIEVE_FLAG_ALL flag can be specified. Note that when specifying this flag + * you do not need to pass any fields. + * + * Both API calls return shared objects. Modification of the object can not occur until it has been copied. + * + * \par Updating Objects + * + * As retrieved objects may be shared the first step to updating the object with new details is creating a + * copy using the \ref ast_sorcery_copy API call. This will return a new object which is specific to the caller. + * Any field within the object may be modified as needed. Once changes are done the changes can be committed + * using the \ref ast_sorcery_update API call. Note that as the copied object is specific to the caller it must + * be unreferenced after use. + * + * \par Deleting Objects + * + * To delete an object simply call the \ref ast_sorcery_delete API call with an object retrieved using the + * ast_sorcery_retrieve_by_* API calls or a copy returned from \ref ast_sorcery_copy. + */ + +#ifndef _ASTERISK_SORCERY_H +#define _ASTERISK_SORCERY_H + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +#include "asterisk/config_options.h" +#include "asterisk/uuid.h" + +/*! \brief Maximum size of an object type */ +#define MAX_OBJECT_TYPE 64 + +/*! + * \brief Retrieval flags + */ +enum ast_sorcery_retrieve_flags { + /*! \brief Default retrieval flags */ + AST_RETRIEVE_FLAG_DEFAULT = 0, + + /*! \brief Return all matching objects */ + AST_RETRIEVE_FLAG_MULTIPLE = (1 << 0), + + /*! \brief Perform no matching, return all objects */ + AST_RETRIEVE_FLAG_ALL = (1 << 1), +}; + +/*! \brief Forward declaration for the sorcery main structure */ +struct ast_sorcery; + +/*! + * \brief A callback function for translating a value into a string + * + * \param obj Object to get value from + * \param args Where the field is + * \param buf Pointer to the buffer that the handler has created which contains the field value + * + * \retval 0 success + * \retval -1 failure + */ +typedef int (*sorcery_field_handler)(const void *obj, const intptr_t *args, char **buf); + +/*! + * \brief A callback function for performing a transformation on an object set + * + * \param set The existing object set + * + * \retval non-NULL new object set if changed + * \retval NULL if no changes present + * + * \note The returned ast_variable list must be *new*. You can not return the input set. + */ +typedef struct ast_variable *(*sorcery_transform_handler)(struct ast_variable *set); + +/*! + * \brief A callback function for when an object set is successfully applied to an object + * + * \param sorcery Sorcery structure in use + * \param obj The object itself + */ +typedef void (*sorcery_apply_handler)(const struct ast_sorcery *sorcery, void *obj); + +/*! \brief Interface for a sorcery wizard */ +struct ast_sorcery_wizard { + /*! \brief Name of the wizard */ + const char *name; + + /*! \brief Pointer to the Asterisk module this wizard is implemented by */ + struct ast_module *module; + + /*! \brief Callback for opening a wizard */ + void *(*open)(const char *data); + + /*! \brief Optional callback for loading persistent objects */ + void (*load)(void *data, const struct ast_sorcery *sorcery, const char *type); + + /*! \brief Optional callback for reloading persistent objects */ + void (*reload)(void *data, const struct ast_sorcery *sorcery, const char *type); + + /*! \brief Callback for creating an object */ + int (*create)(void *data, void *object); + + /*! \brief Callback for retrieving an object using an id */ + void *(*retrieve_id)(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id); + + /*! \brief Optional callback for retrieving an object using fields */ + void *(*retrieve_fields)(const struct ast_sorcery *sorcery, void *data, const char *type, const struct ast_variable *fields); + + /*! \brief Optional callback for retrieving multiple objects using some optional field criteria */ + void (*retrieve_multiple)(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const struct ast_variable *fields); + + /*! \brief Callback for updating an object */ + int (*update)(void *data, void *object); + + /*! \brief Callback for deleting an object */ + int (*delete)(void *data, void *object); + + /*! \brief Callback for closing a wizard */ + void (*close)(void *data); +}; + +/*! \brief Structure which contains details about a sorcery object */ +struct ast_sorcery_object_details { + /*! \brief Unique identifier of this object */ + char id[AST_UUID_STR_LEN]; + + /*! \brief Type of object */ + char type[MAX_OBJECT_TYPE]; +}; + +/*! \brief Macro which must be used at the beginning of each sorcery capable object */ +#define SORCERY_OBJECT(details) \ +struct { \ + struct ast_sorcery_object_details details; \ +} \ + +/*! + * \brief Initialize the sorcery API + * + * \retval 0 success + * \retval -1 failure + */ +int ast_sorcery_init(void); + +/*! + * \brief Register a sorcery wizard + * + * \param interface Pointer to a wizard interface + * \param module Pointer to the module implementing the interface + * + * \retval 0 success + * \retval -1 failure + */ +int __ast_sorcery_wizard_register(const struct ast_sorcery_wizard *interface, struct ast_module *module); + +/*! + * \brief See \ref __ast_sorcery_wizard_register() + */ +#define ast_sorcery_wizard_register(interface) __ast_sorcery_wizard_register(interface, ast_module_info ? ast_module_info->self : NULL) + +/*! + * \brief Unregister a sorcery wizard + * + * \param interface Pointer to the wizard interface + * + * \retval 0 success + * \retval -1 failure + */ +int ast_sorcery_wizard_unregister(const struct ast_sorcery_wizard *interface); + +/*! + * \brief Open a new sorcery structure + * + * \retval non-NULL success + * \retval NULL if allocation failed + */ +struct ast_sorcery *ast_sorcery_open(void); + +/*! + * \brief Apply configured wizard mappings + * + * \param sorcery Pointer to a sorcery structure + * \param name Name of the category to use within the configuration file, normally the module name + * + * \retval 0 success + * \retval -1 failure + */ +int ast_sorcery_apply_config(struct ast_sorcery *sorcery, const char *name); + +/*! + * \brief Apply default object wizard mappings + * + * \param sorcery Pointer to a sorcery structure + * \param type Type of object to apply to + * \param name Name of the wizard to use + * \param data Data to be passed to wizard + * + * \retval 0 success + * \retval -1 failure + * + * \note This should be called *after* applying configuration sourced mappings + * + * \note Only a single default can exist per object type + */ +int ast_sorcery_apply_default(struct ast_sorcery *sorcery, const char *type, const char *name, const char *data); + +/*! + * \brief Register an object type + * + * \param sorcery Pointer to a sorcery structure + * \param type Type of object + * \param alloc Required object allocation callback + * \param transform Optional transformation callback + * \param apply Optional object set apply callback + * + * \retval 0 success + * \retval -1 failure + */ +int ast_sorcery_object_register(struct ast_sorcery *sorcery, const char *type, aco_type_item_alloc alloc, sorcery_transform_handler transform, sorcery_apply_handler apply); + +/*! + * \brief Register a field within an object + * + * \param sorcery Pointer to a sorcery structure + * \param type Type of object + * \param name Name of the field + * \param default_val Default value of the field + * \param opt_type Option type + * \param flags Option type specific flags + * + * \retval 0 success + * \retval -1 failure + */ +int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char *type, const char *name, const char *default_val, enum aco_option_type opt_type, + aco_option_handler config_handler, sorcery_field_handler sorcery_handler, unsigned int flags, size_t argc, ...); + +/*! + * \brief Register a field within an object + * + * \param sorcery Pointer to a sorcery structure + * \param type Type of object + * \param name Name of the field + * \param default_val Default value of the field + * \param opt_type Option type + * \param flags Option type specific flags + * + * \retval 0 success + * \retval -1 failure + */ +#define ast_sorcery_object_field_register(sorcery, type, name, default_val, opt_type, flags, ...) \ + __ast_sorcery_object_field_register(sorcery, type, name, default_val, opt_type, NULL, NULL, flags, VA_NARGS(__VA_ARGS__), __VA_ARGS__) + +/*! + * \brief Register a field within an object with custom handlers + * + * \param sorcery Pointer to a sorcery structure + * \param type Type of object + * \param name Name of the field + * \param default_val Default value of the field + * \param config_handler Custom configuration handler + * \param sorcery_handler Custom sorcery handler + * \param flags Option type specific flags + * + * \retval 0 success + * \retval -1 failure + */ +#define ast_sorcery_object_field_register_custom(sorcery, type, name, default_val, config_handler, sorcery_handler, flags, ...) \ + __ast_sorcery_object_field_register(sorcery, type, name, default_val, OPT_CUSTOM_T, config_handler, sorcery_handler, flags, VA_NARGS(__VA_ARGS__), __VA_ARGS__); + +/*! + * \brief Inform any wizards to load persistent objects + * + * \param sorcery Pointer to a sorcery structure + */ +void ast_sorcery_load(const struct ast_sorcery *sorcery); + +/*! + * \brief Inform any wizards to reload persistent objects + * + * \param sorcery Pointer to a sorcery structure + */ +void ast_sorcery_reload(const struct ast_sorcery *sorcery); + +/*! + * \brief Increase the reference count of a sorcery structure + * + * \param sorcery Pointer to a sorcery structure + */ +void ast_sorcery_ref(struct ast_sorcery *sorcery); + +/*! + * \brief Create an object set (KVP list) 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_variable list must be destroyed using ast_variables_destroy + */ +struct ast_variable *ast_sorcery_objectset_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 + * \param object Pointer to a sorcery object + * \param objectset Object set itself + * + * \retval 0 success + * \retval -1 failure + * + * \note This operation is *not* atomic. If this fails it is possible for the object to be left with a partially + * applied object set. + */ +int ast_sorcery_objectset_apply(const struct ast_sorcery *sorcery, void *object, struct ast_variable *objectset); + +/*! + * \brief Create a changeset given two object sets + * + * \param original Original object set + * \param modified Modified object set + * \param changes Pointer to hold any changes between the object sets + * + * \retval 0 success + * \retval -1 failure + * + * \note The returned ast_variable list must be destroyed using ast_variables_destroy + */ +int ast_sorcery_changeset_create(const struct ast_variable *original, const struct ast_variable *modified, struct ast_variable **changes); + +/*! + * \brief Allocate an object + * + * \param sorcery Pointer to a sorcery structure + * \param type Type of object to allocate + * \param id Optional unique identifier, if none is provided one will be generated + * + * \retval non-NULL success + * \retval NULL failure + */ +void *ast_sorcery_alloc(const struct ast_sorcery *sorcery, const char *type, const char *id); + +/*! + * \brief Create a copy of an object + * + * \param sorcery Pointer to a sorcery structure + * \param object Existing object + * + * \retval non-NULL success + * \retval NULL failure + */ +void *ast_sorcery_copy(const struct ast_sorcery *sorcery, const void *object); + +/*! + * \brief Create a changeset of two objects + * + * \param sorcery Pointer to a sorcery structure + * \param original Original object + * \param modified Modified object + * \param changes Pointer which will be populated with changes if any exist + * + * \retval 0 success + * \retval -1 failure + * + * \note The returned ast_variable list must be destroyed using ast_variables_destroy + * + * \note While the objects must be of the same type they do not have to be the same object + */ +int ast_sorcery_diff(const struct ast_sorcery *sorcery, const void *original, const void *modified, struct ast_variable **changes); + +/*! + * \brief Create and potentially persist an object using an available wizard + * + * \param sorcery Pointer to a sorcery structure + * \param object Pointer to a sorcery object + * + * \retval 0 success + * \retval -1 failure + */ +int ast_sorcery_create(const struct ast_sorcery *sorcery, void *object); + +/*! + * \brief Retrieve an object using its unique identifier + * + * \param sorcery Pointer to a sorcery structure + * \param type Type of object to retrieve + * \param id Unique object identifier + * + * \retval non-NULL if found + * \retval NULL if not found + */ +void *ast_sorcery_retrieve_by_id(const struct ast_sorcery *sorcery, const char *type, const char *id); + +/*! + * \brief Retrieve an object or multiple objects using specific fields + * + * \param sorcery Pointer to a sorcery structure + * \param type Type of object to retrieve + * \param flags Flags to control behavior + * \param fields Optional jbject fields and values to match against + * + * \retval non-NULL if found + * \retval NULL if not found + * + * \note If the AST_RETRIEVE_FLAG_MULTIPLE flag is specified the returned value will be an + * ao2_container that must be unreferenced after use. + * + * \note If the AST_RETRIEVE_FLAG_ALL flag is used you may omit fields to retrieve all objects + * of the given type. + */ +void *ast_sorcery_retrieve_by_fields(const struct ast_sorcery *sorcery, const char *type, unsigned int flags, struct ast_variable *fields); + +/*! + * \brief Update an object + * + * \param sorcery Pointer to a sorcery structure + * \param object Pointer to a sorcery object + * + * \retval 0 success + * \retval -1 failure + */ +int ast_sorcery_update(const struct ast_sorcery *sorcery, void *object); + +/*! + * \brief Delete an object + * + * \param sorcery Pointer to a sorcery structure + * \param object Pointer to a sorcery object + * + * \retval 0 success + * \retval -1 failure + */ +int ast_sorcery_delete(const struct ast_sorcery *sorcery, void *object); + +/*! + * \brief Decrease the reference count of a sorcery structure + * + * \param sorcery Pointer to a sorcery structure + */ +void ast_sorcery_unref(struct ast_sorcery *sorcery); + +/*! + * \brief Get the unique identifier of a sorcery object + * + * \param object Pointer to a sorcery object + * + * \retval unique identifier + */ +const char *ast_sorcery_object_get_id(const void *object); + +/*! + * \brief Get the type of a sorcery object + * + * \param object Pointer to a sorcery object + * + * \retval type of object + */ +const char *ast_sorcery_object_get_type(const void *object); + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* _ASTERISK_SORCERY_H */ diff --git a/main/asterisk.c b/main/asterisk.c index 67d09ff66..f7b62613a 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -239,6 +239,7 @@ int daemon(int, int); /* defined in libresolv of all places */ #include "asterisk/format.h" #include "asterisk/aoc.h" #include "asterisk/uuid.h" +#include "asterisk/sorcery.h" #include "../defaults.h" @@ -4116,6 +4117,11 @@ int main(int argc, char *argv[]) ast_aoc_cli_init(); ast_uuid_init(); + if (ast_sorcery_init()) { + printf("%s", term_quit()); + exit(1); + } + ast_makesocket(); sigemptyset(&sigs); sigaddset(&sigs, SIGHUP); diff --git a/main/sorcery.c b/main/sorcery.c new file mode 100644 index 000000000..a347094cf --- /dev/null +++ b/main/sorcery.c @@ -0,0 +1,965 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012 - 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Sorcery Data Access Layer API + * + * \author Joshua Colp <jcolp@digium.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/logger.h" +#include "asterisk/sorcery.h" +#include "asterisk/astobj2.h" +#include "asterisk/strings.h" +#include "asterisk/config_options.h" +#include "asterisk/netsock2.h" +#include "asterisk/module.h" + +/*! \brief Number of buckets for wizards (should be prime for performance reasons) */ +#define WIZARD_BUCKETS 7 + +/*! \brief Number of buckets for types (should be prime for performance reasons) */ +#define TYPE_BUCKETS 53 + +/*! \brief Maximum length of an object field name */ +#define MAX_OBJECT_FIELD 128 + +/*! \brief Structure for registered object type */ +struct ast_sorcery_object_type { + /*! \brief Unique name of the object type */ + char name[MAX_OBJECT_TYPE]; + + /*! \brief Optional transformation callback */ + sorcery_transform_handler transform; + + /*! \brief Optional object set apply callback */ + sorcery_apply_handler apply; + + /*! \brief Wizard instances */ + struct ao2_container *wizards; + + /*! \brief Object fields */ + struct ao2_container *fields; + + /*! \brief Configuration framework general information */ + struct aco_info *info; + + /*! \brief Configuration framework file information */ + struct aco_file *file; + + /*! \brief Type details */ + struct aco_type type; +}; + +/*! \brief Structure for registered object field */ +struct ast_sorcery_object_field { + /*! \brief Name of the field */ + char name[MAX_OBJECT_FIELD]; + + /*! \brief Callback function for translation */ + sorcery_field_handler handler; + + /*! \brief Position of the field */ + intptr_t args[]; +}; + +/*! \brief Structure for a wizard instance which operates on objects */ +struct ast_sorcery_object_wizard { + /*! \brief Wizard interface itself */ + struct ast_sorcery_wizard *wizard; + + /*! \brief Unique data for the wizard */ + void *data; + + /*! \brief Wizard is acting as an object cache */ + unsigned int caching:1; +}; + +/*! \brief Full structure for sorcery */ +struct ast_sorcery { + /*! \brief Container for known object types */ + struct ao2_container *types; +}; + +/*! \brief Structure for passing load/reload details */ +struct sorcery_load_details { + /*! \brief Sorcery structure in use */ + const struct ast_sorcery *sorcery; + + /*! \brief Type of object being loaded */ + const char *type; + + /*! \brief Whether this is a reload or not */ + unsigned int reload:1; +}; + +/*! \brief Registered sorcery wizards */ +struct ao2_container *wizards; + +static int int_handler_fn(const void *obj, const intptr_t *args, char **buf) +{ + int *field = (int *)(obj + args[0]); + return (ast_asprintf(buf, "%d", *field) < 0) ? -1 : 0; +} + +static int uint_handler_fn(const void *obj, const intptr_t *args, char **buf) +{ + unsigned int *field = (unsigned int *)(obj + args[0]); + return (ast_asprintf(buf, "%u", *field) < 0) ? -1 : 0; +} + +static int double_handler_fn(const void *obj, const intptr_t *args, char **buf) +{ + double *field = (double *)(obj + args[0]); + return (ast_asprintf(buf, "%f", *field) < 0) ? -1 : 0; +} + +static int stringfield_handler_fn(const void *obj, const intptr_t *args, char **buf) +{ + ast_string_field *field = (const char **)(obj + args[0]); + return !(*buf = ast_strdup(*field)) ? -1 : 0; +} + +static int bool_handler_fn(const void *obj, const intptr_t *args, char **buf) +{ + unsigned int *field = (unsigned int *)(obj + args[0]); + return !(*buf = ast_strdup(*field ? "true" : "false")) ? -1 : 0; +} + +static int sockaddr_handler_fn(const void *obj, const intptr_t *args, char **buf) +{ + struct ast_sockaddr *field = (struct ast_sockaddr *)(obj + args[0]); + return !(*buf = ast_strdup(ast_sockaddr_stringify(field))) ? -1 : 0; +} + +static int noop_handler_fn(const void *obj, const intptr_t *args, char **buf) +{ + return 0; +} + +static int chararray_handler_fn(const void *obj, const intptr_t *args, char **buf) +{ + char *field = (char *)(obj + args[0]); + return !(*buf = ast_strdup(field)) ? -1 : 0; +} + +static sorcery_field_handler sorcery_field_default_handler(enum aco_option_type type) +{ + switch(type) { + case OPT_BOOL_T: return bool_handler_fn; + case OPT_CHAR_ARRAY_T: return chararray_handler_fn; + case OPT_DOUBLE_T: return double_handler_fn; + case OPT_INT_T: return int_handler_fn; + case OPT_NOOP_T: return noop_handler_fn; + case OPT_SOCKADDR_T: return sockaddr_handler_fn; + case OPT_STRINGFIELD_T: return stringfield_handler_fn; + case OPT_UINT_T: return uint_handler_fn; + + default: + case OPT_CUSTOM_T: return NULL; + } + + return NULL; +} + +/*! \brief Hashing function for sorcery wizards */ +static int sorcery_wizard_hash(const void *obj, const int flags) +{ + const struct ast_sorcery_wizard *wizard = obj; + const char *name = obj; + + return ast_str_hash(flags & OBJ_KEY ? name : wizard->name); +} + +/*! \brief Comparator function for sorcery wizards */ +static int sorcery_wizard_cmp(void *obj, void *arg, int flags) +{ + struct ast_sorcery_wizard *wizard1 = obj, *wizard2 = arg; + const char *name = arg; + + return !strcmp(wizard1->name, flags & OBJ_KEY ? name : wizard2->name) ? CMP_MATCH | CMP_STOP : 0; +} + +int ast_sorcery_init(void) +{ + ast_assert(wizards == NULL); + + if (!(wizards = ao2_container_alloc(WIZARD_BUCKETS, sorcery_wizard_hash, sorcery_wizard_cmp))) { + return -1; + } + + return 0; +} + +int __ast_sorcery_wizard_register(const struct ast_sorcery_wizard *interface, struct ast_module *module) +{ + struct ast_sorcery_wizard *wizard; + int res = -1; + + ast_assert(!ast_strlen_zero(interface->name)); + + ao2_lock(wizards); + + if ((wizard = ao2_find(wizards, interface->name, OBJ_KEY | OBJ_NOLOCK))) { + ast_log(LOG_WARNING, "Attempted to register sorcery wizard '%s' twice\n", + interface->name); + goto done; + } + + if (!(wizard = ao2_alloc(sizeof(*wizard), NULL))) { + goto done; + } + + *wizard = *interface; + wizard->module = module; + + ao2_link_flags(wizards, wizard, OBJ_NOLOCK); + res = 0; + + ast_verb(2, "Sorcery registered wizard '%s'\n", interface->name); + +done: + ao2_cleanup(wizard); + ao2_unlock(wizards); + + return res; +} + +int ast_sorcery_wizard_unregister(const struct ast_sorcery_wizard *interface) +{ + RAII_VAR(struct ast_sorcery_wizard *, wizard, ao2_find(wizards, interface->name, OBJ_KEY | OBJ_UNLINK), ao2_cleanup); + + if (wizard) { + ast_verb(2, "Sorcery unregistered wizard '%s'\n", interface->name); + return 0; + } else { + return -1; + } +} + +/*! \brief Destructor called when sorcery structure is destroyed */ +static void sorcery_destructor(void *obj) +{ + struct ast_sorcery *sorcery = obj; + + ao2_cleanup(sorcery->types); +} + +/*! \brief Hashing function for sorcery types */ +static int sorcery_type_hash(const void *obj, const int flags) +{ + const struct ast_sorcery_object_type *type = obj; + const char *name = obj; + + return ast_str_hash(flags & OBJ_KEY ? name : type->name); +} + +/*! \brief Comparator function for sorcery types */ +static int sorcery_type_cmp(void *obj, void *arg, int flags) +{ + struct ast_sorcery_object_type *type1 = obj, *type2 = arg; + const char *name = arg; + + return !strcmp(type1->name, flags & OBJ_KEY ? name : type2->name) ? CMP_MATCH | CMP_STOP : 0; +} + +struct ast_sorcery *ast_sorcery_open(void) +{ + struct ast_sorcery *sorcery; + + if (!(sorcery = ao2_alloc(sizeof(*sorcery), sorcery_destructor))) { + return NULL; + } + + if (!(sorcery->types = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, TYPE_BUCKETS, sorcery_type_hash, sorcery_type_cmp))) { + ao2_ref(sorcery, -1); + sorcery = NULL; + } + + return sorcery; +} + +/*! \brief Destructor function for object types */ +static void sorcery_object_type_destructor(void *obj) +{ + struct ast_sorcery_object_type *object_type = obj; + + ao2_cleanup(object_type->wizards); + ao2_cleanup(object_type->fields); + + if (object_type->info) { + aco_info_destroy(object_type->info); + ast_free(object_type->info); + } + + ast_free(object_type->file); +} + +/*! \brief Internal function which allocates an object type structure */ +static struct ast_sorcery_object_type *sorcery_object_type_alloc(const char *type) +{ + struct ast_sorcery_object_type *object_type; + + if (!(object_type = ao2_alloc(sizeof(*object_type), sorcery_object_type_destructor))) { + return NULL; + } + + /* Order matters for object wizards */ + if (!(object_type->wizards = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1, NULL, NULL))) { + ao2_ref(object_type, -1); + return NULL; + } + + if (!(object_type->fields = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 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; + } + + object_type->info->files[0] = object_type->file; + object_type->info->files[1] = NULL; + + ast_copy_string(object_type->name, type, sizeof(object_type->name)); + + return object_type; +} + +/*! \brief Object wizard destructor */ +static void sorcery_object_wizard_destructor(void *obj) +{ + struct ast_sorcery_object_wizard *object_wizard = obj; + + if (object_wizard->data) { + object_wizard->wizard->close(object_wizard->data); + } + + if (object_wizard->wizard) { + ast_module_unref(object_wizard->wizard->module); + } +} + +/*! \brief Internal function which creates an object type and adds a wizard mapping */ +static int sorcery_apply_wizard_mapping(struct ast_sorcery *sorcery, const char *type, const char *name, const char *data, unsigned int caching) +{ + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup); + RAII_VAR(struct ast_sorcery_wizard *, wizard, ao2_find(wizards, name, OBJ_KEY), ao2_cleanup); + RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, ao2_alloc(sizeof(*object_wizard), sorcery_object_wizard_destructor), ao2_cleanup); + int created = 0; + + if (!wizard || !object_wizard) { + return -1; + } + + if (!object_type) { + if (!(object_type = sorcery_object_type_alloc(type))) { + return -1; + } + created = 1; + } + + if (wizard->open && !(object_wizard->data = wizard->open(data))) { + return -1; + } + + ast_module_ref(wizard->module); + + object_wizard->wizard = wizard; + object_wizard->caching = caching; + + ao2_link(object_type->wizards, object_wizard); + + if (created) { + ao2_link(sorcery->types, object_type); + } + + return 0; +} + +int ast_sorcery_apply_config(struct ast_sorcery *sorcery, const char *name) +{ + struct ast_flags flags = { 0 }; + struct ast_config *config = ast_config_load2("sorcery.conf", "sorcery", flags); + struct ast_variable *mapping; + int res = 0; + + if (!config || (config == CONFIG_STATUS_FILEMISSING) || (config == CONFIG_STATUS_FILEINVALID)) { + return -1; + } + + for (mapping = ast_variable_browse(config, name); mapping; mapping = mapping->next) { + RAII_VAR(char *, mapping_name, ast_strdup(mapping->name), ast_free); + RAII_VAR(char *, mapping_value, ast_strdup(mapping->value), ast_free); + char *options = mapping_name, *name = strsep(&options, "/"); + char *data = mapping_value, *wizard = strsep(&data, ","); + unsigned int caching = 0; + + /* If no wizard exists just skip, nothing we can do */ + if (ast_strlen_zero(wizard)) { + continue; + } + + /* If the wizard is configured as a cache treat it as such */ + if (!ast_strlen_zero(options) && strstr(options, "cache")) { + caching = 1; + } + + /* Any error immediately causes us to stop */ + if ((res = sorcery_apply_wizard_mapping(sorcery, name, wizard, data, caching))) { + break; + } + } + + ast_config_destroy(config); + + return res; +} + +int ast_sorcery_apply_default(struct ast_sorcery *sorcery, const char *type, const char *name, const char *data) +{ + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup); + + /* Defaults can not be added if any existing mapping exists */ + if (object_type) { + return -1; + } + + return sorcery_apply_wizard_mapping(sorcery, type, name, data, 0); +} + +int ast_sorcery_object_register(struct ast_sorcery *sorcery, const char *type, aco_type_item_alloc alloc, sorcery_transform_handler transform, sorcery_apply_handler apply) +{ + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup); + + if (!object_type) { + return -1; + } + + object_type->type.type = ACO_ITEM; + object_type->type.category = ""; + object_type->type.item_alloc = alloc; + + object_type->transform = transform; + object_type->file->types[0] = &object_type->type; + object_type->file->types[1] = NULL; + + if (aco_info_init(object_type->info)) { + return -1; + } + + return 0; +} + +int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char *type, const char *name, const char *default_val, enum aco_option_type opt_type, + aco_option_handler config_handler, sorcery_field_handler sorcery_handler, unsigned int flags, size_t argc, ...) +{ + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup); + RAII_VAR(struct ast_sorcery_object_field *, object_field, NULL, ao2_cleanup); + int pos; + va_list args; + + if (!object_type || !object_type->type.item_alloc) { + return -1; + } + + if (!sorcery_handler) { + sorcery_handler = sorcery_field_default_handler(opt_type); + } + + if (!(object_field = ao2_alloc(sizeof(*object_field) + argc * sizeof(object_field->args[0]), NULL))) { + return -1; + } + + ast_copy_string(object_field->name, name, sizeof(object_field->name)); + object_field->handler = sorcery_handler; + + va_start(args, argc); + for (pos = 0; pos < argc; pos++) { + object_field->args[pos] = va_arg(args, size_t); + } + va_end(args); + + ao2_link(object_type->fields, object_field); + + /* TODO: Improve this hack */ + if (!argc) { + __aco_option_register(object_type->info, name, ACO_EXACT, object_type->file->types, default_val, opt_type, config_handler, flags, argc); + } else if (argc == 1) { + __aco_option_register(object_type->info, name, ACO_EXACT, object_type->file->types, default_val, opt_type, config_handler, flags, argc, + object_field->args[0]); + } else if (argc == 2) { + __aco_option_register(object_type->info, name, ACO_EXACT, object_type->file->types, default_val, opt_type, config_handler, flags, argc, + object_field->args[0], object_field->args[1]); + } else if (argc == 3) { + __aco_option_register(object_type->info, name, ACO_EXACT, object_type->file->types, default_val, opt_type, config_handler, flags, argc, + object_field->args[0], object_field->args[1], object_field->args[2]); + } else { + ast_assert(0); /* The hack... she does us no good for this */ + } + + return 0; +} + +static int sorcery_wizard_load(void *obj, void *arg, int flags) +{ + struct ast_sorcery_object_wizard *wizard = obj; + struct sorcery_load_details *details = arg; + void (*load)(void *data, const struct ast_sorcery *sorcery, const char *type); + + load = !details->reload ? wizard->wizard->load : wizard->wizard->reload; + + if (load) { + load(wizard->data, details->sorcery, details->type); + } + + return 0; +} + +static int sorcery_object_load(void *obj, void *arg, int flags) +{ + struct ast_sorcery_object_type *type = obj; + struct sorcery_load_details *details = arg; + + details->type = type->name; + ao2_callback(type->wizards, OBJ_NODATA, sorcery_wizard_load, details); + + return 0; +} + +void ast_sorcery_load(const struct ast_sorcery *sorcery) +{ + struct sorcery_load_details details = { + .sorcery = sorcery, + .reload = 0, + }; + + ao2_callback(sorcery->types, OBJ_NODATA, sorcery_object_load, &details); +} + +void ast_sorcery_reload(const struct ast_sorcery *sorcery) +{ + struct sorcery_load_details details = { + .sorcery = sorcery, + .reload = 1, + }; + + ao2_callback(sorcery->types, OBJ_NODATA, sorcery_object_load, &details); +} + +void ast_sorcery_ref(struct ast_sorcery *sorcery) +{ + ao2_ref(sorcery, +1); +} + +struct ast_variable *ast_sorcery_objectset_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_variable *fields = NULL; + int res = 0; + + if (!object_type) { + return NULL; + } + + i = ao2_iterator_init(object_type->fields, 0); + + for (; (object_field = ao2_iterator_next(&i)); ao2_ref(object_field, -1)) { + RAII_VAR(char *, buf, NULL, ast_free); + struct ast_variable *tmp; + + /* Any fields with no handler just get skipped */ + if (!object_field->handler) { + continue; + } + + if ((res = object_field->handler(object, object_field->args, &buf)) || + !(tmp = ast_variable_new(object_field->name, S_OR(buf, ""), ""))) { + res = -1; + ao2_ref(object_field, -1); + break; + } + + tmp->next = fields; + fields = tmp; + } + + ao2_iterator_destroy(&i); + + /* If any error occurs we destroy all fields handled before so a partial objectset is not returned */ + if (res) { + ast_variables_destroy(fields); + fields = NULL; + } + + return fields; +} + +int ast_sorcery_objectset_apply(const struct ast_sorcery *sorcery, void *object, struct ast_variable *objectset) +{ + 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); + RAII_VAR(struct ast_variable *, transformed, NULL, ast_variables_destroy); + struct ast_variable *field; + int res = 0; + + if (!object_type) { + return -1; + } + + if (object_type->transform && (transformed = object_type->transform(objectset))) { + field = transformed; + } else { + field = objectset; + } + + for (; field; field = field->next) { + if ((res = aco_process_var(&object_type->type, details->id, field, object))) { + break; + } + } + + if (!res && object_type->apply) { + object_type->apply(sorcery, object); + } + + return res; +} + +static const struct ast_variable *sorcery_find_field(const struct ast_variable *fields, const char *name) +{ + const struct ast_variable *field; + + /* Search the linked list of fields to find the correct one */ + for (field = fields; field; field = field->next) { + if (!strcmp(field->name, name)) { + return field; + } + } + + return NULL; +} + +int ast_sorcery_changeset_create(const struct ast_variable *original, const struct ast_variable *modified, struct ast_variable **changes) +{ + const struct ast_variable *field; + int res = 0; + + *changes = NULL; + + /* Unless the ast_variable list changes when examined... it can't differ from itself */ + if (original == modified) { + return 0; + } + + for (field = modified; field; field = field->next) { + const struct ast_variable *old = sorcery_find_field(original, field->name); + + if (!old || strcmp(old->value, field->value)) { + struct ast_variable *tmp; + + if (!(tmp = ast_variable_new(field->name, field->value, ""))) { + res = -1; + break; + } + + tmp->next = *changes; + *changes = tmp; + } + } + + /* If an error occurred do not return a partial changeset */ + if (res) { + ast_variables_destroy(*changes); + *changes = NULL; + } + + return res; +} + +void *ast_sorcery_alloc(const struct ast_sorcery *sorcery, const char *type, const char *id) +{ + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup); + struct ast_sorcery_object_details *details; + + if (!object_type || !object_type->type.item_alloc || + !(details = object_type->type.item_alloc(""))) { + return NULL; + } + + if (ast_strlen_zero(id)) { + struct ast_uuid *uuid = ast_uuid_generate(); + + if (!uuid) { + ao2_ref(details, -1); + return NULL; + } + + ast_uuid_to_str(uuid, details->id, AST_UUID_STR_LEN); + ast_free(uuid); + } else { + ast_copy_string(details->id, id, sizeof(details->id)); + } + + ast_copy_string(details->type, type, sizeof(details->type)); + + if (aco_set_defaults(&object_type->type, id, details)) { + ao2_ref(details, -1); + return NULL; + } + + return details; +} + +void *ast_sorcery_copy(const struct ast_sorcery *sorcery, const void *object) +{ + const struct ast_sorcery_object_details *details = object; + struct ast_sorcery_object_details *copy = ast_sorcery_alloc(sorcery, details->type, details->id); + RAII_VAR(struct ast_variable *, objectset, NULL, ast_variables_destroy); + + if (!copy || + !(objectset = ast_sorcery_objectset_create(sorcery, object)) || + ast_sorcery_objectset_apply(sorcery, copy, objectset)) { + ao2_cleanup(copy); + return NULL; + } + + return copy; +} + +int ast_sorcery_diff(const struct ast_sorcery *sorcery, const void *original, const void *modified, struct ast_variable **changes) +{ + RAII_VAR(struct ast_variable *, objectset1, NULL, ast_variables_destroy); + RAII_VAR(struct ast_variable *, objectset2, NULL, ast_variables_destroy); + + *changes = NULL; + + if (strcmp(ast_sorcery_object_get_type(original), ast_sorcery_object_get_type(modified))) { + return -1; + } + + objectset1 = ast_sorcery_objectset_create(sorcery, original); + objectset2 = ast_sorcery_objectset_create(sorcery, modified); + + return ast_sorcery_changeset_create(objectset1, objectset2, changes); +} + +/*! \brief Internal function used to create an object in caching wizards */ +static int sorcery_cache_create(void *obj, void *arg, int flags) +{ + struct ast_sorcery_object_wizard *object_wizard = obj; + + if (!object_wizard->caching || !object_wizard->wizard->create) { + return 0; + } + + object_wizard->wizard->create(object_wizard->data, arg); + + return 0; +} + +void *ast_sorcery_retrieve_by_id(const struct ast_sorcery *sorcery, const char *type, const char *id) +{ + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup); + void *object = NULL; + struct ao2_iterator i; + struct ast_sorcery_object_wizard *wizard; + unsigned int cached = 0; + + if (!object_type || ast_strlen_zero(id)) { + return NULL; + } + + i = ao2_iterator_init(object_type->wizards, 0); + for (; (wizard = ao2_iterator_next(&i)); ao2_ref(wizard, -1)) { + if (wizard->wizard->retrieve_id && + !(object = wizard->wizard->retrieve_id(sorcery, wizard->data, object_type->name, id))) { + continue; + } + + cached = wizard->caching; + + ao2_ref(wizard, -1); + break; + } + ao2_iterator_destroy(&i); + + if (!cached && object) { + ao2_callback(object_type->wizards, 0, sorcery_cache_create, object); + } + + return object; +} + +void *ast_sorcery_retrieve_by_fields(const struct ast_sorcery *sorcery, const char *type, unsigned int flags, struct ast_variable *fields) +{ + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup); + void *object = NULL; + struct ao2_iterator i; + struct ast_sorcery_object_wizard *wizard; + unsigned int cached = 0; + + if (!object_type) { + return NULL; + } + + /* If returning multiple objects create a container to store them in */ + if ((flags & AST_RETRIEVE_FLAG_MULTIPLE)) { + if (!(object = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1, NULL, NULL))) { + return NULL; + } + } + + /* Inquire with the available wizards for retrieval */ + i = ao2_iterator_init(object_type->wizards, 0); + for (; (wizard = ao2_iterator_next(&i)); ao2_ref(wizard, -1)) { + if ((flags & AST_RETRIEVE_FLAG_MULTIPLE)) { + if (wizard->wizard->retrieve_multiple) { + wizard->wizard->retrieve_multiple(sorcery, wizard->data, object_type->name, object, fields); + } + } else if (fields && wizard->wizard->retrieve_fields) { + if (wizard->wizard->retrieve_fields) { + object = wizard->wizard->retrieve_fields(sorcery, wizard->data, object_type->name, fields); + } + } + + if ((flags & AST_RETRIEVE_FLAG_MULTIPLE) || !object) { + continue; + } + + cached = wizard->caching; + + ao2_ref(wizard, -1); + break; + } + ao2_iterator_destroy(&i); + + /* If we are returning a single object and it came from a non-cache source create it in any caches */ + if (!(flags & AST_RETRIEVE_FLAG_MULTIPLE) && !cached && object) { + ao2_callback(object_type->wizards, 0, sorcery_cache_create, object); + } + + return object; +} + +/*! \brief Internal function which returns if the wizard has created the object */ +static int sorcery_wizard_create(void *obj, void *arg, int flags) +{ + const struct ast_sorcery_object_wizard *object_wizard = obj; + + return (!object_wizard->caching && !object_wizard->wizard->create(object_wizard->data, arg)) ? CMP_MATCH | CMP_STOP : 0; +} + +int ast_sorcery_create(const struct ast_sorcery *sorcery, 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); + RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, NULL, ao2_cleanup); + + if (!object_type) { + return -1; + } + + object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_create, object); + + return object_wizard ? 0 : -1; +} + +/*! \brief Internal function which returns if a wizard has updated the object */ +static int sorcery_wizard_update(void *obj, void *arg, int flags) +{ + const struct ast_sorcery_object_wizard *object_wizard = obj; + + return (object_wizard->wizard->update && !object_wizard->wizard->update(object_wizard->data, arg) && + !object_wizard->caching) ? CMP_MATCH | CMP_STOP : 0; +} + +int ast_sorcery_update(const struct ast_sorcery *sorcery, 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); + RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, NULL, ao2_cleanup); + + if (!object_type) { + return -1; + } + + object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_update, object); + + return object_wizard ? 0 : -1; +} + +/*! \brief Internal function which returns if a wizard has deleted the object */ +static int sorcery_wizard_delete(void *obj, void *arg, int flags) +{ + const struct ast_sorcery_object_wizard *object_wizard = obj; + + return (object_wizard->wizard->delete && !object_wizard->wizard->delete(object_wizard->data, arg) && + !object_wizard->caching) ? CMP_MATCH | CMP_STOP : 0; +} + +int ast_sorcery_delete(const struct ast_sorcery *sorcery, 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); + RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, NULL, ao2_cleanup); + + if (!object_type) { + return -1; + } + + object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_delete, object); + + return object_wizard ? 0 : -1; +} + +void ast_sorcery_unref(struct ast_sorcery *sorcery) +{ + ao2_cleanup(sorcery); +} + +const char *ast_sorcery_object_get_id(const void *object) +{ + const struct ast_sorcery_object_details *details = object; + return details->id; +} + +const char *ast_sorcery_object_get_type(const void *object) +{ + const struct ast_sorcery_object_details *details = object; + return details->type; +} diff --git a/res/res_sorcery_config.c b/res/res_sorcery_config.c new file mode 100644 index 000000000..9fe636d68 --- /dev/null +++ b/res/res_sorcery_config.c @@ -0,0 +1,346 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012 - 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * + * \brief Sorcery Configuration File Object Wizard + * + * \author Joshua Colp <jcolp@digium.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/sorcery.h" +#include "asterisk/astobj2.h" +#include "asterisk/config.h" + +/*! \brief Default number of buckets for sorcery objects */ +#define DEFAULT_OBJECT_BUCKETS 53 + +/*! \brief Structure for storing configuration file sourced objects */ +struct sorcery_config { + /*! \brief Objects retrieved from the configuration file */ + struct ao2_global_obj objects; + + /*! \brief Any specific variable criteria for considering a defined category for this object */ + struct ast_variable *criteria; + + /*! \brief Number of buckets to use for objects */ + unsigned int buckets; + + /*! \brief Enable file level integrity instead of object level */ + unsigned int file_integrity:1; + + /*! \brief Filename of the configuration file */ + char filename[]; +}; + +/*! \brief Structure used for fields comparison */ +struct sorcery_config_fields_cmp_params { + /*! \brief Pointer to the sorcery structure */ + const struct ast_sorcery *sorcery; + + /*! \brief Pointer to the fields to check */ + const struct ast_variable *fields; + + /*! \brief Optional container to put object into */ + struct ao2_container *container; +}; + +static void *sorcery_config_open(const char *data); +static void sorcery_config_load(void *data, const struct ast_sorcery *sorcery, const char *type); +static void sorcery_config_reload(void *data, const struct ast_sorcery *sorcery, const char *type); +static void *sorcery_config_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id); +static void *sorcery_config_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type, const struct ast_variable *fields); +static void sorcery_config_retrieve_multiple(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, + const struct ast_variable *fields); +static void sorcery_config_close(void *data); + +static struct ast_sorcery_wizard config_object_wizard = { + .name = "config", + .open = sorcery_config_open, + .load = sorcery_config_load, + .reload = sorcery_config_reload, + .retrieve_id = sorcery_config_retrieve_id, + .retrieve_fields = sorcery_config_retrieve_fields, + .retrieve_multiple = sorcery_config_retrieve_multiple, + .close = sorcery_config_close, +}; + +/*! \brief Destructor function for sorcery config */ +static void sorcery_config_destructor(void *obj) +{ + struct sorcery_config *config = obj; + + ao2_global_obj_release(config->objects); + ast_rwlock_destroy(&config->objects.lock); + ast_variables_destroy(config->criteria); +} + +/*! \brief Hashing function for sorcery objects */ +static int sorcery_config_hash(const void *obj, const int flags) +{ + const char *id = obj; + + return ast_str_hash(flags & OBJ_KEY ? id : ast_sorcery_object_get_id(obj)); +} + +/*! \brief Comparator function for sorcery objects */ +static int sorcery_config_cmp(void *obj, void *arg, int flags) +{ + const char *id = arg; + + return !strcmp(ast_sorcery_object_get_id(obj), flags & OBJ_KEY ? id : ast_sorcery_object_get_id(arg)) ? CMP_MATCH | CMP_STOP : 0; +} + +static int sorcery_config_fields_cmp(void *obj, void *arg, int flags) +{ + const struct sorcery_config_fields_cmp_params *params = arg; + RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy); + RAII_VAR(struct ast_variable *, diff, NULL, ast_variables_destroy); + + /* If we can't turn the object into an object set OR if differences exist between the fields + * passed in and what are present on the object they are not a match. + */ + if (params->fields && + (!(objset = ast_sorcery_objectset_create(params->sorcery, obj)) || + (ast_sorcery_changeset_create(objset, params->fields, &diff)) || + diff)) { + return 0; + } + + if (params->container) { + ao2_link(params->container, obj); + + /* As multiple objects are being returned keep going */ + return 0; + } else { + /* Immediately stop and return, we only want a single object */ + return CMP_MATCH | CMP_STOP; + } +} + +static void *sorcery_config_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type, const struct ast_variable *fields) +{ + struct sorcery_config *config = data; + RAII_VAR(struct ao2_container *, objects, ao2_global_obj_ref(config->objects), ao2_cleanup); + struct sorcery_config_fields_cmp_params params = { + .sorcery = sorcery, + .fields = fields, + .container = NULL, + }; + + /* If no fields are present return nothing, we require *something*, same goes if no objects exist yet */ + if (!objects || !fields) { + return NULL; + } + + return ao2_callback(objects, 0, sorcery_config_fields_cmp, ¶ms); +} + +static void *sorcery_config_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id) +{ + struct sorcery_config *config = data; + RAII_VAR(struct ao2_container *, objects, ao2_global_obj_ref(config->objects), ao2_cleanup); + + return objects ? ao2_find(objects, id, OBJ_KEY | OBJ_NOLOCK) : NULL; +} + +static void sorcery_config_retrieve_multiple(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const struct ast_variable *fields) +{ + struct sorcery_config *config = data; + RAII_VAR(struct ao2_container *, config_objects, ao2_global_obj_ref(config->objects), ao2_cleanup); + struct sorcery_config_fields_cmp_params params = { + .sorcery = sorcery, + .fields = fields, + .container = objects, + }; + + if (!config_objects) { + return; + } + + ao2_callback(config_objects, 0, sorcery_config_fields_cmp, ¶ms); +} + +/*! \brief Internal function which determines if criteria has been met for considering an object set applicable */ +static int sorcery_is_criteria_met(struct ast_variable *objset, struct ast_variable *criteria) +{ + RAII_VAR(struct ast_variable *, diff, NULL, ast_variables_destroy); + + return (!criteria || (!ast_sorcery_changeset_create(objset, criteria, &diff) && !diff)) ? 1 : 0; +} + +static void sorcery_config_internal_load(void *data, const struct ast_sorcery *sorcery, const char *type, unsigned int reload) +{ + struct sorcery_config *config = data; + struct ast_flags flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + struct ast_config *cfg = ast_config_load2(config->filename, "res_sorcery_config", flags); + RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup); + const char *id = NULL; + + if (!cfg) { + ast_log(LOG_ERROR, "Unable to load config file '%s'n", config->filename); + return; + } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) { + ast_debug(1, "Config file '%s' was unchanged\n", config->filename); + return; + } else if (cfg == CONFIG_STATUS_FILEINVALID) { + ast_log(LOG_ERROR, "Contents of config file '%s' are invalid and cannot be parsed\n", config->filename); + return; + } + + if (!(objects = ao2_container_alloc(config->buckets, sorcery_config_hash, sorcery_config_cmp))) { + ast_log(LOG_ERROR, "Could not create bucket for new objects from '%s', keeping existing objects\n", + config->filename); + ast_config_destroy(cfg); + return; + } + + while ((id = ast_category_browse(cfg, id))) { + RAII_VAR(void *, obj, NULL, ao2_cleanup); + + /* If given criteria has not been met skip the category, it is not applicable */ + if (!sorcery_is_criteria_met(ast_variable_browse(cfg, id), config->criteria)) { + continue; + } + + if (!(obj = ast_sorcery_alloc(sorcery, type, id)) || + ast_sorcery_objectset_apply(sorcery, obj, ast_variable_browse(cfg, id))) { + ast_debug(1, "Could not create an object of type '%s' with id '%s' from configuration file '%s'\n", + type, id, config->filename); + + if (config->file_integrity) { + ast_log(LOG_ERROR, "Config file '%s' could not be loaded due to error with object '%s' of type '%s'\n", + config->filename, id, type); + ast_config_destroy(cfg); + return; + } + + ao2_cleanup(obj); + + /* To ensure we don't lose the object that already exists we retrieve it from the old objects container and add it to the new one */ + if (!(obj = sorcery_config_retrieve_id(sorcery, data, type, id))) { + continue; + } + } + + ao2_link_flags(objects, obj, OBJ_NOLOCK); + } + + ao2_global_obj_replace_unref(config->objects, objects); + ast_config_destroy(cfg); +} + +static void sorcery_config_load(void *data, const struct ast_sorcery *sorcery, const char *type) +{ + sorcery_config_internal_load(data, sorcery, type, 0); +} + +static void sorcery_config_reload(void *data, const struct ast_sorcery *sorcery, const char *type) +{ + sorcery_config_internal_load(data, sorcery, type, 1); +} + +static void *sorcery_config_open(const char *data) +{ + char *tmp = ast_strdupa(data), *filename = strsep(&tmp, ","), *option; + struct sorcery_config *config; + + if (ast_strlen_zero(filename) || !(config = ao2_alloc_options(sizeof(*config) + strlen(filename) + 1, sorcery_config_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK))) { + return NULL; + } + + ast_rwlock_init(&config->objects.lock); + config->buckets = DEFAULT_OBJECT_BUCKETS; + strcpy(config->filename, filename); + + while ((option = strsep(&tmp, ","))) { + char *name = strsep(&option, "="), *value = option; + + if (!strcasecmp(name, "buckets")) { + if (sscanf(value, "%30d", &config->buckets) != 1) { + ast_log(LOG_ERROR, "Unsupported bucket size of '%s' used for configuration file '%s', defaulting to '%d'\n", + value, filename, DEFAULT_OBJECT_BUCKETS); + } + } else if (!strcasecmp(name, "integrity")) { + if (!strcasecmp(value, "file")) { + config->file_integrity = 1; + } else if (!strcasecmp(value, "object")) { + config->file_integrity = 0; + } else { + ast_log(LOG_ERROR, "Unsupported integrity value of '%s' used for configuration file '%s', defaulting to 'object'\n", + value, filename); + } + } else if (!strcasecmp(name, "criteria")) { + char *field = strsep(&value, "="); + struct ast_variable *criteria = ast_variable_new(field, value, ""); + + if (criteria) { + criteria->next = config->criteria; + config->criteria = criteria; + } else { + /* This is fatal since not following criteria would potentially yield invalid objects */ + ast_log(LOG_ERROR, "Could not create criteria entry of field '%s' with value '%s' for configuration file '%s'\n", + field, value, filename); + ao2_ref(config, -1); + return NULL; + } + } else { + ast_log(LOG_ERROR, "Unsupported option '%s' used for configuration file '%s'\n", name, filename); + } + } + + return config; +} + +static void sorcery_config_close(void *data) +{ + struct sorcery_config *config = data; + + ao2_ref(config, -1); +} + +static int load_module(void) +{ + if (ast_sorcery_wizard_register(&config_object_wizard)) { + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sorcery_wizard_unregister(&config_object_wizard); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Sorcery Configuration File Object Wizard", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DEPEND, +); diff --git a/res/res_sorcery_memory.c b/res/res_sorcery_memory.c new file mode 100644 index 000000000..cc8ff7fcd --- /dev/null +++ b/res/res_sorcery_memory.c @@ -0,0 +1,211 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012 - 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * + * \brief Sorcery In-Memory Object Wizard + * + * \author Joshua Colp <jcolp@digium.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/sorcery.h" +#include "asterisk/astobj2.h" + +/*! \brief Number of buckets for sorcery objects */ +#define OBJECT_BUCKETS 53 + +static void *sorcery_memory_open(const char *data); +static int sorcery_memory_create(void *data, void *object); +static void *sorcery_memory_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id); +static void *sorcery_memory_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type, const struct ast_variable *fields); +static void sorcery_memory_retrieve_multiple(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, + const struct ast_variable *fields); +static int sorcery_memory_update(void *data, void *object); +static int sorcery_memory_delete(void *data, void *object); +static void sorcery_memory_close(void *data); + +static struct ast_sorcery_wizard memory_object_wizard = { + .name = "memory", + .open = sorcery_memory_open, + .create = sorcery_memory_create, + .retrieve_id = sorcery_memory_retrieve_id, + .retrieve_fields = sorcery_memory_retrieve_fields, + .retrieve_multiple = sorcery_memory_retrieve_multiple, + .update = sorcery_memory_update, + .delete = sorcery_memory_delete, + .close = sorcery_memory_close, +}; + +/*! \brief Structure used for fields comparison */ +struct sorcery_memory_fields_cmp_params { + /*! \brief Pointer to the sorcery structure */ + const struct ast_sorcery *sorcery; + + /*! \brief Pointer to the fields to check */ + const struct ast_variable *fields; + + /*! \brief Optional container to put object into */ + struct ao2_container *container; +}; + +/*! \brief Hashing function for sorcery objects */ +static int sorcery_memory_hash(const void *obj, const int flags) +{ + const char *id = obj; + + return ast_str_hash(flags & OBJ_KEY ? id : ast_sorcery_object_get_id(obj)); +} + +/*! \brief Comparator function for sorcery objects */ +static int sorcery_memory_cmp(void *obj, void *arg, int flags) +{ + const char *id = arg; + + return !strcmp(ast_sorcery_object_get_id(obj), flags & OBJ_KEY ? id : ast_sorcery_object_get_id(arg)) ? CMP_MATCH | CMP_STOP : 0; +} + +static int sorcery_memory_create(void *data, void *object) +{ + ao2_link(data, object); + return 0; +} + +static int sorcery_memory_fields_cmp(void *obj, void *arg, int flags) +{ + const struct sorcery_memory_fields_cmp_params *params = arg; + RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy); + RAII_VAR(struct ast_variable *, diff, NULL, ast_variables_destroy); + + /* If we can't turn the object into an object set OR if differences exist between the fields + * passed in and what are present on the object they are not a match. + */ + if (params->fields && + (!(objset = ast_sorcery_objectset_create(params->sorcery, obj)) || + (ast_sorcery_changeset_create(objset, params->fields, &diff)) || + diff)) { + return 0; + } + + if (params->container) { + ao2_link(params->container, obj); + + /* As multiple objects are being returned keep going */ + return 0; + } else { + /* Immediately stop and return, we only want a single object */ + return CMP_MATCH | CMP_STOP; + } +} + +static void *sorcery_memory_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type, const struct ast_variable *fields) +{ + struct sorcery_memory_fields_cmp_params params = { + .sorcery = sorcery, + .fields = fields, + .container = NULL, + }; + + /* If no fields are present return nothing, we require *something* */ + if (!fields) { + return NULL; + } + + return ao2_callback(data, 0, sorcery_memory_fields_cmp, ¶ms); +} + +static void *sorcery_memory_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id) +{ + return ao2_find(data, id, OBJ_KEY); +} + +static void sorcery_memory_retrieve_multiple(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const struct ast_variable *fields) +{ + struct sorcery_memory_fields_cmp_params params = { + .sorcery = sorcery, + .fields = fields, + .container = objects, + }; + + ao2_callback(data, 0, sorcery_memory_fields_cmp, ¶ms); +} + +static int sorcery_memory_update(void *data, void *object) +{ + RAII_VAR(void *, existing, NULL, ao2_cleanup); + + ao2_lock(data); + + if (!(existing = ao2_find(data, ast_sorcery_object_get_id(object), OBJ_KEY | OBJ_UNLINK))) { + ao2_unlock(data); + return -1; + } + + ao2_link(data, object); + + ao2_unlock(data); + + return 0; +} + +static int sorcery_memory_delete(void *data, void *object) +{ + RAII_VAR(void *, existing, ao2_find(data, ast_sorcery_object_get_id(object), OBJ_KEY | OBJ_UNLINK), ao2_cleanup); + + return existing ? 0 : -1; +} + +static void *sorcery_memory_open(const char *data) +{ + return ao2_container_alloc(OBJECT_BUCKETS, sorcery_memory_hash, sorcery_memory_cmp); +} + +static void sorcery_memory_close(void *data) +{ + ao2_ref(data, -1); +} + +static int load_module(void) +{ + if (ast_sorcery_wizard_register(&memory_object_wizard)) { + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sorcery_wizard_unregister(&memory_object_wizard); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Sorcery In-Memory Object Wizard", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DEPEND, +); diff --git a/tests/test_sorcery.c b/tests/test_sorcery.c new file mode 100644 index 000000000..662210253 --- /dev/null +++ b/tests/test_sorcery.c @@ -0,0 +1,1950 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012 - 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief Sorcery Unit Tests + * + * \author Joshua Colp <jcolp@digium.com> + * + */ + +/*** MODULEINFO + <depend>TEST_FRAMEWORK</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "") + +#include "asterisk/test.h" +#include "asterisk/module.h" +#include "asterisk/sorcery.h" +#include "asterisk/logger.h" + +/*! \brief Dummy sorcery object */ +struct test_sorcery_object { + SORCERY_OBJECT(details); + unsigned int bob; + unsigned int joe; +}; + +/*! \brief Internal function to allocate a test object */ +static void *test_sorcery_object_alloc(const char *id) +{ + return ao2_alloc(sizeof(struct test_sorcery_object), NULL); +} + +/*! \brief Internal function for object set transformation */ +static struct ast_variable *test_sorcery_transform(struct ast_variable *set) +{ + struct ast_variable *field, *transformed = NULL; + + for (field = set; field; field = field->next) { + struct ast_variable *transformed_field; + + if (!strcmp(field->name, "joe")) { + transformed_field = ast_variable_new(field->name, "5000", ""); + } else { + transformed_field = ast_variable_new(field->name, field->value, ""); + } + + if (!transformed_field) { + ast_variables_destroy(transformed); + return NULL; + } + + transformed_field->next = transformed; + transformed = transformed_field; + } + + return transformed; +} + +/*! \brief Test structure for caching */ +struct sorcery_test_caching { + /*! \brief Whether the object has been created in the cache or not */ + unsigned int created:1; + + /*! \brief Whether the object has been updated in the cache or not */ + unsigned int updated:1; + + /*! \brief Whether the object has been deleted from the cache or not */ + unsigned int deleted:1; + + /*! \brief Object to return when asked */ + struct test_sorcery_object object; +}; + +/*! \brief Global scope caching structure for testing */ +static struct sorcery_test_caching cache = { 0, }; + +static int sorcery_test_create(void *data, void *object) +{ + cache.created = 1; + cache.updated = 0; + cache.deleted = 0; + return 0; +} + +static void *sorcery_test_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id) +{ + return (cache.created && !cache.deleted) ? ast_sorcery_alloc(sorcery, type, id) : NULL; +} + +static int sorcery_test_update(void *data, void *object) +{ + cache.updated = 1; + return 0; +} + +static int sorcery_test_delete(void *data, void *object) +{ + cache.deleted = 1; + return 0; +} + +/*! \brief Dummy sorcery wizard, not actually used so we only populate the name and nothing else */ +static struct ast_sorcery_wizard test_wizard = { + .name = "test", + .create = sorcery_test_create, + .retrieve_id = sorcery_test_retrieve_id, + .update = sorcery_test_update, + .delete = sorcery_test_delete, +}; + +static struct ast_sorcery *alloc_and_initialize_sorcery(void) +{ + struct ast_sorcery *sorcery; + + if (!(sorcery = ast_sorcery_open())) { + return NULL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "memory", NULL) || + ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) { + ast_sorcery_unref(sorcery); + return NULL; + } + + ast_sorcery_object_field_register(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob)); + ast_sorcery_object_field_register(sorcery, "test", "joe", "10", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, joe)); + + return sorcery; +} + +AST_TEST_DEFINE(wizard_registration) +{ + switch (cmd) { + case TEST_INIT: + info->name = "wizard_registration"; + info->category = "/main/sorcery/"; + info->summary = "sorcery wizard registration and unregistration unit test"; + info->description = + "Test registration and unregistration of a sorcery wizard"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (ast_sorcery_wizard_register(&test_wizard)) { + ast_test_status_update(test, "Failed to register a perfectly valid sorcery wizard\n"); + return AST_TEST_FAIL; + } + + if (!ast_sorcery_wizard_register(&test_wizard)) { + ast_test_status_update(test, "Successfully registered a sorcery wizard twice, which is bad\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_wizard_unregister(&test_wizard)) { + ast_test_status_update(test, "Failed to unregister a perfectly valid sorcery wizard\n"); + return AST_TEST_FAIL; + } + + if (!ast_sorcery_wizard_unregister(&test_wizard)) { + ast_test_status_update(test, "Successfully unregistered a sorcery wizard twice, which is bad\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(open) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "open"; + info->category = "/main/sorcery/"; + info->summary = "sorcery open unit test"; + info->description = + "Test opening of sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open new sorcery structure\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(apply_default) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "apply_default"; + info->category = "/main/sorcery/"; + info->summary = "sorcery default wizard unit test"; + info->description = + "Test setting default type wizard in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!ast_sorcery_apply_default(sorcery, "test", "dummy", NULL)) { + ast_test_status_update(test, "Successfully set a default wizard that doesn't exist\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "memory", NULL)) { + ast_test_status_update(test, "Failed to set a known wizard as a default\n"); + return AST_TEST_FAIL; + } + + if (!ast_sorcery_apply_default(sorcery, "test", "memory", NULL)) { + ast_test_status_update(test, "Successfully set a default wizard on a type twice\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(apply_config) +{ + struct ast_flags flags = { CONFIG_FLAG_NOCACHE }; + struct ast_config *config; + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "apply_config"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object mapping configuration unit test"; + info->description = + "Test configured object mapping in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(config = ast_config_load2("sorcery.conf", "test_sorcery", flags))) { + ast_test_status_update(test, "Sorcery configuration file not present - skipping apply_config test\n"); + return AST_TEST_NOT_RUN; + } + + if (!ast_category_get(config, "test_sorcery")) { + ast_test_status_update(test, "Sorcery configuration file does not have test_sorcery section\n"); + ast_config_destroy(config); + return AST_TEST_NOT_RUN; + } + + ast_config_destroy(config); + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_config(sorcery, "test_sorcery")) { + ast_test_status_update(test, "Failed to apply configured object mappings\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_register) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "object_register"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object type registration unit test"; + info->description = + "Test object type registration in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open structure\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "memory", NULL)) { + ast_test_status_update(test, "Failed to set a known wizard as a default\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) { + ast_test_status_update(test, "Failed to register object type\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_register_without_mapping) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "object_register_without_mapping"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object type registration (without mapping) unit test"; + info->description = + "Test object type registration when no mapping exists in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) { + ast_test_status_update(test, "Registered object type when no object mapping exists\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_field_register) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "object_field_register"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object field registration unit test"; + info->description = + "Test object field registration in sorcery with a provided id"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!ast_sorcery_object_field_register(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob))) { + ast_test_status_update(test, "Registered an object field successfully when no mappings or object types exist\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "memory", NULL)) { + ast_test_status_update(test, "Failed to set a known wizard as a default\n"); + return AST_TEST_FAIL; + } + + if (!ast_sorcery_object_field_register(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob))) { + ast_test_status_update(test, "Registered an object field successfully when object type does not exist\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) { + ast_test_status_update(test, "Failed to register object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_object_field_register(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob))) { + ast_test_status_update(test, "Could not successfully register object field when mapping and object type exists\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_alloc_with_id) +{ + 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); + + switch (cmd) { + case TEST_INIT: + info->name = "object_alloc_with_id"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object allocation (with id) unit test"; + info->description = + "Test object allocation in sorcery with a provided id"; + 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"); + res = AST_TEST_FAIL; + } else if (ast_strlen_zero(ast_sorcery_object_get_id(obj))) { + ast_test_status_update(test, "Allocated object has empty id when it should not\n"); + res = AST_TEST_FAIL; + } else if (strcmp(ast_sorcery_object_get_id(obj), "blah")) { + ast_test_status_update(test, "Allocated object does not have correct id\n"); + res = AST_TEST_FAIL; + } else if (ast_strlen_zero(ast_sorcery_object_get_type(obj))) { + ast_test_status_update(test, "Allocated object has empty type when it should not\n"); + res = AST_TEST_FAIL; + } else if (strcmp(ast_sorcery_object_get_type(obj), "test")) { + ast_test_status_update(test, "Allocated object does not have correct type\n"); + res = AST_TEST_FAIL; + } else if ((obj->bob != 5) || (obj->joe != 10)) { + ast_test_status_update(test, "Allocated object does not have defaults set as it should\n"); + res = AST_TEST_FAIL; + } + + return res; +} + +AST_TEST_DEFINE(object_alloc_without_id) +{ + 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); + + switch (cmd) { + case TEST_INIT: + info->name = "object_alloc_without_id"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object allocation (without id) unit test"; + info->description = + "Test object allocation in sorcery with no provided id"; + 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", NULL))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + res = AST_TEST_FAIL; + } else if (ast_strlen_zero(ast_sorcery_object_get_id(obj))) { + ast_test_status_update(test, "Allocated object has empty id when it should not\n"); + res = AST_TEST_FAIL; + } + + return res; +} + + +AST_TEST_DEFINE(object_copy) +{ + 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 test_sorcery_object *, copy, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_copy"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object copy unit test"; + info->description = + "Test object copy 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; + } + + obj->bob = 50; + obj->joe = 100; + + if (!(copy = ast_sorcery_copy(sorcery, obj))) { + ast_test_status_update(test, "Failed to create a copy of a known valid object\n"); + res = AST_TEST_FAIL; + } else if (copy == obj) { + ast_test_status_update(test, "Created copy is actually the original object\n"); + res = AST_TEST_FAIL; + } else if (copy->bob != obj->bob) { + ast_test_status_update(test, "Value of 'bob' on newly created copy is not the same as original\n"); + res = AST_TEST_FAIL; + } else if (copy->joe != obj->joe) { + ast_test_status_update(test, "Value of 'joe' on newly created copy is not the same as original\n"); + res = AST_TEST_FAIL; + } + + return res; +} + +AST_TEST_DEFINE(object_diff) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj1, NULL, ao2_cleanup); + RAII_VAR(struct test_sorcery_object *, obj2, NULL, ao2_cleanup); + RAII_VAR(struct ast_variable *, changes, NULL, ast_variables_destroy); + struct ast_variable *field; + int res = AST_TEST_PASS; + + switch (cmd) { + case TEST_INIT: + info->name = "object_diff"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object diff unit test"; + info->description = + "Test object diffing 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 (!(obj1 = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + obj1->bob = 99; + obj1->joe = 55; + + if (!(obj2 = ast_sorcery_alloc(sorcery, "test", "blah2"))) { + ast_test_status_update(test, "Failed to allocate a second known object type\n"); + return AST_TEST_FAIL; + } + + obj2->bob = 99; + obj2->joe = 42; + + if (ast_sorcery_diff(sorcery, obj1, obj2, &changes)) { + ast_test_status_update(test, "Failed to diff obj1 and obj2\n"); + } else if (!changes) { + ast_test_status_update(test, "Failed to produce a diff of two objects, despite there being differences\n"); + return AST_TEST_FAIL; + } + + for (field = changes; field; field = field->next) { + if (!strcmp(field->name, "joe")) { + if (strcmp(field->value, "42")) { + ast_test_status_update(test, "Object diff produced unexpected value '%s' for joe\n", field->value); + res = AST_TEST_FAIL; + } + } else { + ast_test_status_update(test, "Object diff produced unexpected field '%s'\n", field->name); + res = AST_TEST_FAIL; + } + } + + return res; +} + +AST_TEST_DEFINE(objectset_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_variable *, objset, NULL, ast_variables_destroy); + struct ast_variable *field; + + switch (cmd) { + case TEST_INIT: + info->name = "objectset_create"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object set creation unit test"; + info->description = + "Test object set creation 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_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 = objset; field; field = field->next) { + if (!strcmp(field->name, "bob")) { + if (strcmp(field->value, "5")) { + ast_test_status_update(test, "Object set failed to create proper value for 'bob'\n"); + res = AST_TEST_FAIL; + } + } else if (!strcmp(field->name, "joe")) { + if (strcmp(field->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", field->name); + res = AST_TEST_FAIL; + } + } + + return res; +} + +AST_TEST_DEFINE(objectset_apply) +{ + 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_variable *, objset, NULL, ast_variables_destroy); + + switch (cmd) { + case TEST_INIT: + info->name = "objectset_apply"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object apply unit test"; + info->description = + "Test object set applying 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_variable_new("joe", "25", ""))) { + ast_test_status_update(test, "Failed to create an object set, test could not occur\n"); + res = AST_TEST_FAIL; + } else if (ast_sorcery_objectset_apply(sorcery, obj, objset)) { + ast_test_status_update(test, "Failed to apply valid object set to object\n"); + res = AST_TEST_FAIL; + } else if (obj->joe != 25) { + ast_test_status_update(test, "Object set was not actually applied to object despite it returning success\n"); + res = AST_TEST_FAIL; + } + + return res; +} + +AST_TEST_DEFINE(objectset_apply_invalid) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy); + + switch (cmd) { + case TEST_INIT: + info->name = "objectset_apply_invalid"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object invalid apply unit test"; + info->description = + "Test object set applying of an invalid set 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_variable_new("fred", "99", ""))) { + ast_test_status_update(test, "Failed to create an object set, test could not occur\n"); + return AST_TEST_FAIL; + } else if (!ast_sorcery_objectset_apply(sorcery, obj, objset)) { + ast_test_status_update(test, "Successfully applied an invalid object set\n"); + return AST_TEST_FAIL; + } else if ((obj->bob != 5) || (obj->joe != 10)) { + ast_test_status_update(test, "Object set modified object fields when it should not have\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(objectset_transform) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy); + + switch (cmd) { + case TEST_INIT: + info->name = "objectset_transform"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object set transformation unit test"; + info->description = + "Test object set transformation in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "memory", NULL)) { + ast_test_status_update(test, "Failed to set a known wizard as a default\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, test_sorcery_transform, NULL)) { + ast_test_status_update(test, "Failed to register object type\n"); + return AST_TEST_FAIL; + } + + ast_sorcery_object_field_register(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob)); + ast_sorcery_object_field_register(sorcery, "test", "joe", "10", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, joe)); + + 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_create(sorcery, obj))) { + ast_test_status_update(test, "Failed to create an object set for a known sane object\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_objectset_apply(sorcery, obj, objset)) { + ast_test_status_update(test, "Failed to apply properly created object set against object\n"); + return AST_TEST_FAIL; + } + + if (obj->bob != 5) { + ast_test_status_update(test, "Application of object set produced incorrect value on 'bob'\n"); + return AST_TEST_FAIL; + } else if (obj->joe == 10) { + ast_test_status_update(test, "Transformation callback did not change value of 'joe' from provided value\n"); + return AST_TEST_FAIL; + } else if (obj->joe != 5000) { + ast_test_status_update(test, "Value of 'joe' differs from default AND from transformation value\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(changeset_create) +{ + int res = AST_TEST_PASS; + RAII_VAR(struct ast_variable *, original, NULL, ast_variables_destroy); + RAII_VAR(struct ast_variable *, modified, NULL, ast_variables_destroy); + RAII_VAR(struct ast_variable *, changes, NULL, ast_variables_destroy); + struct ast_variable *tmp; + + switch (cmd) { + case TEST_INIT: + info->name = "changeset_create"; + info->category = "/main/sorcery/"; + info->summary = "sorcery changeset creation unit test"; + info->description = + "Test changeset creation in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(tmp = ast_variable_new("bananas", "purple", ""))) { + ast_test_status_update(test, "Failed to create first field for original objectset\n"); + return AST_TEST_FAIL; + } + tmp->next = original; + original = tmp; + + if (!(tmp = ast_variable_new("apples", "orange", ""))) { + ast_test_status_update(test, "Failed to create second field for original objectset\n"); + return AST_TEST_FAIL; + } + tmp->next = original; + original = tmp; + + if (!(tmp = ast_variable_new("bananas", "green", ""))) { + ast_test_status_update(test, "Failed to create first field for modified objectset\n"); + return AST_TEST_FAIL; + } + tmp->next = modified; + modified = tmp; + + if (!(tmp = ast_variable_new("apples", "orange", ""))) { + ast_test_status_update(test, "Failed to create second field for modified objectset\n"); + return AST_TEST_FAIL; + } + tmp->next = modified; + modified = tmp; + + if (ast_sorcery_changeset_create(original, modified, &changes)) { + ast_test_status_update(test, "Failed to create a changeset due to an error\n"); + return AST_TEST_FAIL; + } else if (!changes) { + ast_test_status_update(test, "Failed to produce a changeset when there should be one\n"); + return AST_TEST_FAIL; + } + + for (tmp = changes; tmp; tmp = tmp->next) { + if (!strcmp(tmp->name, "bananas")) { + if (strcmp(tmp->value, "green")) { + ast_test_status_update(test, "Changeset produced had unexpected value '%s' for bananas\n", tmp->value); + res = AST_TEST_FAIL; + } + } else { + ast_test_status_update(test, "Changeset produced had unexpected field '%s'\n", tmp->name); + res = AST_TEST_FAIL; + } + } + + return res; +} + +AST_TEST_DEFINE(changeset_create_unchanged) +{ + RAII_VAR(struct ast_variable *, original, NULL, ast_variables_destroy); + RAII_VAR(struct ast_variable *, changes, NULL, ast_variables_destroy); + RAII_VAR(struct ast_variable *, same, NULL, ast_variables_destroy); + struct ast_variable *tmp; + + switch (cmd) { + case TEST_INIT: + info->name = "changeset_create_unchanged"; + info->category = "/main/sorcery/"; + info->summary = "sorcery changeset creation unit test when no changes exist"; + info->description = + "Test changeset creation in sorcery when no changes actually exist"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(tmp = ast_variable_new("bananas", "purple", ""))) { + ast_test_status_update(test, "Failed to create first field for original objectset\n"); + return AST_TEST_FAIL; + } + tmp->next = original; + original = tmp; + + if (!(tmp = ast_variable_new("apples", "orange", ""))) { + ast_test_status_update(test, "Failed to create second field for original objectset\n"); + return AST_TEST_FAIL; + } + tmp->next = original; + original = tmp; + + if (ast_sorcery_changeset_create(original, original, &changes)) { + ast_test_status_update(test, "Failed to create a changeset due to an error\n"); + return AST_TEST_FAIL; + } else if (changes) { + ast_test_status_update(test, "Created a changeset when no changes actually exist\n"); + return AST_TEST_FAIL; + } + + if (!(tmp = ast_variable_new("bananas", "purple", ""))) { + ast_test_status_update(test, "Failed to create first field for same objectset\n"); + return AST_TEST_FAIL; + } + tmp->next = same; + same = tmp; + + if (!(tmp = ast_variable_new("apples", "orange", ""))) { + ast_test_status_update(test, "Failed to create second field for same objectset\n"); + return AST_TEST_FAIL; + } + tmp->next = same; + same = tmp; + + if (ast_sorcery_changeset_create(original, same, &changes)) { + ast_test_status_update(test, "Failed to create a changeset due to an error\n"); + return AST_TEST_FAIL; + } else if (changes) { + ast_test_status_update(test, "Created a changeset between two different objectsets when no changes actually exist\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_create) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_create"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object creation unit test"; + info->description = + "Test object creation 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 (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using in-memory wizard\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_retrieve_id) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_retrieve_id"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object retrieval using id unit test"; + info->description = + "Test object retrieval using id 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 (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using in-memory wizard\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah2"))) { + ast_test_status_update(test, "Failed to allocate second instance of a known object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create second object using in-memory wizard\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + if (!(obj = ast_sorcery_retrieve_by_id(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to retrieve properly created object using id of 'blah'\n"); + return AST_TEST_FAIL; + } else if (strcmp(ast_sorcery_object_get_id(obj), "blah")) { + ast_test_status_update(test, "Retrieved object does not have correct id\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_retrieve_field) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct ast_variable *, fields, ast_variable_new("joe", "42", ""), ast_variables_destroy); + + switch (cmd) { + case TEST_INIT: + info->name = "object_retrieve_field"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object retrieval using a specific field unit test"; + info->description = + "Test object retrieval using a specific field in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!fields) { + ast_test_status_update(test, "Failed to create fields for object retrieval attempt\n"); + return AST_TEST_FAIL; + } + + 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; + } + + obj->joe = 42; + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using in-memory wizard\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + if (!(obj = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_DEFAULT, fields))) { + ast_test_status_update(test, "Failed to retrieve properly created object using 'joe' field\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + ast_variables_destroy(fields); + + if (!(fields = ast_variable_new("joe", "49", ""))) { + ast_test_status_update(test, "Failed to create fields for object retrieval attempt\n"); + return AST_TEST_FAIL; + } + + if ((obj = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_DEFAULT, fields))) { + ast_test_status_update(test, "Retrieved an object using a field with an in-correct value... that should not happen\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_retrieve_multiple_all) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_retrieve_multiple_all"; + info->category = "/main/sorcery/"; + info->summary = "sorcery multiple object retrieval unit test"; + info->description = + "Test multiple object retrieval 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 (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using in-memory wizard\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah2"))) { + ast_test_status_update(test, "Failed to allocate second instance of a known object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create second object using in-memory wizard\n"); + return AST_TEST_FAIL; + } + + if (!(objects = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL))) { + ast_test_status_update(test, "Failed to retrieve a container of all objects\n"); + return AST_TEST_FAIL; + } else if (ao2_container_count(objects) != 2) { + ast_test_status_update(test, "Received a container with no objects in it when there should be some\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_retrieve_multiple_field) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup); + RAII_VAR(struct ast_variable *, fields, ast_variable_new("joe", "6", ""), ast_variables_destroy); + + switch (cmd) { + case TEST_INIT: + info->name = "object_retrieve_multiple_field"; + info->category = "/main/sorcery/"; + info->summary = "sorcery multiple object retrieval unit test"; + info->description = + "Test multiple object retrieval in sorcery using fields"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!fields) { + ast_test_status_update(test, "Failed to create fields for multiple retrieve\n"); + return AST_TEST_FAIL; + } + + 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; + } + + obj->joe = 6; + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using in-memory wizard\n"); + return AST_TEST_FAIL; + } + + if (!(objects = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_MULTIPLE, fields))) { + ast_test_status_update(test, "Failed to retrieve a container of all objects\n"); + return AST_TEST_FAIL; + } else if (ao2_container_count(objects) != 1) { + ast_test_status_update(test, "Received a container with no objects in it when there should be some\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(objects); + ast_variables_destroy(fields); + + if (!(fields = ast_variable_new("joe", "7", ""))) { + ast_test_status_update(test, "Failed to create fields for multiple retrieval\n"); + return AST_TEST_FAIL; + } else if (!(objects = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_MULTIPLE, fields))) { + ast_test_status_update(test, "Failed to retrieve an empty container when retrieving multiple\n"); + return AST_TEST_FAIL; + } else if (ao2_container_count(objects)) { + ast_test_status_update(test, "Received a container with objects when there should be none in it\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_update) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct test_sorcery_object *, obj2, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_update"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object update unit test"; + info->description = + "Test object updating 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 (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using in-memory wizard\n"); + return AST_TEST_FAIL; + } + + if (!(obj2 = ast_sorcery_copy(sorcery, obj))) { + ast_test_status_update(test, "Failed to allocate a known object type for updating\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + if (ast_sorcery_update(sorcery, obj2)) { + ast_test_status_update(test, "Failed to update sorcery with new object\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_retrieve_by_id(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to retrieve properly updated object\n"); + return AST_TEST_FAIL; + } else if (obj != obj2) { + ast_test_status_update(test, "Object retrieved is not the updated object\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_update_uncreated) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_update_uncreated"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object update unit test"; + info->description = + "Test updating of an uncreated object 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 (!ast_sorcery_update(sorcery, obj)) { + ast_test_status_update(test, "Successfully updated an object which has not been created yet\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_delete) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_delete"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object deletion unit test"; + info->description = + "Test object deletion 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 (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using in-memory wizard\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_delete(sorcery, obj)) { + ast_test_status_update(test, "Failed to delete object using in-memory wizard\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + if ((obj = ast_sorcery_retrieve_by_id(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Retrieved deleted object that should not be there\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_delete_uncreated) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_delete_uncreated"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object deletion unit test"; + info->description = + "Test object deletion of an uncreated object 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 (!ast_sorcery_delete(sorcery, obj)) { + ast_test_status_update(test, "Successfully deleted an object which was never created\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(caching_wizard_behavior) +{ + struct ast_flags flags = { CONFIG_FLAG_NOCACHE }; + struct ast_config *config; + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct test_sorcery_object *, obj2, NULL, ao2_cleanup); + int res = AST_TEST_FAIL; + + switch (cmd) { + case TEST_INIT: + info->name = "caching_wizard_behavior"; + info->category = "/main/sorcery/"; + info->summary = "sorcery caching wizard behavior unit test"; + info->description = + "Test internal behavior of caching wizards"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(config = ast_config_load2("sorcery.conf", "test_sorcery_cache", flags))) { + ast_test_status_update(test, "Sorcery configuration file not present - skipping caching_wizard_behavior test\n"); + return AST_TEST_NOT_RUN; + } + + if (!ast_category_get(config, "test_sorcery_cache")) { + ast_test_status_update(test, "Sorcery configuration file does not contain 'test_sorcery_cache' section\n"); + ast_config_destroy(config); + return AST_TEST_NOT_RUN; + } + + ast_config_destroy(config); + + if (ast_sorcery_wizard_register(&test_wizard)) { + ast_test_status_update(test, "Failed to register a perfectly valid sorcery wizard\n"); + return AST_TEST_FAIL; + } + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + goto end; + } + + if (ast_sorcery_apply_config(sorcery, "test_sorcery_cache")) { + ast_test_status_update(test, "Failed to apply configured object mappings\n"); + goto end; + } + + if (ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) { + ast_test_status_update(test, "Failed to register object type\n"); + goto end; + } + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + goto end; + } + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using in-memory wizard\n"); + goto end; + } + + ao2_cleanup(obj); + + if (!(obj = ast_sorcery_retrieve_by_id(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to retrieve just created object\n"); + goto end; + } else if (!cache.created) { + ast_test_status_update(test, "Caching wizard was not told to cache just created object\n"); + goto end; + } else if (!(obj2 = ast_sorcery_retrieve_by_id(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to retrieve just cached object\n"); + goto end; + } else if (obj == obj2) { + ast_test_status_update(test, "Returned object is *NOT* a cached object\n"); + goto end; + } else if (ast_sorcery_update(sorcery, obj)) { + ast_test_status_update(test, "Failed to update a known stored object\n"); + goto end; + } else if (!cache.updated) { + ast_test_status_update(test, "Caching wizard was not told to update object\n"); + goto end; + } else if (ast_sorcery_delete(sorcery, obj)) { + ast_test_status_update(test, "Failed to delete a known stored object\n"); + goto end; + } else if (!cache.deleted) { + ast_test_status_update(test, "Caching wizard was not told to delete object\n"); + goto end; + } + + ao2_cleanup(obj2); + + if ((obj2 = ast_sorcery_retrieve_by_id(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Retrieved an object that should have been deleted\n"); + goto end; + } + + res = AST_TEST_PASS; + +end: + ast_sorcery_unref(sorcery); + sorcery = NULL; + + if (ast_sorcery_wizard_unregister(&test_wizard)) { + ast_test_status_update(test, "Failed to unregister test sorcery wizard\n"); + return AST_TEST_FAIL; + } + + return res; +} + +AST_TEST_DEFINE(configuration_file_wizard) +{ + struct ast_flags flags = { CONFIG_FLAG_NOCACHE }; + struct ast_config *config; + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "configuration_file_wizard"; + info->category = "/main/sorcery/"; + info->summary = "sorcery configuration file wizard unit test"; + info->description = + "Test the configuration file wizard in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(config = ast_config_load2("test_sorcery.conf", "test_sorcery", flags))) { + ast_test_status_update(test, "Test sorcery configuration file wizard file not present - skipping configuration_file_wizard test\n"); + return AST_TEST_NOT_RUN; + } + + ast_config_destroy(config); + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "config", "test_sorcery.conf")) { + ast_test_status_update(test, "Could not set a default wizard of the 'config' type, so skipping since it may not be loaded\n"); + return AST_TEST_NOT_RUN; + } + + if (ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) { + ast_test_status_update(test, "Failed to register object type\n"); + return AST_TEST_FAIL; + } + + ast_sorcery_object_field_register(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob)); + ast_sorcery_object_field_register(sorcery, "test", "joe", "10", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, joe)); + + ast_sorcery_load(sorcery); + + if ((obj = ast_sorcery_retrieve_by_id(sorcery, "test", "hey2"))) { + ast_test_status_update(test, "Retrieved object which has an unknown field\n"); + return AST_TEST_FAIL; + } else if (!(obj = ast_sorcery_retrieve_by_id(sorcery, "test", "hey"))) { + ast_test_status_update(test, "Failed to retrieve a known object that has been configured in the configuration file\n"); + return AST_TEST_FAIL; + } else if (obj->bob != 98) { + ast_test_status_update(test, "Value of 'bob' on object is not what is configured in configuration file\n"); + return AST_TEST_FAIL; + } else if (obj->joe != 41) { + ast_test_status_update(test, "Value of 'joe' on object is not what is configured in configuration file\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(configuration_file_wizard_with_file_integrity) +{ + struct ast_flags flags = { CONFIG_FLAG_NOCACHE }; + struct ast_config *config; + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "configuration_file_wizard_with_file_integrity"; + info->category = "/main/sorcery/"; + info->summary = "sorcery configuration file wizard file integrity unit test"; + info->description = + "Test the configuration file wizard with file integrity turned on in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(config = ast_config_load2("test_sorcery.conf", "test_sorcery", flags))) { + ast_test_status_update(test, "Test sorcery configuration file wizard file not present - skipping configuration_file_wizard_with_file_integrity test\n"); + return AST_TEST_NOT_RUN; + } + + ast_config_destroy(config); + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "config", "test_sorcery.conf,integrity=file")) { + ast_test_status_update(test, "Could not set a default wizard of the 'config' type, so skipping since it may not be loaded\n"); + return AST_TEST_NOT_RUN; + } + + if (ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) { + ast_test_status_update(test, "Failed to register object type\n"); + return AST_TEST_FAIL; + } + + ast_sorcery_object_field_register(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob)); + ast_sorcery_object_field_register(sorcery, "test", "joe", "10", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, joe)); + + ast_sorcery_load(sorcery); + + if ((obj = ast_sorcery_retrieve_by_id(sorcery, "test", "hey"))) { + ast_test_status_update(test, "Retrieved object which has an unknown field\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(configuration_file_wizard_with_criteria) +{ + struct ast_flags flags = { CONFIG_FLAG_NOCACHE }; + struct ast_config *config; + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "configuration_file_wizard_with_criteria"; + info->category = "/main/sorcery/"; + info->summary = "sorcery configuration file wizard with criteria unit test"; + info->description = + "Test the configuration file wizard with criteria matching in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(config = ast_config_load2("test_sorcery.conf", "test_sorcery", flags))) { + ast_test_status_update(test, "Test sorcery configuration file wizard file not present - skipping configuration_file_wizard_with_criteria test\n"); + return AST_TEST_NOT_RUN; + } + + ast_config_destroy(config); + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "config", "test_sorcery.conf,criteria=type=zombies")) { + ast_test_status_update(test, "Could not set a default wizard of the 'config' type, so skipping since it may not be loaded\n"); + return AST_TEST_NOT_RUN; + } + + if (ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) { + ast_test_status_update(test, "Failed to register object type\n"); + return AST_TEST_FAIL; + } + + ast_sorcery_object_field_register(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob)); + ast_sorcery_object_field_register(sorcery, "test", "joe", "10", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, joe)); + ast_sorcery_object_field_register(sorcery, "test", "type", NULL, OPT_NOOP_T, 0, NULL); + + ast_sorcery_load(sorcery); + + if ((obj = ast_sorcery_retrieve_by_id(sorcery, "test", "hey"))) { + ast_test_status_update(test, "Retrieved object which did not match criteria\n"); + return AST_TEST_FAIL; + } else if (!(obj = ast_sorcery_retrieve_by_id(sorcery, "test", "hey2"))) { + ast_test_status_update(test, "Failed to retrieve a known object which matches criteria\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(configuration_file_wizard_retrieve_field) +{ + struct ast_flags flags = { CONFIG_FLAG_NOCACHE }; + struct ast_config *config; + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct ast_variable *, fields, ast_variable_new("joe", "41", ""), ast_variables_destroy); + + switch (cmd) { + case TEST_INIT: + info->name = "configuration_file_wizard_retrieve_field"; + info->category = "/main/sorcery/"; + info->summary = "sorcery configuration file wizard field retrieval unit test"; + info->description = + "Test the configuration file wizard retrieval using field in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(config = ast_config_load2("test_sorcery.conf", "test_sorcery", flags))) { + ast_test_status_update(test, "Test sorcery configuration file wizard file not present - skipping configuration_file_wizard_retrieve_field test\n"); + return AST_TEST_NOT_RUN; + } + + ast_config_destroy(config); + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "config", "test_sorcery.conf")) { + ast_test_status_update(test, "Could not set a default wizard of the 'config' type, so skipping since it may not be loaded\n"); + return AST_TEST_NOT_RUN; + } + + if (ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) { + ast_test_status_update(test, "Failed to register object type\n"); + return AST_TEST_FAIL; + } + + ast_sorcery_object_field_register(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob)); + ast_sorcery_object_field_register(sorcery, "test", "joe", "10", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, joe)); + + ast_sorcery_load(sorcery); + + if (!(obj = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_DEFAULT, fields))) { + ast_test_status_update(test, "Failed to retrieve a known object that has been configured with the correct field\n"); + return AST_TEST_FAIL; + } else if (strcmp(ast_sorcery_object_get_id(obj), "hey")) { + ast_test_status_update(test, "Retrieved object has incorrect object id of '%s'\n", ast_sorcery_object_get_id(obj)); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(configuration_file_wizard_retrieve_multiple) +{ + struct ast_flags flags = { CONFIG_FLAG_NOCACHE }; + struct ast_config *config; + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup); + RAII_VAR(struct ast_variable *, fields, ast_variable_new("joe", "99", ""), ast_variables_destroy); + + switch (cmd) { + case TEST_INIT: + info->name = "configuration_file_wizard_retrieve_multiple"; + info->category = "/main/sorcery/"; + info->summary = "sorcery configuration file wizard multiple retrieval unit test"; + info->description = + "Test the configuration file wizard multiple retrieval in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(config = ast_config_load2("test_sorcery.conf", "test_sorcery", flags))) { + ast_test_status_update(test, "Test sorcery configuration file wizard file not present - skipping configuration_file_wizard_retrieve_multiple test\n"); + return AST_TEST_NOT_RUN; + } + + ast_config_destroy(config); + + if (!fields) { + ast_test_status_update(test, "Failed to create fields for multiple retrieve\n"); + return AST_TEST_FAIL; + } + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "config", "test_sorcery.conf")) { + ast_test_status_update(test, "Could not set a default wizard of the 'config' type, so skipping since it may not be loaded\n"); + return AST_TEST_NOT_RUN; + } + + if (ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) { + ast_test_status_update(test, "Failed to register object type\n"); + return AST_TEST_FAIL; + } + + ast_sorcery_object_field_register(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob)); + ast_sorcery_object_field_register(sorcery, "test", "joe", "10", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, joe)); + + ast_sorcery_load(sorcery); + + if (!(objects = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_MULTIPLE, fields))) { + ast_test_status_update(test, "Failed to retrieve an empty container when retrieving multiple\n"); + return AST_TEST_FAIL; + } else if (ao2_container_count(objects)) { + ast_test_status_update(test, "Received a container with objects when there should be none in it\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(objects); + ast_variables_destroy(fields); + + if (!(fields = ast_variable_new("joe", "41", ""))) { + ast_test_status_update(test, "Failed to create fields for multiple retrieve\n"); + return AST_TEST_FAIL; + } else if (!(objects = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_MULTIPLE, fields))) { + ast_test_status_update(test, "Failed to retrieve a container when retrieving multiple\n"); + return AST_TEST_FAIL; + } else if (ao2_container_count(objects) != 1) { + ast_test_status_update(test, "Received a container with no objects in it when there should be\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(configuration_file_wizard_retrieve_multiple_all) +{ + struct ast_flags flags = { CONFIG_FLAG_NOCACHE }; + struct ast_config *config; + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "configuration_file_wizard_retrieve_multiple_all"; + info->category = "/main/sorcery/"; + info->summary = "sorcery configuration file wizard multiple retrieve all unit test"; + info->description = + "Test the configuration file wizard multiple retrieve all in sorcery"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(config = ast_config_load2("test_sorcery.conf", "test_sorcery", flags))) { + ast_test_status_update(test, "Test sorcery configuration file wizard file not present - skipping configuration_file_wizard_retrieve_multiple_all test\n"); + return AST_TEST_NOT_RUN; + } + + ast_config_destroy(config); + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "config", "test_sorcery.conf")) { + ast_test_status_update(test, "Could not set a default wizard of the 'config' type, so skipping since it may not be loaded\n"); + return AST_TEST_NOT_RUN; + } + + if (ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) { + ast_test_status_update(test, "Failed to register object type\n"); + return AST_TEST_FAIL; + } + + ast_sorcery_object_field_register(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob)); + ast_sorcery_object_field_register(sorcery, "test", "joe", "10", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, joe)); + + ast_sorcery_load(sorcery); + + if (!(objects = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL))) { + ast_test_status_update(test, "Failed to retrieve a container with all objects when there should be one\n"); + return AST_TEST_FAIL; + } else if (ao2_container_count(objects) != 2) { + ast_test_status_update(test, "Returned container does not have the correct number of objects in it\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(wizard_registration); + AST_TEST_UNREGISTER(open); + AST_TEST_UNREGISTER(apply_default); + AST_TEST_UNREGISTER(apply_config); + AST_TEST_UNREGISTER(object_register); + AST_TEST_UNREGISTER(object_register_without_mapping); + AST_TEST_UNREGISTER(object_field_register); + AST_TEST_UNREGISTER(object_alloc_with_id); + AST_TEST_UNREGISTER(object_alloc_without_id); + AST_TEST_UNREGISTER(object_copy); + AST_TEST_UNREGISTER(object_diff); + AST_TEST_UNREGISTER(objectset_create); + AST_TEST_UNREGISTER(objectset_apply); + AST_TEST_UNREGISTER(objectset_apply_invalid); + AST_TEST_UNREGISTER(objectset_transform); + AST_TEST_UNREGISTER(changeset_create); + AST_TEST_UNREGISTER(changeset_create_unchanged); + AST_TEST_UNREGISTER(object_create); + AST_TEST_UNREGISTER(object_retrieve_id); + AST_TEST_UNREGISTER(object_retrieve_field); + AST_TEST_UNREGISTER(object_retrieve_multiple_all); + AST_TEST_UNREGISTER(object_retrieve_multiple_field); + AST_TEST_UNREGISTER(object_update); + AST_TEST_UNREGISTER(object_update_uncreated); + AST_TEST_UNREGISTER(object_delete); + AST_TEST_UNREGISTER(object_delete_uncreated); + AST_TEST_UNREGISTER(caching_wizard_behavior); + AST_TEST_UNREGISTER(configuration_file_wizard); + AST_TEST_UNREGISTER(configuration_file_wizard_with_file_integrity); + AST_TEST_UNREGISTER(configuration_file_wizard_with_criteria); + AST_TEST_UNREGISTER(configuration_file_wizard_retrieve_field); + AST_TEST_UNREGISTER(configuration_file_wizard_retrieve_multiple); + AST_TEST_UNREGISTER(configuration_file_wizard_retrieve_multiple_all); + return 0; +} + +static int load_module(void) +{ + AST_TEST_REGISTER(wizard_registration); + AST_TEST_REGISTER(open); + AST_TEST_REGISTER(apply_default); + AST_TEST_REGISTER(apply_config); + AST_TEST_REGISTER(object_register); + AST_TEST_REGISTER(object_register_without_mapping); + AST_TEST_REGISTER(object_field_register); + AST_TEST_REGISTER(object_alloc_with_id); + AST_TEST_REGISTER(object_alloc_without_id); + AST_TEST_REGISTER(object_copy); + AST_TEST_REGISTER(object_diff); + AST_TEST_REGISTER(objectset_create); + AST_TEST_REGISTER(objectset_apply); + AST_TEST_REGISTER(objectset_apply_invalid); + AST_TEST_REGISTER(objectset_transform); + AST_TEST_REGISTER(changeset_create); + AST_TEST_REGISTER(changeset_create_unchanged); + AST_TEST_REGISTER(object_create); + AST_TEST_REGISTER(object_retrieve_id); + AST_TEST_REGISTER(object_retrieve_field); + AST_TEST_REGISTER(object_retrieve_multiple_all); + AST_TEST_REGISTER(object_retrieve_multiple_field); + AST_TEST_REGISTER(object_update); + AST_TEST_REGISTER(object_update_uncreated); + AST_TEST_REGISTER(object_delete); + AST_TEST_REGISTER(object_delete_uncreated); + AST_TEST_REGISTER(caching_wizard_behavior); + AST_TEST_REGISTER(configuration_file_wizard); + AST_TEST_REGISTER(configuration_file_wizard_with_file_integrity); + AST_TEST_REGISTER(configuration_file_wizard_with_criteria); + AST_TEST_REGISTER(configuration_file_wizard_retrieve_field); + AST_TEST_REGISTER(configuration_file_wizard_retrieve_multiple); + AST_TEST_REGISTER(configuration_file_wizard_retrieve_multiple_all); + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Sorcery test module"); |