diff options
-rw-r--r-- | include/asterisk/bucket.h | 69 | ||||
-rw-r--r-- | main/bucket.c | 88 | ||||
-rw-r--r-- | tests/test_bucket.c | 170 |
3 files changed, 327 insertions, 0 deletions
diff --git a/include/asterisk/bucket.h b/include/asterisk/bucket.h index da83759ce..c335fd351 100644 --- a/include/asterisk/bucket.h +++ b/include/asterisk/bucket.h @@ -218,6 +218,23 @@ struct ast_bucket *ast_bucket_alloc(const char *uri); int ast_bucket_create(struct ast_bucket *bucket); /*! + * \brief Clone a bucket + * + * This will create a copy of the passed in \c ast_bucket structure. While + * all properties of the \c ast_bucket structure are copied, any metadata + * in the original structure simply has its reference count increased. + * + * \param file The bucket to clone + * + * \retval non-NULL success + * \retval NULL failure + * + * \note This operation should be called prior to updating a bucket + * object, as \c ast_bucket instances are immutable + */ +struct ast_bucket *ast_bucket_clone(struct ast_bucket *bucket); + +/*! * \brief Delete a bucket from backend storage * * \param bucket The bucket @@ -240,6 +257,23 @@ int ast_bucket_delete(struct ast_bucket *bucket); struct ast_bucket *ast_bucket_retrieve(const char *uri); /*! + * \brief Retrieve whether or not the backing datastore views the bucket as stale + * \since 14.0.0 + * + * This function will ask whatever data storage backs the bucket's schema + * type if the current instance of the object is stale. It will not + * update the bucket object itself, as said objects are immutable. If the + * caller of this function would like to update the object, it should perform + * a retrieve operation. + * + * \param bucket The bucket object to check + * + * \retval 0 if \c bucket is not stale + * \retval 1 if \c bucket is stale + */ +int ast_bucket_is_stale(struct ast_bucket *bucket); + +/*! * \brief Add an observer for bucket creation and deletion operations * * \param callbacks Implementation of the sorcery observer interface @@ -309,6 +343,24 @@ int ast_bucket_file_create(struct ast_bucket_file *file); struct ast_bucket_file *ast_bucket_file_copy(struct ast_bucket_file *file, const char *uri); /*! + * \brief Clone a bucket file + * + * This will create a copy of the passed in \c ast_bucket_file structure. While + * all properties of the \c ast_bucket_file structure are copied, any metadata + * in the original structure simply has its reference count increased. Note that + * this copies the structure, not the underlying file. + * + * \param file The bucket file to clone + * + * \retval non-NULL success + * \retval NULL failure + * + * \note This operation should be called prior to updating a bucket file + * object, as \c ast_bucket_file instances are immutable + */ +struct ast_bucket_file *ast_bucket_file_clone(struct ast_bucket_file *file); + +/*! * \brief Update an existing bucket file in backend storage * * \param file The bucket file @@ -343,6 +395,23 @@ int ast_bucket_file_delete(struct ast_bucket_file *file); struct ast_bucket_file *ast_bucket_file_retrieve(const char *uri); /*! + * \brief Retrieve whether or not the backing datastore views the bucket file as stale + * \since 14.0.0 + * + * This function will ask whatever data storage backs the bucket file's schema + * type if the current instance of the object is stale. It will not + * update the bucket file object itself, as said objects are immutable. If the + * caller of this function would like to update the object, it should perform + * a retrieve operation. + * + * \param bucket_file The bucket file object to check + * + * \retval 0 if \c bucket_file is not stale + * \retval 1 if \c bucket_file is stale + */ +int ast_bucket_file_is_stale(struct ast_bucket_file *file); + +/*! * \brief Add an observer for bucket file creation and deletion operations * * \param callbacks Implementation of the sorcery observer interface diff --git a/main/bucket.c b/main/bucket.c index e93c66b19..afb017574 100644 --- a/main/bucket.c +++ b/main/bucket.c @@ -163,12 +163,25 @@ static int bucket_wizard_delete(const struct ast_sorcery *sorcery, void *data, v return bucket->scheme_impl->bucket->delete(sorcery, data, object); } +/*! \brief Callback function for determining if a bucket is stale */ +static int bucket_wizard_is_stale(const struct ast_sorcery *sorcery, void *data, void *object) +{ + struct ast_bucket *bucket = object; + + if (!bucket->scheme_impl->bucket->is_stale) { + return 0; + } + + return bucket->scheme_impl->bucket->is_stale(sorcery, data, object); +} + /*! \brief Intermediary bucket wizard */ static struct ast_sorcery_wizard bucket_wizard = { .name = "bucket", .create = bucket_wizard_create, .retrieve_id = bucket_wizard_retrieve, .delete = bucket_wizard_delete, + .is_stale = bucket_wizard_is_stale, }; /*! \brief Callback function for creating a bucket file */ @@ -240,6 +253,18 @@ static int bucket_file_wizard_delete(const struct ast_sorcery *sorcery, void *da return file->scheme_impl->file->delete(sorcery, data, object); } +/*! \brief Callback function for determining if a bucket is stale */ +static int bucket_file_wizard_is_stale(const struct ast_sorcery *sorcery, void *data, void *object) +{ + struct ast_bucket_file *file = object; + + if (!file->scheme_impl->file->is_stale) { + return 0; + } + + return file->scheme_impl->file->is_stale(sorcery, data, object); +} + /*! \brief Intermediary file wizard */ static struct ast_sorcery_wizard bucket_file_wizard = { .name = "bucket_file", @@ -247,6 +272,7 @@ static struct ast_sorcery_wizard bucket_file_wizard = { .retrieve_id = bucket_file_wizard_retrieve, .update = bucket_file_wizard_update, .delete = bucket_file_wizard_delete, + .is_stale = bucket_file_wizard_is_stale, }; int __ast_bucket_scheme_register(const char *name, struct ast_sorcery_wizard *bucket, @@ -459,6 +485,28 @@ int ast_bucket_create(struct ast_bucket *bucket) return ast_sorcery_create(bucket_sorcery, bucket); } +/*! + * \internal + * \brief Sorcery object type copy handler for \c ast_bucket + */ +static int bucket_copy_handler(const void *src, void *dst) +{ + const struct ast_bucket *src_bucket = src; + struct ast_bucket *dst_bucket = dst; + + dst_bucket->scheme_impl = ao2_bump(src_bucket->scheme_impl); + ast_string_field_set(dst_bucket, scheme, src_bucket->scheme); + dst_bucket->created = src_bucket->created; + dst_bucket->modified = src_bucket->modified; + + return 0; +} + +struct ast_bucket *ast_bucket_clone(struct ast_bucket *bucket) +{ + return ast_sorcery_copy(bucket_sorcery, bucket); +} + struct ast_bucket *ast_bucket_retrieve(const char *uri) { if (ast_strlen_zero(uri)) { @@ -468,6 +516,11 @@ struct ast_bucket *ast_bucket_retrieve(const char *uri) return ast_sorcery_retrieve_by_id(bucket_sorcery, "bucket", uri); } +int ast_bucket_is_stale(struct ast_bucket *bucket) +{ + return ast_sorcery_is_stale(bucket_sorcery, bucket); +} + int ast_bucket_observer_add(const struct ast_sorcery_observer *callbacks) { return ast_sorcery_observer_add(bucket_sorcery, "bucket", callbacks); @@ -730,6 +783,29 @@ static int bucket_copy(const char *infile, const char *outfile) return 0; /* success */ } +/*! + * \internal + * \brief Sorcery object type copy handler for \c ast_bucket_file + */ +static int bucket_file_copy_handler(const void *src, void *dst) +{ + const struct ast_bucket_file *src_file = src; + struct ast_bucket_file *dst_file = dst; + + dst_file->scheme_impl = ao2_bump(src_file->scheme_impl); + ast_string_field_set(dst_file, scheme, src_file->scheme); + dst_file->created = src_file->created; + dst_file->modified = src_file->modified; + strcpy(dst_file->path, src_file->path); /* safe */ + + dst_file->metadata = ao2_container_clone(src_file->metadata, 0); + if (!dst_file->metadata) { + return -1; + } + + return 0; +} + struct ast_bucket_file *ast_bucket_file_copy(struct ast_bucket_file *file, const char *uri) { RAII_VAR(struct ast_bucket_file *, copy, ast_bucket_file_alloc(uri), ao2_cleanup); @@ -749,6 +825,11 @@ struct ast_bucket_file *ast_bucket_file_copy(struct ast_bucket_file *file, const return copy; } +struct ast_bucket_file *ast_bucket_file_clone(struct ast_bucket_file *file) +{ + return ast_sorcery_copy(bucket_sorcery, file); +} + struct ast_bucket_file *ast_bucket_file_retrieve(const char *uri) { if (ast_strlen_zero(uri)) { @@ -758,6 +839,11 @@ struct ast_bucket_file *ast_bucket_file_retrieve(const char *uri) return ast_sorcery_retrieve_by_id(bucket_sorcery, "file", uri); } +int ast_bucket_file_is_stale(struct ast_bucket_file *file) +{ + return ast_sorcery_is_stale(bucket_sorcery, file); +} + int ast_bucket_file_observer_add(const struct ast_sorcery_observer *callbacks) { return ast_sorcery_observer_add(bucket_sorcery, "file", callbacks); @@ -945,6 +1031,7 @@ int ast_bucket_init(void) ast_sorcery_object_field_register(bucket_sorcery, "bucket", "scheme", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_bucket, scheme)); ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "created", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket, created)); ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "modified", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket, modified)); + ast_sorcery_object_set_copy_handler(bucket_sorcery, "bucket", bucket_copy_handler); if (ast_sorcery_apply_default(bucket_sorcery, "file", "bucket_file", NULL) == AST_SORCERY_APPLY_FAIL) { ast_log(LOG_ERROR, "Failed to apply intermediary for 'file' object type in Bucket sorcery\n"); @@ -959,6 +1046,7 @@ int ast_bucket_init(void) ast_sorcery_object_field_register(bucket_sorcery, "file", "scheme", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_bucket_file, scheme)); ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "created", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket_file, created)); ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "modified", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket_file, modified)); + ast_sorcery_object_set_copy_handler(bucket_sorcery, "file", bucket_file_copy_handler); return 0; } diff --git a/tests/test_bucket.c b/tests/test_bucket.c index 7dadd1473..eb8fc9028 100644 --- a/tests/test_bucket.c +++ b/tests/test_bucket.c @@ -50,6 +50,8 @@ struct bucket_test_state { unsigned int updated:1; /*! \brief Whether the object has been deleted or not */ unsigned int deleted:1; + /*! \brief Whether the object is stale or not */ + unsigned int is_stale:1; }; /*! \brief Global scope structure for testing bucket wizards */ @@ -60,6 +62,7 @@ static void bucket_test_wizard_clear(void) bucket_test_wizard_state.created = 0; bucket_test_wizard_state.updated = 0; bucket_test_wizard_state.deleted = 0; + bucket_test_wizard_state.is_stale = 0; } static int bucket_test_wizard_create(const struct ast_sorcery *sorcery, void *data, void *object) @@ -107,11 +110,17 @@ static int bucket_test_wizard_delete(const struct ast_sorcery *sorcery, void *da return 0; } +static int bucket_test_wizard_is_stale(const struct ast_sorcery *sorcery, void *data, void *object) +{ + return bucket_test_wizard_state.is_stale; +} + static struct ast_sorcery_wizard bucket_test_wizard = { .name = "test", .create = bucket_test_wizard_create, .retrieve_id = bucket_test_wizard_retrieve_id, .delete = bucket_test_wizard_delete, + .is_stale = bucket_test_wizard_is_stale, }; static struct ast_sorcery_wizard bucket_file_test_wizard = { @@ -120,6 +129,7 @@ static struct ast_sorcery_wizard bucket_file_test_wizard = { .update = bucket_test_wizard_update, .retrieve_id = bucket_test_wizard_retrieve_id, .delete = bucket_test_wizard_delete, + .is_stale = bucket_test_wizard_is_stale, }; AST_TEST_DEFINE(bucket_scheme_register) @@ -233,6 +243,50 @@ AST_TEST_DEFINE(bucket_create) return AST_TEST_PASS; } +AST_TEST_DEFINE(bucket_clone) +{ + RAII_VAR(struct ast_bucket *, bucket, NULL, ao2_cleanup); + RAII_VAR(struct ast_bucket *, clone, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "bucket_clone"; + info->category = "/main/bucket/"; + info->summary = "bucket clone unit test"; + info->description = + "Test cloning a bucket"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(bucket = ast_bucket_alloc("test:///tmp/bob"))) { + ast_test_status_update(test, "Failed to allocate bucket\n"); + return AST_TEST_FAIL; + } + + bucket_test_wizard_clear(); + + if (ast_bucket_create(bucket)) { + ast_test_status_update(test, "Failed to create bucket with URI '%s'\n", + ast_sorcery_object_get_id(bucket)); + return AST_TEST_FAIL; + } + + clone = ast_bucket_clone(bucket); + if (!clone) { + ast_test_status_update(test, "Failed to clone bucket with URI '%s'\n", + ast_sorcery_object_get_id(bucket)); + return AST_TEST_FAIL; + } + + ast_test_validate(test, strcmp(ast_sorcery_object_get_id(bucket), ast_sorcery_object_get_id(clone)) == 0); + ast_test_validate(test, bucket->scheme_impl == clone->scheme_impl); + ast_test_validate(test, strcmp(bucket->scheme, clone->scheme) == 0); + + return AST_TEST_PASS; +} + AST_TEST_DEFINE(bucket_delete) { RAII_VAR(struct ast_bucket *, bucket, NULL, ao2_cleanup); @@ -276,6 +330,38 @@ AST_TEST_DEFINE(bucket_delete) return AST_TEST_PASS; } +AST_TEST_DEFINE(bucket_is_stale) +{ + RAII_VAR(struct ast_bucket *, bucket, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "bucket_is_stale"; + info->category = "/main/bucket/"; + info->summary = "bucket staleness unit test"; + info->description = + "Test if staleness of a bucket is reported correctly"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(bucket = ast_bucket_alloc("test:///tmp/bob"))) { + ast_test_status_update(test, "Failed to allocate bucket\n"); + return AST_TEST_FAIL; + } + + bucket_test_wizard_clear(); + + ast_test_validate(test, ast_bucket_is_stale(bucket) == 0); + + bucket_test_wizard_state.is_stale = 1; + + ast_test_validate(test, ast_bucket_is_stale(bucket) == 1); + + return AST_TEST_PASS; +} + AST_TEST_DEFINE(bucket_json) { RAII_VAR(struct ast_bucket *, bucket, NULL, ao2_cleanup); @@ -440,6 +526,52 @@ AST_TEST_DEFINE(bucket_file_create) return AST_TEST_PASS; } +AST_TEST_DEFINE(bucket_file_clone) +{ + RAII_VAR(struct ast_bucket_file *, file, NULL, ao2_cleanup); + RAII_VAR(struct ast_bucket_file *, clone, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "bucket_file_clone"; + info->category = "/main/bucket/"; + info->summary = "file clone unit test"; + info->description = + "Test cloning a file"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(file = ast_bucket_file_alloc("test:///tmp/bob"))) { + ast_test_status_update(test, "Failed to allocate file\n"); + return AST_TEST_FAIL; + } + + bucket_test_wizard_clear(); + + if (ast_bucket_file_create(file)) { + ast_test_status_update(test, "Failed to create file with URI '%s'\n", + ast_sorcery_object_get_id(file)); + return AST_TEST_FAIL; + } + ast_bucket_file_metadata_set(file, "bob", "joe"); + + clone = ast_bucket_file_clone(file); + if (!clone) { + ast_test_status_update(test, "Failed to clone file with URI '%s'\n", + ast_sorcery_object_get_id(file)); + return AST_TEST_FAIL; + } + + ast_test_validate(test, strcmp(ast_sorcery_object_get_id(file), ast_sorcery_object_get_id(clone)) == 0); + ast_test_validate(test, file->scheme_impl == clone->scheme_impl); + ast_test_validate(test, strcmp(file->scheme, clone->scheme) == 0); + ast_test_validate(test, ao2_container_count(file->metadata) == ao2_container_count(clone->metadata)); + + return AST_TEST_PASS; +} + AST_TEST_DEFINE(bucket_file_copy) { RAII_VAR(struct ast_bucket_file *, file, NULL, ao2_cleanup); @@ -625,6 +757,38 @@ AST_TEST_DEFINE(bucket_file_delete) return AST_TEST_PASS; } +AST_TEST_DEFINE(bucket_file_is_stale) +{ + RAII_VAR(struct ast_bucket_file *, file, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "bucket_file_is_stale"; + info->category = "/main/bucket/"; + info->summary = "file staleness unit test"; + info->description = + "Test if staleness of a bucket file is reported correctly"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(file = ast_bucket_file_alloc("test:///tmp/bob"))) { + ast_test_status_update(test, "Failed to allocate file\n"); + return AST_TEST_FAIL; + } + + bucket_test_wizard_clear(); + + ast_test_validate(test, ast_bucket_file_is_stale(file) == 0); + + bucket_test_wizard_state.is_stale = 1; + + ast_test_validate(test, ast_bucket_file_is_stale(file) == 1); + + return AST_TEST_PASS; +} + AST_TEST_DEFINE(bucket_file_metadata_set) { RAII_VAR(struct ast_bucket_file *, file, NULL, ao2_cleanup); @@ -827,11 +991,13 @@ static int unload_module(void) AST_TEST_UNREGISTER(bucket_scheme_register); AST_TEST_UNREGISTER(bucket_alloc); AST_TEST_UNREGISTER(bucket_create); + AST_TEST_UNREGISTER(bucket_clone); AST_TEST_UNREGISTER(bucket_delete); AST_TEST_UNREGISTER(bucket_retrieve); AST_TEST_UNREGISTER(bucket_json); AST_TEST_UNREGISTER(bucket_file_alloc); AST_TEST_UNREGISTER(bucket_file_create); + AST_TEST_UNREGISTER(bucket_file_clone); AST_TEST_UNREGISTER(bucket_file_copy); AST_TEST_UNREGISTER(bucket_file_retrieve); AST_TEST_UNREGISTER(bucket_file_update); @@ -854,15 +1020,19 @@ static int load_module(void) AST_TEST_REGISTER(bucket_scheme_register); AST_TEST_REGISTER(bucket_alloc); AST_TEST_REGISTER(bucket_create); + AST_TEST_REGISTER(bucket_clone); AST_TEST_REGISTER(bucket_delete); AST_TEST_REGISTER(bucket_retrieve); + AST_TEST_REGISTER(bucket_is_stale); AST_TEST_REGISTER(bucket_json); AST_TEST_REGISTER(bucket_file_alloc); AST_TEST_REGISTER(bucket_file_create); + AST_TEST_REGISTER(bucket_file_clone); AST_TEST_REGISTER(bucket_file_copy); AST_TEST_REGISTER(bucket_file_retrieve); AST_TEST_REGISTER(bucket_file_update); AST_TEST_REGISTER(bucket_file_delete); + AST_TEST_REGISTER(bucket_file_is_stale); AST_TEST_REGISTER(bucket_file_metadata_set); AST_TEST_REGISTER(bucket_file_metadata_unset); AST_TEST_REGISTER(bucket_file_metadata_get); |