From d8b55837fde104049448314462aec6c8dd3c4832 Mon Sep 17 00:00:00 2001 From: Loic Dachary Date: Mon, 16 Sep 2013 12:12:52 +0200 Subject: [PATCH 1/4] ErasureCode: proofread abstract API documentation * Andreas-Joachim Peters suggests to reduce copies to the minimum. When possible the output arguments will just point to the input argument. This must be documented as any side effect on the input argument may modify the output argument * Fix typos * Fix may/could/must/should to better reflect what's mandatory and what's not. * Reword the explanation of minimum_to_decode_with_cost to not suggest an implementation. This will need to be revisited anyway, when the semantic of the cost is defined. * Explain chunk size constraints Signed-off-by: Loic Dachary --- src/osd/ErasureCodeInterface.h | 88 +++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/src/osd/ErasureCodeInterface.h b/src/osd/ErasureCodeInterface.h index 5ce2842d562..656ee91987e 100644 --- a/src/osd/ErasureCodeInterface.h +++ b/src/osd/ErasureCodeInterface.h @@ -25,15 +25,15 @@ are systematic (i.e. the data is not mangled and can be reconstructed by concatenating chunks ). - All methods returns **0** on success and a negative value on + All methods return **0** on success and a negative value on error. If the value returned on error is not explained in **ErasureCodeInterface**, the sources or the documentation of the - interface implementer must be read to figure out what it means. It - is recommended that each error code matches an *errno* value that - relates to the cause of the error. + interface implementer (i.e. the plugin ) must be read to figure + out what it means. It is recommended that each error code matches + an *errno* value that relates to the cause of the error. Assuming the interface implementer provides three data chunks ( K - = 3 ) and two coding chunks ( M = 2 ), a buffer can be encoded as + = 3 ) and two coding chunks ( M = 2 ), a buffer could be encoded as follows: ~~~~~~~~~~~~~~~~{.c} @@ -50,16 +50,20 @@ encoded[4] // coding chunk 1 ~~~~~~~~~~~~~~~~ - If encoded[2] ( which contains **EF** ) is missing and accessing - encoded[3] ( the first coding chunk ) is more expensive than - accessing encoded[4] ( the second coding chunk ), the - **minimum_to_decode_with_cost** method can be called as follows: + The **minimum_to_decode_with_cost** method can be used to minimize + the cost of fetching the chunks necessary to retrieve a given + content. For instance, if encoded[2] (contained **EF**) is missing + and accessing encoded[3] (the first coding chunk) is more + expensive than accessing encoded[4] (the second coding chunk), + **minimum_to_decode_with_cost** is expected to chose the first + coding chunk. ~~~~~~~~~~~~~~~~{.c} set want_to_read(2); // want the chunk containing "EF" map available( 0 => 1, // data chunk 0 : available and costs 1 1 => 1, // data chunk 1 : available and costs 1 + // data chunk 2 : missing 3 => 9, // coding chunk 1 : available and costs 9 4 => 1, // coding chunk 2 : available and costs 1 ); @@ -67,14 +71,14 @@ minimum_to_decode_with_cost(want_to_read, available, &minimum); - minimum == set(0, 1, 4); + minimum == set(0, 1, 4); // NOT set(0, 1, 3); ~~~~~~~~~~~~~~~~ It sets **minimum** with three chunks to reconstruct the desired data chunk and will pick the second coding chunk ( 4 ) because it is less expensive ( 1 < 9 ) to retrieve than the first coding chunk ( 3 ). The caller is responsible for retrieving the chunks - and call **decode** to reconstruct the second data chunk content. + and call **decode** to reconstruct the second data chunk. ~~~~~~~~~~~~~~~~{.c} map chunks; @@ -85,6 +89,10 @@ decoded[2] == "EF" ~~~~~~~~~~~~~~~~ + The semantic of the cost value is defined by the caller and must + be known to the implementer. For instance, it may be more + expensive to retrieve two chunks with cost 1 + 9 = 10 than two + chunks with cost 6 + 6 = 12. */ #include @@ -113,7 +121,7 @@ namespace ceph { * * @param [in] want_to_read chunk indexes to be decoded * @param [in] available chunk indexes containing valid data - * @param [out] minimum chunk indexes to retrieve for decode + * @param [out] minimum chunk indexes to retrieve * @return **0** on success or a negative errno on error. */ virtual int minimum_to_decode(const set &want_to_read, @@ -124,8 +132,8 @@ namespace ceph { * Compute the smallest subset of **available** chunks that needs * to be retrieved in order to successfully decode * **want_to_read** chunks. If there are more than one possible - * subset, select the subset that contains the chunks with the - * lowest cost. + * subset, select the subset that minimizes the overall retrieval + * cost. * * The **available** parameter maps chunk indexes to their * retrieval cost. The higher the cost value, the more costly it @@ -141,7 +149,7 @@ namespace ceph { * @param [in] want_to_read chunk indexes to be decoded * @param [in] available map chunk indexes containing valid data * to their retrieval cost - * @param [out] minimum chunk indexes to retrieve for decode + * @param [out] minimum chunk indexes to retrieve * @return **0** on success or a negative errno on error. */ virtual int minimum_to_decode_with_cost(const set &want_to_read, @@ -150,15 +158,31 @@ namespace ceph { /** * Encode the content of **in** and store the result in - * **encoded**. The **encoded** map contains at least all - * chunk indexes found in the **want_to_encode** set. + * **encoded**. All buffers pointed to by **encoded** have the + * same size. The **encoded** map contains at least all chunk + * indexes found in the **want_to_encode** set. * * The **encoded** map is expected to be a pointer to an empty * map. * + * Assuming the **in** parameter is **length** bytes long, + * the concatenation of the first **length** bytes of the + * **encoded** buffers is equal to the content of the **in** + * parameter. + * * The **encoded** map may contain more chunks than required by * **want_to_encode** and the caller is expected to permanently - * store all of them, not just the chunks from **want_to_encode**. + * store all of them, not just the chunks listed in + * **want_to_encode**. + * + * The **encoded** map may contain pointers to data stored in + * the **in** parameter. If the caller modifies the content of + * **in** after calling the encode method, it may have a side + * effect on the content of **encoded**. + * + * The **encoded** map may contain pointers to buffers allocated + * by the encode method. They will be freed when **encoded** is + * freed. The allocation method is not specified. * * Returns 0 on success. * @@ -172,24 +196,30 @@ namespace ceph { map *encoded) = 0; /** - * Decode the **chunks** and store at least **want_to_read** chunks - * in **decoded**. + * Decode the **chunks** and store at least **want_to_read** + * chunks in **decoded**. + * + * The **decoded** map must be a pointer to an empty map. * * There must be enough **chunks** ( as returned by * **minimum_to_decode** or **minimum_to_decode_with_cost** ) to - * perform a successfull decoding of all chunks found in + * perform a successful decoding of all chunks listed in * **want_to_read**. * - * The **decoded** map is expected to be a pointer to an empty - * map. + * All buffers pointed by **in** must have the same size. * - * The **decoded** map may contain more chunks than required by - * **want_to_read** and they can safely be used by the caller. + * On success, the **decoded** map may contain more chunks than + * required by **want_to_read** and they can safely be used by the + * caller. * - * If a chunk is listed in **want_to_read** and there is - * corresponding **bufferlist** in **chunks**, it will be copied - * verbatim into **decoded**. If not it will be reconstructed from - * the existing chunks. + * If a chunk is listed in **want_to_read** and there is a + * corresponding **bufferlist** in **chunks**, it will be + * referenced in **decoded**. If not it will be reconstructed from + * the existing chunks. + * + * Because **decoded** may contain pointers to data found in + * **chunks**, modifying the content of **chunks** after calling + * decode may have a side effect on the content of **decoded**. * * Returns 0 on success. * From def05f06edb6d48d65126e7261e91808a6d9b054 Mon Sep 17 00:00:00 2001 From: Loic Dachary Date: Tue, 17 Sep 2013 10:50:58 +0200 Subject: [PATCH 2/4] ErasureCode: improve API implementation example * minimum_to_decode and minimum_to_decode_with_cost are replaced with meaningfull examples instead of placeholders * encode and decode are commented and hard coded constants are replaced by defines for readability * run against valgrind Signed-off-by: Loic Dachary --- src/test/osd/ErasureCodeExample.h | 85 +++++++++++++++++++++----- src/test/osd/TestErasureCodeExample.cc | 46 +++++++++++++- 2 files changed, 116 insertions(+), 15 deletions(-) diff --git a/src/test/osd/ErasureCodeExample.h b/src/test/osd/ErasureCodeExample.h index 896e614c6b5..95d79feb923 100644 --- a/src/test/osd/ErasureCodeExample.h +++ b/src/test/osd/ErasureCodeExample.h @@ -19,12 +19,19 @@ #include #include +#include #include #include "osd/ErasureCodeInterface.h" +#define FIRST_DATA_CHUNK 0 +#define SECOND_DATA_CHUNK 1 #define DATA_CHUNKS 2u + +#define CODING_CHUNK 2 #define CODING_CHUNKS 1u +#define MINIMUM_TO_RECOVER 2u + class ErasureCodeExample : public ErasureCodeInterface { public: useconds_t delay; @@ -43,21 +50,43 @@ public: virtual int minimum_to_decode(const set &want_to_read, const set &available_chunks, set *minimum) { - if (available_chunks.size() < DATA_CHUNKS) + if (includes(available_chunks.begin(), available_chunks.end(), + want_to_read.begin(), want_to_read.end())) { + *minimum = want_to_read; + return 0; + } else if (available_chunks.size() >= MINIMUM_TO_RECOVER) { + *minimum = available_chunks; + return 0; + } else { return -EIO; - set::iterator i; - unsigned j; - for (i = available_chunks.begin(), j = 0; j < DATA_CHUNKS; i++, j++) - minimum->insert(*i); - return 0; + } } virtual int minimum_to_decode_with_cost(const set &want_to_read, const map &available, set *minimum) { + // + // If one chunk is more expensive to fetch than the others, + // recover it instead. For instance, if the cost reflects the + // time it takes for a chunk to be retrieved from a remote + // OSD and if CPU is cheap, it could make sense to recover + // instead of fetching the chunk. + // + map c2c(available); + if (c2c.size() > DATA_CHUNKS) { + if (c2c[FIRST_DATA_CHUNK] > c2c[SECOND_DATA_CHUNK] && + c2c[FIRST_DATA_CHUNK] > c2c[CODING_CHUNK]) + c2c.erase(FIRST_DATA_CHUNK); + else if(c2c[SECOND_DATA_CHUNK] > c2c[FIRST_DATA_CHUNK] && + c2c[SECOND_DATA_CHUNK] > c2c[CODING_CHUNK]) + c2c.erase(SECOND_DATA_CHUNK); + else if(c2c[CODING_CHUNK] > c2c[FIRST_DATA_CHUNK] && + c2c[CODING_CHUNK] > c2c[SECOND_DATA_CHUNK]) + c2c.erase(CODING_CHUNK); + } set available_chunks; - for (map::const_iterator i = available.begin(); - i != available.end(); + for (map::const_iterator i = c2c.begin(); + i != c2c.end(); i++) available_chunks.insert(i->first); return minimum_to_decode(want_to_read, available_chunks, minimum); @@ -66,16 +95,28 @@ public: virtual int encode(const set &want_to_encode, const bufferlist &in, map *encoded) { + // + // make sure all data chunks have the same length, allocating + // padding if necessary. + // unsigned chunk_length = ( in.length() / DATA_CHUNKS ) + 1; unsigned length = chunk_length * ( DATA_CHUNKS + CODING_CHUNKS ); bufferlist out(in); bufferptr pad(length - in.length()); pad.zero(0, DATA_CHUNKS); out.push_back(pad); + // + // compute the coding chunk with first chunk ^ second chunk + // char *p = out.c_str(); - for (unsigned i = 0; i < chunk_length * DATA_CHUNKS; i++) - p[i + 2 * chunk_length] = - p[i + 0 * chunk_length] ^ p[i + 1 * chunk_length]; + for (unsigned i = 0; i < chunk_length; i++) + p[i + CODING_CHUNK * chunk_length] = + p[i + FIRST_DATA_CHUNK * chunk_length] ^ + p[i + SECOND_DATA_CHUNK * chunk_length]; + // + // populate the bufferlist with bufferptr pointing + // to chunk boundaries + // const bufferptr ptr = out.buffers().front(); for (set::iterator j = want_to_encode.begin(); j != want_to_encode.end(); @@ -89,14 +130,30 @@ public: virtual int decode(const set &want_to_read, const map &chunks, map *decoded) { - + // + // All chunks have the same size + // unsigned chunk_length = (*chunks.begin()).second.length(); for (set::iterator i = want_to_read.begin(); i != want_to_read.end(); i++) { - if (chunks.find(*i) != chunks.end()) + if (chunks.find(*i) != chunks.end()) { + // + // If the chunk is available, just copy the bufferptr pointer + // to the decoded argument. + // (*decoded)[*i] = chunks.find(*i)->second; - else { + } else if(chunks.size() != 2) { + // + // If a chunk is missing and there are not enough chunks + // to recover, abort. + // + return -ERANGE; + } else { + // + // No matter what the missing chunk is, XOR of the other + // two recovers it. + // bufferptr chunk(chunk_length); map::const_iterator k = chunks.begin(); const char *a = k->second.buffers().front().c_str(); diff --git a/src/test/osd/TestErasureCodeExample.cc b/src/test/osd/TestErasureCodeExample.cc index 66f521d7863..6866dfdbb9f 100644 --- a/src/test/osd/TestErasureCodeExample.cc +++ b/src/test/osd/TestErasureCodeExample.cc @@ -65,10 +65,54 @@ TEST(ErasureCodeExample, minimum_to_decode) EXPECT_EQ(0, example.minimum_to_decode(want_to_read, available_chunks, &minimum)); + EXPECT_EQ(1u, minimum.size()); + EXPECT_EQ(1u, minimum.count(1)); + } +} + +TEST(ErasureCodeExample, minimum_to_decode_with_cost) +{ + map parameters; + ErasureCodeExample example(parameters); + map available; + set want_to_read; + want_to_read.insert(1); + { + set minimum; + EXPECT_EQ(-EIO, example.minimum_to_decode_with_cost(want_to_read, + available, + &minimum)); + } + available[0] = 1; + available[2] = 1; + { + set minimum; + EXPECT_EQ(0, example.minimum_to_decode_with_cost(want_to_read, + available, + &minimum)); EXPECT_EQ(2u, minimum.size()); EXPECT_EQ(1u, minimum.count(0)); + EXPECT_EQ(1u, minimum.count(2)); + } + { + set minimum; + available[1] = 1; + EXPECT_EQ(0, example.minimum_to_decode_with_cost(want_to_read, + available, + &minimum)); + EXPECT_EQ(1u, minimum.size()); EXPECT_EQ(1u, minimum.count(1)); } + { + set minimum; + available[1] = 2; + EXPECT_EQ(0, example.minimum_to_decode_with_cost(want_to_read, + available, + &minimum)); + EXPECT_EQ(2u, minimum.size()); + EXPECT_EQ(1u, minimum.count(0)); + EXPECT_EQ(1u, minimum.count(2)); + } } TEST(ErasureCodeExample, encode_decode) @@ -142,5 +186,5 @@ int main(int argc, char **argv) { } // Local Variables: -// compile-command: "cd ../.. ; make -j4 && make unittest_erasure_code_example && ./unittest_erasure_code_example --gtest_filter=*.* --log-to-stderr=true --debug-osd=20" +// compile-command: "cd ../.. ; make -j4 && make unittest_erasure_code_example && valgrind --leak-check=full --tool=memcheck ./unittest_erasure_code_example --gtest_filter=*.* --log-to-stderr=true --debug-osd=20" // End: From 7324931d5ec8e57ff9d5ae4e5700a1a479f5a6bb Mon Sep 17 00:00:00 2001 From: Loic Dachary Date: Thu, 19 Sep 2013 09:19:51 +0200 Subject: [PATCH 3/4] ErasureCode: plugin loading in progress flag The bool loading data member of ErasureCodePluginRegistry is set to true when a plugin is being loaded, to provide an observable side effect for test purposes. Signed-off-by: Loic Dachary --- src/osd/ErasureCodePlugin.cc | 5 ++++- src/osd/ErasureCodePlugin.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/osd/ErasureCodePlugin.cc b/src/osd/ErasureCodePlugin.cc index 10b65b2604b..d8b9ae0fbbd 100644 --- a/src/osd/ErasureCodePlugin.cc +++ b/src/osd/ErasureCodePlugin.cc @@ -36,7 +36,8 @@ static ostream& _prefix(std::ostream* _dout) ErasureCodePluginRegistry ErasureCodePluginRegistry::singleton; ErasureCodePluginRegistry::ErasureCodePluginRegistry() : - lock("ErasureCodePluginRegistry::lock") + lock("ErasureCodePluginRegistry::lock"), + loading(false) { } @@ -76,7 +77,9 @@ int ErasureCodePluginRegistry::factory(const std::string &plugin_name, int r = 0; ErasureCodePlugin *plugin = get(plugin_name); if (plugin == 0) { + loading = true; r = load(plugin_name, parameters, &plugin); + loading = false; if (r != 0) return r; } diff --git a/src/osd/ErasureCodePlugin.h b/src/osd/ErasureCodePlugin.h index f1c1ccb31b3..a2feb71695a 100644 --- a/src/osd/ErasureCodePlugin.h +++ b/src/osd/ErasureCodePlugin.h @@ -41,6 +41,7 @@ namespace ceph { class ErasureCodePluginRegistry { public: Mutex lock; + bool loading; std::map plugins; static ErasureCodePluginRegistry singleton; From 4c9497f9be0560e0aed4b97d5969001c27a58203 Mon Sep 17 00:00:00 2001 From: Loic Dachary Date: Thu, 19 Sep 2013 09:28:14 +0200 Subject: [PATCH 4/4] ErasureCode: complete plugin loader unit tests * TestErasureCodePluginExample.cc is renamed to TestErasureCodePlugin.cc because it's not limited to the example which is really used to support tests rather than being tested. * Bugous plugins are added to exhibit failures and enable the unit tests to check they are handled as expected ErasureCodePluginFailToInitialize : the entry point returns != 0 ErasureCodePluginFailToRegister : the plugin registry is not updated ErasureCodePluginMissingEntryPoint : the shared library has no entry point * It would be difficult to prove that the mutex protecting against multiple loads actually does what it is expected to because of the lack of thread introspection functions such as : tell me if this thread is waiting on this mutex. A simpler approach is chosen : create a thread that blocks forever when loading ( that's what the delay in the example plugin is for ) and then check that the lock has indeed been acquired. Since this mutex is merely about making sure that only one thread at a time runs this sequence of code, it's probably enough. Signed-off-by: Loic Dachary --- src/test/Makefile.am | 23 +++- .../osd/ErasureCodePluginFailToInitialize.cc | 23 ++++ .../osd/ErasureCodePluginFailToRegister.cc | 22 ++++ .../osd/ErasureCodePluginMissingEntryPoint.cc | 1 + src/test/osd/TestErasureCodePlugin.cc | 114 ++++++++++++++++++ src/test/osd/TestErasureCodePluginExample.cc | 51 -------- 6 files changed, 182 insertions(+), 52 deletions(-) create mode 100644 src/test/osd/ErasureCodePluginFailToInitialize.cc create mode 100644 src/test/osd/ErasureCodePluginFailToRegister.cc create mode 100644 src/test/osd/ErasureCodePluginMissingEntryPoint.cc create mode 100644 src/test/osd/TestErasureCodePlugin.cc delete mode 100644 src/test/osd/TestErasureCodePluginExample.cc diff --git a/src/test/Makefile.am b/src/test/Makefile.am index 80ec69425ca..278243b5beb 100644 --- a/src/test/Makefile.am +++ b/src/test/Makefile.am @@ -307,7 +307,28 @@ libec_example_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS) libec_example_la_LDFLAGS = ${AM_LDFLAGS} -export-symbols-regex '.*__erasure_code_.*' erasure_codelib_LTLIBRARIES += libec_example.la -unittest_erasure_code_plugin_SOURCES = test/osd/TestErasureCodePluginExample.cc +libec_missing_entry_point_la_SOURCES = test/osd/ErasureCodePluginMissingEntryPoint.cc +libec_missing_entry_point_la_CFLAGS = ${AM_CFLAGS} +libec_missing_entry_point_la_CXXFLAGS= ${AM_CXXFLAGS} +libec_missing_entry_point_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS) +libec_missing_entry_point_la_LDFLAGS = ${AM_LDFLAGS} -export-symbols-regex '.*__erasure_code_.*' +erasure_codelib_LTLIBRARIES += libec_missing_entry_point.la + +libec_fail_to_initialize_la_SOURCES = test/osd/ErasureCodePluginFailToInitialize.cc +libec_fail_to_initialize_la_CFLAGS = ${AM_CFLAGS} +libec_fail_to_initialize_la_CXXFLAGS= ${AM_CXXFLAGS} +libec_fail_to_initialize_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS) +libec_fail_to_initialize_la_LDFLAGS = ${AM_LDFLAGS} -export-symbols-regex '.*__erasure_code_.*' +erasure_codelib_LTLIBRARIES += libec_fail_to_initialize.la + +libec_fail_to_register_la_SOURCES = test/osd/ErasureCodePluginFailToRegister.cc +libec_fail_to_register_la_CFLAGS = ${AM_CFLAGS} +libec_fail_to_register_la_CXXFLAGS= ${AM_CXXFLAGS} +libec_fail_to_register_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS) +libec_fail_to_register_la_LDFLAGS = ${AM_LDFLAGS} -export-symbols-regex '.*__erasure_code_.*' +erasure_codelib_LTLIBRARIES += libec_fail_to_register.la + +unittest_erasure_code_plugin_SOURCES = test/osd/TestErasureCodePlugin.cc unittest_erasure_code_plugin_CXXFLAGS = $(UNITTEST_CXXFLAGS) unittest_erasure_code_plugin_LDADD = $(LIBOSD) $(LIBCOMMON) $(UNITTEST_LDADD) $(CEPH_GLOBAL) if LINUX diff --git a/src/test/osd/ErasureCodePluginFailToInitialize.cc b/src/test/osd/ErasureCodePluginFailToInitialize.cc new file mode 100644 index 00000000000..cded6eef556 --- /dev/null +++ b/src/test/osd/ErasureCodePluginFailToInitialize.cc @@ -0,0 +1,23 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2013 Cloudwatt + * + * Author: Loic Dachary + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + */ + +#include +#include "osd/ErasureCodePlugin.h" + +int __erasure_code_init(char *plugin_name) +{ + return -ESRCH; +} diff --git a/src/test/osd/ErasureCodePluginFailToRegister.cc b/src/test/osd/ErasureCodePluginFailToRegister.cc new file mode 100644 index 00000000000..ea980b722ae --- /dev/null +++ b/src/test/osd/ErasureCodePluginFailToRegister.cc @@ -0,0 +1,22 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2013 Cloudwatt + * + * Author: Loic Dachary + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + */ + +#include "osd/ErasureCodePlugin.h" + +int __erasure_code_init(char *plugin_name) +{ + return 0; +} diff --git a/src/test/osd/ErasureCodePluginMissingEntryPoint.cc b/src/test/osd/ErasureCodePluginMissingEntryPoint.cc new file mode 100644 index 00000000000..fc60f866086 --- /dev/null +++ b/src/test/osd/ErasureCodePluginMissingEntryPoint.cc @@ -0,0 +1 @@ +// missing int __erasure_code_init(char *plugin_name) {} diff --git a/src/test/osd/TestErasureCodePlugin.cc b/src/test/osd/TestErasureCodePlugin.cc new file mode 100644 index 00000000000..ba7d13fbd2d --- /dev/null +++ b/src/test/osd/TestErasureCodePlugin.cc @@ -0,0 +1,114 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2013 Cloudwatt + * + * Author: Loic Dachary + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include "common/Thread.h" +#include "global/global_init.h" +#include "osd/ErasureCodePlugin.h" +#include "common/ceph_argparse.h" +#include "global/global_context.h" +#include "gtest/gtest.h" + +class ErasureCodePluginRegistryTest : public ::testing::Test { +protected: + + class Thread_factory : public Thread { + public: + useconds_t delay; + + Thread_factory(useconds_t _delay) : + delay(_delay) + {} + + virtual void *entry() { + map parameters; + parameters["erasure-code-directory"] = ".libs"; + parameters["usleep"] = delay; + ErasureCodePluginRegistry &instance = ErasureCodePluginRegistry::instance(); + ErasureCodeInterfaceRef erasure_code; + instance.factory("example", parameters, &erasure_code); + return NULL; + } + }; + +}; + +TEST_F(ErasureCodePluginRegistryTest, factory_mutex) { + ErasureCodePluginRegistry &instance = ErasureCodePluginRegistry::instance(); + + EXPECT_TRUE(instance.lock.TryLock()); + instance.lock.Unlock(); + + // + // Test that the loading of a plugin is protected by a mutex. + // + useconds_t delay = 0; + const useconds_t DELAY_MAX = 20 * 1000 * 1000; + Thread_factory sleep_forever(1024 * 1024 * 1024); + sleep_forever.create(); + do { + cout << "Trying (1) with delay " << delay << "us\n"; + if (delay > 0) + usleep(delay); + if (!instance.loading) + delay = ( delay + 1 ) * 2; + } while(!instance.loading && delay < DELAY_MAX); + ASSERT_TRUE(delay < DELAY_MAX); + + EXPECT_FALSE(instance.lock.TryLock()); + + EXPECT_EQ(0, sleep_forever.detach()); +} + +TEST_F(ErasureCodePluginRegistryTest, all) +{ + map parameters; + parameters["erasure-code-directory"] = ".libs"; + ErasureCodeInterfaceRef erasure_code; + ErasureCodePluginRegistry &instance = ErasureCodePluginRegistry::instance(); + EXPECT_FALSE(erasure_code); + EXPECT_EQ(-EIO, instance.factory("invalid", parameters, &erasure_code)); + EXPECT_FALSE(erasure_code); + EXPECT_EQ(-ENOENT, instance.factory("missing_entry_point", parameters, + &erasure_code)); + EXPECT_FALSE(erasure_code); + EXPECT_EQ(-ESRCH, instance.factory("fail_to_initialize", parameters, + &erasure_code)); + EXPECT_FALSE(erasure_code); + EXPECT_EQ(-EBADF, instance.factory("fail_to_register", parameters, + &erasure_code)); + EXPECT_FALSE(erasure_code); + EXPECT_EQ(0, instance.factory("example", parameters, &erasure_code)); + EXPECT_TRUE(erasure_code); + ErasureCodePlugin *plugin = 0; + EXPECT_EQ(-EEXIST, instance.load("example", parameters, &plugin)); +} + +int main(int argc, char **argv) { + vector args; + argv_to_vec(argc, (const char **)argv, args); + + global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, 0); + common_init_finish(g_ceph_context); + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +// Local Variables: +// compile-command: "cd ../.. ; make -j4 && make unittest_erasure_code_plugin && valgrind --leak-check=full --tool=memcheck ./unittest_erasure_code_plugin --gtest_filter=*.* --log-to-stderr=true --debug-osd=20" +// End: diff --git a/src/test/osd/TestErasureCodePluginExample.cc b/src/test/osd/TestErasureCodePluginExample.cc deleted file mode 100644 index 67b41f2011a..00000000000 --- a/src/test/osd/TestErasureCodePluginExample.cc +++ /dev/null @@ -1,51 +0,0 @@ -// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- -// vim: ts=8 sw=2 smarttab -/* - * Ceph - scalable distributed file system - * - * Copyright (C) 2013 Cloudwatt - * - * Author: Loic Dachary - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - */ - -#include -#include "common/Thread.h" -#include "global/global_init.h" -#include "osd/ErasureCodePlugin.h" -#include "common/ceph_argparse.h" -#include "global/global_context.h" -#include "gtest/gtest.h" - -TEST(ErasureCodePluginRegistry, factory) -{ - map parameters; - parameters["erasure-code-directory"] = ".libs"; - ErasureCodeInterfaceRef erasure_code; - ErasureCodePluginRegistry &instance = ErasureCodePluginRegistry::instance(); - EXPECT_FALSE(erasure_code); - EXPECT_EQ(0, instance.factory("example", parameters, &erasure_code)); - EXPECT_TRUE(erasure_code); - ErasureCodePlugin *plugin = 0; - EXPECT_EQ(-EEXIST, instance.load("example", parameters, &plugin)); -} - -int main(int argc, char **argv) { - vector args; - argv_to_vec(argc, (const char **)argv, args); - - global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, 0); - common_init_finish(g_ceph_context); - - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - -// Local Variables: -// compile-command: "cd ../.. ; make -j4 && make unittest_erasure_code_plugin && ./unittest_erasure_code_plugin --gtest_filter=*.* --log-to-stderr=true --debug-osd=20" -// End: