|
| 1 | +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
| 2 | +// vim: ts=8 sw=2 smarttab |
| 3 | + |
| 4 | +#include <cstdlib> |
| 5 | +#include <initializer_list> |
| 6 | +#include <stdio.h> |
| 7 | +#include <string.h> |
| 8 | +#include <iostream> |
| 9 | +#include <time.h> |
| 10 | +#include <fcntl.h> |
| 11 | +#include <unistd.h> |
| 12 | +#include <random> |
| 13 | +#include <thread> |
| 14 | +#include <stack> |
| 15 | +#include <gtest/gtest.h> |
| 16 | +#include "global/global_init.h" |
| 17 | +#include "common/ceph_argparse.h" |
| 18 | +#include "include/stringify.h" |
| 19 | +#include "include/scope_guard.h" |
| 20 | +#include "common/errno.h" |
| 21 | + |
| 22 | +#include "os/bluestore/Allocator.h" |
| 23 | +#include "os/bluestore/bluestore_common.h" |
| 24 | +#include "os/bluestore/BlueFS.h" |
| 25 | + |
| 26 | +using namespace std; |
| 27 | + |
| 28 | +int argc; |
| 29 | +char **argv; |
| 30 | + |
| 31 | +std::unique_ptr<char[]> gen_buffer(uint64_t size) |
| 32 | +{ |
| 33 | + std::unique_ptr<char[]> buffer = std::make_unique<char[]>(size); |
| 34 | + std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned char> e; |
| 35 | + std::generate(buffer.get(), buffer.get()+size, std::ref(e)); |
| 36 | + return buffer; |
| 37 | +} |
| 38 | + |
| 39 | +class TempBdev { |
| 40 | +public: |
| 41 | + TempBdev() {} |
| 42 | + ~TempBdev() {} |
| 43 | + void choose_name(pid_t pid = getpid()) { |
| 44 | + static int n = 0; |
| 45 | + path = "ceph_test_bluefs.tmp.block." + stringify(pid) |
| 46 | + + "." + stringify(++n); |
| 47 | + } |
| 48 | + void create_bdev(uint64_t size) { |
| 49 | + ceph_assert(!path.empty()); |
| 50 | + int fd = ::open(path.c_str(), O_CREAT|O_RDWR|O_TRUNC, 0644); |
| 51 | + ceph_assert(fd >= 0); |
| 52 | + int r = ::ftruncate(fd, size); |
| 53 | + ceph_assert(r >= 0); |
| 54 | + ::close(fd); |
| 55 | + } |
| 56 | + void rm_bdev() { |
| 57 | + ceph_assert(!path.empty()); |
| 58 | + ::unlink(path.c_str()); |
| 59 | + } |
| 60 | + std::string path; |
| 61 | +}; |
| 62 | + |
| 63 | +class ConfSaver { |
| 64 | + std::stack<std::pair<std::string, std::string>> saved_settings; |
| 65 | + ConfigProxy& conf; |
| 66 | +public: |
| 67 | + ConfSaver(ConfigProxy& conf) : conf(conf) { |
| 68 | + conf._clear_safe_to_start_threads(); |
| 69 | + }; |
| 70 | + ~ConfSaver() { |
| 71 | + conf._clear_safe_to_start_threads(); |
| 72 | + while(saved_settings.size() > 0) { |
| 73 | + auto& e = saved_settings.top(); |
| 74 | + conf.set_val_or_die(e.first, e.second); |
| 75 | + saved_settings.pop(); |
| 76 | + } |
| 77 | + conf.set_safe_to_start_threads(); |
| 78 | + conf.apply_changes(nullptr); |
| 79 | + } |
| 80 | + void SetVal(const char* key, const char* val) { |
| 81 | + std::string skey(key); |
| 82 | + std::string prev_val; |
| 83 | + conf.get_val(skey, &prev_val); |
| 84 | + conf.set_val_or_die(skey, val); |
| 85 | + saved_settings.emplace(skey, prev_val); |
| 86 | + } |
| 87 | + void ApplyChanges() { |
| 88 | + conf.set_safe_to_start_threads(); |
| 89 | + conf.apply_changes(nullptr); |
| 90 | + } |
| 91 | +}; |
| 92 | + |
| 93 | + |
| 94 | +class BlueFS_ex : virtual public ::testing::Test { |
| 95 | + |
| 96 | +public: |
| 97 | + explicit BlueFS_ex() |
| 98 | + { |
| 99 | + } |
| 100 | + boost::intrusive_ptr<CephContext> init_ceph() |
| 101 | + { |
| 102 | + boost::intrusive_ptr<CephContext> cct; |
| 103 | + auto args = argv_to_vec(argc, argv); |
| 104 | + map<string, string> defaults = { |
| 105 | + {"debug_bluefs", "0/20"}, |
| 106 | + {"debug_bdev", "0/20"}, |
| 107 | + {"log_to_stderr", "false"}}; |
| 108 | + cct = global_init( |
| 109 | + &defaults, args, CEPH_ENTITY_TYPE_CLIENT, |
| 110 | + CODE_ENVIRONMENT_UTILITY, CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); |
| 111 | + common_init_finish(g_ceph_context); |
| 112 | + g_ceph_context->_conf.set_val( |
| 113 | + "enable_experimental_unrecoverable_data_corrupting_features", "*"); |
| 114 | + g_ceph_context->_conf.apply_changes(nullptr); |
| 115 | + return cct; |
| 116 | + } |
| 117 | + |
| 118 | + void SetUp() override |
| 119 | + { |
| 120 | + } |
| 121 | + void TearDown() override |
| 122 | + { |
| 123 | + } |
| 124 | + |
| 125 | + void grow_log_interrupt_on_compact(pid_t parent_pid, uint32_t stop_point) |
| 126 | + { |
| 127 | + auto cct = init_ceph(); |
| 128 | + ConfSaver conf(g_ceph_context->_conf); |
| 129 | + conf.SetVal("bluefs_alloc_size", "4096"); |
| 130 | + conf.SetVal("bluefs_shared_alloc_size", "4096"); |
| 131 | + conf.SetVal("bluefs_compact_log_sync", "false"); |
| 132 | + conf.SetVal("bluefs_log_compact_min_size", "1048576"); |
| 133 | + conf.ApplyChanges(); |
| 134 | + |
| 135 | + auto stop_at_fixed_point = [&](uint32_t i) -> void { |
| 136 | + if (i == stop_point) exit(107); |
| 137 | + }; |
| 138 | + BlueFS fs(g_ceph_context); |
| 139 | + fs.tracepoint_async_compact = stop_at_fixed_point; |
| 140 | + ASSERT_EQ(0, fs.add_block_device(BlueFS::BDEV_DB, bdev.path, false)); |
| 141 | + uuid_d fsid; |
| 142 | + ASSERT_EQ(0, fs.mkfs(fsid, {BlueFS::BDEV_DB, false, false})); |
| 143 | + ASSERT_EQ(0, fs.mount()); |
| 144 | + ASSERT_EQ(0, fs.maybe_verify_layout({BlueFS::BDEV_DB, false, false})); |
| 145 | + ASSERT_EQ(0, fs.mkdir("dir")); |
| 146 | + |
| 147 | + auto fill = [&](uint32_t filenum) { |
| 148 | + char data[2000] = {'x'}; |
| 149 | + BlueFS::FileWriter *h; |
| 150 | + ASSERT_EQ(0, fs.open_for_write("dir", "file"+to_string(filenum), &h, false)); |
| 151 | + for (size_t i = 0; i < 10000; i++) { |
| 152 | + h->append(data, 2000); |
| 153 | + fs.fsync(h); |
| 154 | + } |
| 155 | + fs.close_writer(h); |
| 156 | + }; |
| 157 | + std::thread thr[10]; |
| 158 | + for (int i=0; i< 10;i++) { |
| 159 | + thr[i] = std::thread(fill, i); |
| 160 | + } |
| 161 | + for (int i=0; i< 10;i++) { |
| 162 | + thr[i].join(); |
| 163 | + } |
| 164 | + EXPECT_TRUE(false && "reaching this point means test was not executed"); |
| 165 | + exit(111); |
| 166 | + } |
| 167 | + |
| 168 | + TempBdev bdev; |
| 169 | +}; |
| 170 | + |
| 171 | +TEST_F(BlueFS_ex, test_interrupted_compaction) |
| 172 | +{ |
| 173 | + for (uint32_t stop_point = 1; stop_point <= 6; stop_point++) |
| 174 | + { |
| 175 | + pid_t fork_for_test = fork(); |
| 176 | + if (fork_for_test != 0) { |
| 177 | + int stat; |
| 178 | + waitpid(fork_for_test, &stat, 0); |
| 179 | + ASSERT_TRUE(WIFEXITED(stat)); |
| 180 | + ASSERT_TRUE(WEXITSTATUS(stat) == 0); |
| 181 | + continue; |
| 182 | + } |
| 183 | + pid_t parent_pid = getpid(); |
| 184 | + uint64_t size = 1048576LL * (2 * 1024 + 128); |
| 185 | + bdev.choose_name(); |
| 186 | + bdev.create_bdev(size); |
| 187 | + pid_t fork_pid = fork(); |
| 188 | + if (fork_pid == 0) { |
| 189 | + std::cout << "growing BlueFS log for async compact, stop at #" << (int)stop_point << std::endl; |
| 190 | + grow_log_interrupt_on_compact(parent_pid, stop_point); |
| 191 | + } else { |
| 192 | + int stat; |
| 193 | + std::cout << "waiting for compaction to terminate" << std::endl; |
| 194 | + waitpid(fork_pid, &stat, 0); |
| 195 | + std::cout << "done code=" << WEXITSTATUS(stat) << std::endl; |
| 196 | + if(!WIFEXITED(stat) || WEXITSTATUS(stat) != 107) exit(107); |
| 197 | + auto cct = init_ceph(); |
| 198 | + ConfSaver conf(g_ceph_context->_conf); |
| 199 | + conf.SetVal("bluefs_alloc_size", "4096"); |
| 200 | + conf.SetVal("bluefs_shared_alloc_size", "4096"); |
| 201 | + conf.SetVal("bluefs_compact_log_sync", "false"); |
| 202 | + conf.SetVal("bluefs_log_compact_min_size", "1048576"); |
| 203 | + conf.ApplyChanges(); |
| 204 | + |
| 205 | + BlueFS fs(g_ceph_context); |
| 206 | + ASSERT_EQ(0, fs.add_block_device(BlueFS::BDEV_DB, bdev.path, false)); |
| 207 | + // fs.log_dump(); |
| 208 | + ASSERT_EQ(0, fs.mount()); |
| 209 | + fs.umount(); |
| 210 | + } |
| 211 | + bdev.rm_bdev(); |
| 212 | + exit(0); //this terminates one loop of 'fork_for_test' |
| 213 | + } |
| 214 | + |
| 215 | +} |
| 216 | + |
| 217 | +int main(int _argc, char **_argv) { |
| 218 | + argc = _argc; |
| 219 | + argv = _argv; |
| 220 | + ::testing::InitGoogleTest(&argc, argv); |
| 221 | + return RUN_ALL_TESTS(); |
| 222 | +} |
0 commit comments