diff --git a/docs/usage/cli.en.md b/docs/usage/cli.en.md index a389860d..7d6c8f18 100644 --- a/docs/usage/cli.en.md +++ b/docs/usage/cli.en.md @@ -176,6 +176,7 @@ Remove inode data without changing metadata. --wait-list Retrieve full objects listings before starting to remove objects. Requires more memory, but allows to show correct removal progress. --min-offset Purge only data starting with specified offset. +--max-offset Purge only data before specified offset. ``` ## merge-data diff --git a/docs/usage/cli.ru.md b/docs/usage/cli.ru.md index cf4f3615..b0a74a27 100644 --- a/docs/usage/cli.ru.md +++ b/docs/usage/cli.ru.md @@ -184,6 +184,7 @@ vitastor-cli snap-create [-p|--pool ] @ --wait-list Сначала запросить полный листинг объектов, а потом начать удалять. Требует больше памяти, но позволяет правильно печатать прогресс удаления. --min-offset Удалять только данные, начиная с заданного смещения. +--max-offset Удалять только данные до (исключительно) заданного смещения. ``` ## merge-data diff --git a/src/client/msgr_receive.cpp b/src/client/msgr_receive.cpp index 3cc613b6..1f3d8cf5 100644 --- a/src/client/msgr_receive.cpp +++ b/src/client/msgr_receive.cpp @@ -179,6 +179,8 @@ bool osd_messenger_t::handle_read_buffer(osd_client_t *cl, void *curbuf, int rem bool osd_messenger_t::handle_finished_read(osd_client_t *cl) { + cl->ping_time_remaining = osd_ping_timeout; + cl->idle_time_remaining = osd_idle_timeout; cl->recv_list.reset(); if (cl->read_state == CL_READ_HDR) { diff --git a/src/cmd/cli.cpp b/src/cmd/cli.cpp index b9036ce7..31b99283 100644 --- a/src/cmd/cli.cpp +++ b/src/cmd/cli.cpp @@ -70,6 +70,7 @@ static const char* help_text = " --wait-list Retrieve full objects listings before starting to remove objects.\n" " Requires more memory, but allows to show correct removal progress.\n" " --min-offset Purge only data starting with specified offset.\n" + " --max-offset Purge only data before specified offset.\n" "\n" "vitastor-cli merge-data [--target ]\n" " Merge layer data without changing metadata. Merge .. to .\n" diff --git a/src/cmd/cli_rm_data.cpp b/src/cmd/cli_rm_data.cpp index e88c6baf..69110862 100644 --- a/src/cmd/cli_rm_data.cpp +++ b/src/cmd/cli_rm_data.cpp @@ -25,6 +25,7 @@ struct rm_inode_t uint64_t inode = 0; pool_id_t pool_id = 0; uint64_t min_offset = 0; + uint64_t max_offset = 0; bool down_ok = false; cli_tool_t *parent = NULL; @@ -52,7 +53,7 @@ struct rm_inode_t .obj_done = 0, .synced = parent->cli->get_immediate_commit(inode), }); - if (min_offset == 0) + if (min_offset == 0 && max_offset == 0) { total_count += objects.size(); } @@ -60,7 +61,7 @@ struct rm_inode_t { for (object_id oid: objects) { - if (oid.stripe >= min_offset) + if (oid.stripe >= min_offset && (!max_offset || oid.stripe < max_offset)) { total_count++; } @@ -116,7 +117,7 @@ struct rm_inode_t } while (cur_list->in_flight < parent->iodepth && cur_list->obj_pos != cur_list->objects.end()) { - if (cur_list->obj_pos->stripe >= min_offset) + if (cur_list->obj_pos->stripe >= min_offset && (!max_offset || cur_list->obj_pos->stripe < max_offset)) { osd_op_t *op = new osd_op_t(); op->op_type = OSD_OP_OUT; @@ -287,6 +288,7 @@ std::function cli_tool_t::start_rm_data(json11::Json cfg) remover->down_ok = cfg["down_ok"].bool_value(); remover->pool_id = INODE_POOL(remover->inode); remover->min_offset = cfg["min_offset"].uint64_value(); + remover->max_offset = cfg["max_offset"].uint64_value(); return [remover](cli_result_t & result) { remover->loop(); diff --git a/src/nfs/CMakeLists.txt b/src/nfs/CMakeLists.txt index 89fb8aee..42891c39 100644 --- a/src/nfs/CMakeLists.txt +++ b/src/nfs/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(vitastor-nfs nfs_block.cpp nfs_kv.cpp nfs_kv_create.cpp + nfs_kv_defrag.cpp nfs_kv_getattr.cpp nfs_kv_link.cpp nfs_kv_lookup.cpp diff --git a/src/nfs/nfs_kv.cpp b/src/nfs/nfs_kv.cpp index 6badb575..5dbb28ba 100644 --- a/src/nfs/nfs_kv.cpp +++ b/src/nfs/nfs_kv.cpp @@ -116,17 +116,37 @@ std::string kv_direntry_filename(const std::string & key) return key; } -std::string kv_inode_key(uint64_t ino) +std::string kv_inode_prefix_key(uint64_t ino, const char *prefix) { - char key[32] = { 0 }; - snprintf(key, sizeof(key), "i%x", INODE_POOL(ino)); - int n = strnlen(key, sizeof(key)-1); - snprintf(key+n+1, sizeof(key)-n-1, "%jx", INODE_NO_POOL(ino)); - int m = strnlen(key+n+1, sizeof(key)-n-2); + int max = 32+strlen(prefix); + char key[max] = { 0 }; + snprintf(key, max, "%s%x", prefix, INODE_POOL(ino)); + int n = strnlen(key, max-1); + snprintf(key+n+1, max-n-1, "%jx", INODE_NO_POOL(ino)); + int m = strnlen(key+n+1, max-n-2); key[n] = 'G'+m; return std::string(key); } +std::string kv_inode_key(uint64_t ino) +{ + return kv_inode_prefix_key(ino, "i"); +} + +uint64_t kv_key_inode(const std::string & key, int prefix_len) +{ + if (key.size() < prefix_len) + return 0; + uint32_t pool_id = 0; + char len_plus_g = 0; + uint64_t inode_id = 0; + char null_byte = 0; + int scanned = sscanf(key.c_str()+prefix_len, "%x%c%jx%c", &pool_id, &len_plus_g, &inode_id, &null_byte); + if (scanned != 3 || !inode_id || INODE_POOL(inode_id) != 0) + return 0; + return INODE_WITH_POOL(pool_id, inode_id); +} + std::string kv_fh(uint64_t ino) { char key[32] = { 0 }; @@ -248,8 +268,36 @@ void kv_fs_state_t::init(nfs_proxy_t *proxy, json11::Json cfg) if (!id_alloc_batch_size) id_alloc_batch_size = 200; touch_interval = cfg["touch_interval"].uint64_value(); - if (touch_interval < 100) // ms + if (!touch_interval) + touch_interval = 1000; // ms + else if (touch_interval < 100) touch_interval = 100; + volume_stats_interval_mul = cfg["volume_stats_interval"].uint64_value() / touch_interval; + if (!volume_stats_interval_mul) + volume_stats_interval_mul = 1; + volume_touch_interval_mul = cfg["volume_touch_interval"].uint64_value() / touch_interval; + if (!volume_touch_interval_mul) + volume_touch_interval_mul = 30; + volume_untouched_sec = cfg["volume_untouched"].uint64_value(); + if (!volume_untouched_sec) + volume_untouched_sec = 86400; + if (volume_untouched_sec < 60) + volume_untouched_sec = 60; + defrag_percent = cfg["defrag_percent"].is_null() ? 50 : cfg["defrag_percent"].uint64_value(); + if (defrag_percent < 0) + defrag_percent = 0; + if (defrag_percent > 100) + defrag_percent = 100; + defrag_block_count = cfg["defrag_block_count"].is_null() ? 16 : cfg["defrag_block_count"].uint64_value(); + if (defrag_block_count < 1) + defrag_block_count = 1; + if (defrag_block_count > 1048576) + defrag_block_count = 1048576; + defrag_iodepth = cfg["defrag_iodepth"].is_null() ? 16 : cfg["defrag_iodepth"].uint64_value(); + if (defrag_iodepth < 1) + defrag_iodepth = 1; + if (defrag_iodepth > 1048576) + defrag_iodepth = 1048576; pool_block_size = pool_cfg.pg_stripe_size; pool_alignment = pool_cfg.bitmap_granularity; // Open DB and wait @@ -261,6 +309,7 @@ void kv_fs_state_t::init(nfs_proxy_t *proxy, json11::Json cfg) { kv_cfg[kv.first] = kv.second.as_string(); } + // Open K/V DB proxy->db->open(fs_kv_inode, kv_cfg, [&](int res) { open_done = true; @@ -279,6 +328,7 @@ void kv_fs_state_t::init(nfs_proxy_t *proxy, json11::Json cfg) strerror(-open_res), open_res); exit(1); } + // Proceed fs_inode_count = ((uint64_t)1 << (64-POOL_ID_BITS)) - 1; shared_inode_threshold = pool_block_size; if (!cfg["shared_inode_threshold"].is_null()) @@ -299,31 +349,36 @@ kv_fs_state_t::~kv_fs_state_t() } } -static void touch_inode(nfs_proxy_t *proxy, inode_t ino, bool allow_cache) +void kv_fs_state_t::update_inode(inode_t ino, bool allow_cache, std::function change, std::function cb) { - kv_read_inode(proxy, ino, [proxy, ino](int res, const std::string & value, json11::Json attrs) + // FIXME: Use "update" query + kv_read_inode(proxy, ino, [=](int res, const std::string & value, json11::Json attrs) { if (!res) { auto ientry = attrs.object_items(); - ientry["mtime"] = ientry["ctime"] = nfstime_now_str(); - ientry.erase("verf"); - // FIXME: Use "update" query + change(ientry); bool *found = new bool; *found = true; - proxy->db->set(kv_inode_key(ino), json11::Json(ientry).dump(), [proxy, ino, found](int res) + proxy->db->set(kv_inode_key(ino), json11::Json(ientry).dump(), [=](int res) { if (!*found) res = -ENOENT; delete found; if (res == -EAGAIN) - touch_inode(proxy, ino, false); + update_inode(ino, false, change, cb); + else if (cb) + cb(res); }, [value, found](int res, const std::string & old_value) { *found = res == 0; return res == 0 && old_value == value; }); } + else if (cb) + { + cb(res); + } }, allow_cache); } @@ -332,6 +387,31 @@ void kv_fs_state_t::touch_inodes() std::set q = std::move(touch_queue); for (auto ino: q) { - touch_inode(proxy, ino, true); + update_inode(ino, true, [](json11::Json::object & ientry) + { + ientry["mtime"] = ientry["ctime"] = nfstime_now_str(); + ientry.erase("verf"); + }, NULL); + } + if (++volume_stats_ctr >= volume_stats_interval_mul) + { + volume_stats_ctr = 0; + auto shr = std::move(volume_removed); + for (auto & sp: shr) + { + update_inode(sp.first, true, [removed = sp.second](json11::Json::object & ientry) + { + ientry["removed"] = ientry["removed"].uint64_value() + removed; + }, NULL); + } + } + if (!((volume_touch_ctr++) % volume_touch_interval_mul) && cur_shared_inode) + { + volume_touch_ctr = 1; + update_inode(cur_shared_inode, true, [size = cur_shared_offset](json11::Json::object & ientry) + { + ientry["opentime"] = nfstime_now_str(); + ientry["size"] = size; + }, NULL); } } diff --git a/src/nfs/nfs_kv.h b/src/nfs/nfs_kv.h index 2a96285a..07ab9b69 100644 --- a/src/nfs/nfs_kv.h +++ b/src/nfs/nfs_kv.h @@ -60,6 +60,13 @@ struct kv_fs_state_t uint64_t pool_alignment = 0; uint64_t shared_inode_threshold = 0; uint64_t touch_interval = 1000; + uint64_t volume_stats_interval_mul = 1; + uint64_t volume_touch_interval_mul = 30; + uint64_t volume_untouched_sec = 86400; + uint64_t defrag_percent = 50; + uint64_t defrag_block_count = 16; + uint64_t defrag_iodepth = 16; + bool dry_run = false; std::map list_cookies; std::map idgen; @@ -67,12 +74,19 @@ struct kv_fs_state_t uint64_t cur_shared_inode = 0, cur_shared_offset = 0; std::map extends; std::set touch_queue; + std::map volume_removed; + uint64_t volume_stats_ctr = 0; + uint64_t volume_touch_ctr = 0; std::vector zero_block; std::vector scrap_block; void init(nfs_proxy_t *proxy, json11::Json cfg); void touch_inodes(); + void update_inode(inode_t ino, bool allow_cache, std::function change, std::function cb); + void upgrade_db(std::function cb); + void defrag_all(json11::Json cfg, std::function cb); + void defrag_volume(inode_t ino, bool no_rm, bool dry_run, std::function cb); ~kv_fs_state_t(); }; @@ -105,16 +119,20 @@ int kv_map_type(const std::string & type); fattr3 get_kv_attributes(nfs_client_t *self, uint64_t ino, json11::Json attrs); std::string kv_direntry_key(uint64_t dir_ino, const std::string & filename); std::string kv_direntry_filename(const std::string & key); +std::string kv_inode_prefix_key(uint64_t ino, const char *prefix); std::string kv_inode_key(uint64_t ino); +uint64_t kv_key_inode(const std::string & key, int prefix_len = 1); std::string kv_fh(uint64_t ino); uint64_t kv_fh_inode(const std::string & fh); bool kv_fh_valid(const std::string & fh); -void allocate_new_id(nfs_client_t *self, pool_id_t pool_id, std::function cb); +void allocate_new_id(nfs_proxy_t *proxy, pool_id_t pool_id, std::function cb); void kv_read_inode(nfs_proxy_t *proxy, uint64_t ino, std::function cb, bool allow_cache = false); uint64_t align_shared_size(nfs_client_t *self, uint64_t size); void nfs_do_rmw(nfs_rmw_t *rmw); +void nfs_move_inode_from(nfs_proxy_t *proxy, uint64_t ino, uint64_t shared_ino, + uint64_t shared_offset, std::function cb); int kv_nfs3_getattr_proc(void *opaque, rpc_op_t *rop); int kv_nfs3_setattr_proc(void *opaque, rpc_op_t *rop); diff --git a/src/nfs/nfs_kv_create.cpp b/src/nfs/nfs_kv_create.cpp index e9ef58e2..f1213c6e 100644 --- a/src/nfs/nfs_kv_create.cpp +++ b/src/nfs/nfs_kv_create.cpp @@ -9,9 +9,9 @@ #include "nfs_proxy.h" #include "nfs_kv.h" -void allocate_new_id(nfs_client_t *self, pool_id_t pool_id, std::function cb) +void allocate_new_id(nfs_proxy_t *proxy, pool_id_t pool_id, std::function cb) { - auto & idgen = self->parent->kvfs->idgen[pool_id]; + auto & idgen = proxy->kvfs->idgen[pool_id]; if (idgen.unallocated_ids.size()) { auto new_id = idgen.unallocated_ids.back(); @@ -31,9 +31,9 @@ void allocate_new_id(nfs_client_t *self, pool_id_t pool_id, std::functionparent->db->get((pool_id ? "id"+std::to_string(pool_id) : "id"), [=](int res, const std::string & prev_str) + proxy->db->get((pool_id ? "id"+std::to_string(pool_id) : "id"), [=](int res, const std::string & prev_str) { - auto & idgen = self->parent->kvfs->idgen[pool_id]; + auto & idgen = proxy->kvfs->idgen[pool_id]; if (res < 0 && res != -ENOENT) { cb(res, 0); @@ -49,17 +49,21 @@ void allocate_new_id(nfs_client_t *self, pool_id_t pool_id, std::functionparent->kvfs->id_alloc_batch_size; - if (new_val >= self->parent->kvfs->fs_inode_count) + uint64_t new_val = prev_val + proxy->kvfs->id_alloc_batch_size; + if (new_val >= proxy->kvfs->fs_inode_count) { - new_val = self->parent->kvfs->fs_inode_count; + new_val = proxy->kvfs->fs_inode_count; } - self->parent->db->set((pool_id ? "id"+std::to_string(pool_id) : "id"), std::to_string(new_val), [=](int res) + if (!pool_id && res == -ENOENT) + { + proxy->db->set("version", "1", [](int){}); + } + proxy->db->set((pool_id ? "id"+std::to_string(pool_id) : "id"), std::to_string(new_val), [=](int res) { if (res == -EAGAIN) { // CAS failure - retry - allocate_new_id(self, pool_id, cb); + allocate_new_id(proxy, pool_id, cb); } else if (res < 0) { @@ -67,7 +71,7 @@ void allocate_new_id(nfs_client_t *self, pool_id_t pool_id, std::functionparent->kvfs->idgen[pool_id]; + auto & idgen = proxy->kvfs->idgen[pool_id]; idgen.next_id = prev_val+2; idgen.allocated_id = new_val; cb(0, INODE_WITH_POOL(pool_id, prev_val+1)); @@ -125,7 +129,7 @@ resume_1: st->pool_id = kv_map_type(st->attrs["type"].string_value()) == NF3REG ? st->self->parent->default_pool_id : 0; - allocate_new_id(st->self, st->pool_id, [st](int res, uint64_t new_id) + allocate_new_id(st->self->parent, st->pool_id, [st](int res, uint64_t new_id) { st->res = res; st->new_id = new_id; diff --git a/src/nfs/nfs_kv_defrag.cpp b/src/nfs/nfs_kv_defrag.cpp new file mode 100644 index 00000000..ad36234d --- /dev/null +++ b/src/nfs/nfs_kv_defrag.cpp @@ -0,0 +1,528 @@ +// Copyright (c) Vitaliy Filippov, 2019+ +// License: VNPL-1.1 (see README.md for details) +// +// NFS proxy over VitastorKV database - defragmentation + +#include + +#include "nfs_proxy.h" +#include "nfs_common.h" +#include "nfs_kv.h" +#include "str_util.h" +#include "cli.h" + +struct kv_fs_defrag_t +{ + nfs_proxy_t *proxy = NULL; + inode_t shared_ino = 0; + bool dry_run = false; + bool no_rm = false; + bool progress = true; + uint64_t bitmap_granularity = 0; + uint64_t buf_size = 0; + uint8_t *block_buf = NULL; + + timespec prev_progress = {}; + int errcode = 0; + bool reading = false; + bool empty = false; + uint64_t last_offset = 0; + uint64_t real_size = 0; + uint64_t buf_pos = 0; + uint64_t iodepth = 0; + uint64_t num_moved = 0, num_unused = 0; + uint64_t bytes_moved = 0, bytes_unused = 0; + uint64_t max_ctime = 0; + bool handling = false; + std::function cb; + + void read(); + void handle_read(); + void finish(int retval); +}; + +void kv_fs_defrag_t::finish(int retval) +{ + auto cb = std::move(this->cb); + delete block_buf; + block_buf = NULL; + cb(retval, real_size, bytes_unused, max_ctime); + delete this; +} + +void kv_fs_defrag_t::read() +{ + if (errcode) + { + finish(errcode); + return; + } + auto op = new cluster_op_t; + op->opcode = OSD_OP_READ; + op->inode = shared_ino; + op->offset = last_offset; + op->len = buf_size; + op->iov.push_back(block_buf, buf_size); + reading = true; + op->callback = [this](cluster_op_t *op) + { + reading = false; + if (op->retval != op->len) + { + fprintf(stderr, "Error reading 0x%jx bytes from volume 0x%jx at 0x%jx: %s (code %d)\n", + op->len, shared_ino, op->offset, strerror(-op->retval), op->retval); + finish(op->retval >= 0 ? -EIO : op->retval); + } + else + { + // Check that any data was actually read or it's the last iteration + uint64_t bitmap_size = (op->len / bitmap_granularity + 7) / 8; + uint64_t bitmap_pos = 0; + empty = true; + for (; bitmap_pos < bitmap_size; bitmap_pos += 8) + { + if (*((uint64_t*)(op->bitmap_buf + bitmap_pos))) + empty = false; + } + for (; bitmap_pos < bitmap_size; bitmap_pos++) + { + if (*((uint8_t*)(op->bitmap_buf + bitmap_pos))) + empty = false; + } + buf_pos = 0; + handle_read(); + } + delete op; + }; + proxy->cli->execute(op); +} + +void kv_fs_defrag_t::handle_read() +{ + if (handling) + { + return; + } + handling = true; + while (!empty && !errcode && buf_pos < buf_size && iodepth < proxy->kvfs->defrag_iodepth) + { + // Next object header may be at any position after any number of zeroes + // Commonly it's either in the beginning of a 4 KB sector or in the end of it + if ((*(uint64_t*)(block_buf+buf_pos)) == SHARED_FILE_MAGIC_V1) + { + iodepth++; + shared_file_header_t *hdr = (shared_file_header_t*)(block_buf+buf_pos); + uint64_t shared_offset = last_offset + buf_pos; + buf_pos += hdr->alloc; + real_size = shared_offset + hdr->alloc; + auto move_cb = [this, ino = hdr->inode, alloc = hdr->alloc, shared_offset](int res, bool was_moved) + { + if (res < 0 && res != -ENOENT) + { + fprintf(stderr, "Error checking/moving inode 0x%jx from volume 0x%jx offset 0x%jx: %s (code %d)\n", + ino, shared_ino, shared_offset, strerror(-res), res); + errcode = res; + } + else + { + if (was_moved) + { + bytes_moved += alloc; + num_moved++; + } + else + { + bytes_unused += alloc; + num_unused++; + } + if (proxy->trace) + { + fprintf( + stderr, was_moved + ? "Moved inode 0x%jx (%ju bytes) from volume 0x%jx offset 0x%ju\n" + : "Inode 0x%jx (%ju bytes) data in volume 0x%jx at offset 0x%ju is unused", + ino, alloc, shared_ino, shared_offset + ); + } + else if (progress) + { + timespec now; + clock_gettime(CLOCK_REALTIME, &now); + if (now.tv_sec >= prev_progress.tv_sec+2) + { + prev_progress = now; + fprintf(stderr, "Processed %s, %s %s, unused %s\n", format_size(real_size).c_str(), + dry_run ? "in use" : "moved", format_size(bytes_moved).c_str(), format_size(bytes_unused).c_str()); + } + } + } + iodepth--; + handle_read(); + }; + if (dry_run) + { + kv_read_inode(proxy, hdr->inode, [=](int res, const std::string & value, json11::Json attrs) + { + uint64_t ctime = (uint64_t)attrs["ctime"].number_value(); + if (max_ctime < ctime) + max_ctime = ctime; + move_cb(res, !res && attrs["shared_ino"] == shared_ino && attrs["shared_offset"] == shared_offset); + }); + } + else + { + nfs_move_inode_from(proxy, hdr->inode, shared_ino, shared_offset, move_cb); + } + } + else + { + buf_pos += 8; + } + } + handling = false; + if (errcode) + { + if (iodepth) + { + // Wait for completion + return; + } + finish(errcode); + } + else if (empty) + { + if (iodepth) + { + // Wait for completion + return; + } + // Finish - now we can purge shared inode + fprintf( + stderr, dry_run + ? "Estimated volume 0x%jx - in use %s (%ju files), unused %s (%ju files), last inode change time %s\n" + : "Defragmented volume 0x%jx - moved %s (%ju files), unused %s (%ju files), last inode change time %s. Purging volume data\n", + shared_ino, format_size(bytes_moved).c_str(), num_moved, format_size(bytes_unused).c_str(), num_unused, format_datetime(max_ctime).c_str() + ); + if (dry_run || no_rm) + { + finish(0); + } + else + { + proxy->cmd->loop_and_wait(proxy->cmd->start_rm_data(json11::Json::object { + { "inode", INODE_NO_POOL(shared_ino) }, + { "pool", (uint64_t)INODE_POOL(shared_ino) }, + { "progress", (uint64_t)proxy->trace } + }), [this](const cli_result_t & r) + { + if (r.err) + { + fprintf(stderr, "Failed to remove volume 0x%jx data: %s (code %d)\n", + shared_ino, r.text.c_str(), r.err); + finish(r.err); + } + else + { + proxy->db->del(kv_inode_key(shared_ino), [=](int res) + { + if (res < 0) + { + fprintf(stderr, "Failed to remove volume key %s: %s (code %d)\n", + kv_inode_key(shared_ino).c_str(), strerror(-res), res); + finish(res); + } + else + { + proxy->db->del(kv_inode_prefix_key(shared_ino, "shared"), [=](int res) + { + if (res < 0) + { + fprintf(stderr, "Failed to remove volume key %s: %s (code %d)\n", + kv_inode_prefix_key(shared_ino, "shared").c_str(), strerror(-res), res); + } + finish(res); + }); + } + }); + } + }); + } + } + else if (!reading && buf_pos >= buf_size) + { + last_offset += buf_pos; + read(); + } +} + +// Linear read all object headers, check which of them are still alive, move them away +void kv_fs_state_t::defrag_volume(inode_t ino, bool no_rm, bool dry_run, std::function cb) +{ + auto pool_it = proxy->cli->st_cli.pool_config.find(INODE_POOL(ino)); + if (pool_it == proxy->cli->st_cli.pool_config.end()) + { + fprintf(stderr, "Volume 0x%jx references a non-existing pool with ID %u, skipping\n", ino, INODE_POOL(ino)); + cb(0, 0, 0, 0); + return; + } + auto st = new kv_fs_defrag_t; + st->proxy = proxy; + st->shared_ino = ino; + st->dry_run = dry_run; + st->no_rm = no_rm; + st->buf_size = pool_it->second.pg_stripe_size * defrag_block_count; + st->block_buf = (uint8_t*)malloc_or_die(st->buf_size); + st->bitmap_granularity = pool_it->second.bitmap_granularity; + st->cb = cb; + clock_gettime(CLOCK_REALTIME, &st->prev_progress); + st->read(); +} + +struct kv_fs_defrag_all_t +{ + std::function cb; + nfs_proxy_t *proxy = NULL; + bool dry_run = false; + bool no_rm = false; + bool recalc_stats = false; + bool include_empty = false; + + timespec now = {}; + void *list_shared = NULL; + uint64_t ino = 0; + json11::Json ientry; + bool recalc = false; + uint64_t real_size = 0; + uint64_t removed_size = 0; + uint64_t opentime = 0; + int res = 0; + + void run(int); +}; + +void kv_fs_defrag_all_t::run(int st) +{ + if (st == 1) + goto resume_1; + else if (st == 2) + goto resume_2; + else if (st == 3) + goto resume_3; + else if (st == 4) + goto resume_4; + else if (st == 5) + goto resume_5; + else if (st == 6) + goto resume_6; + clock_gettime(CLOCK_REALTIME, &now); + list_shared = proxy->db->list_start("shared"); + proxy->db->list_next(list_shared, [this](int res, const std::string & key, const std::string & value) + { + if (res == -ENOENT || key.substr(0, 6) != "shared") + this->res = -ENOENT; + else + { + this->res = res; + this->ino = kv_key_inode(key, 6); + } + run(1); + }); + return; + while (true) + { +resume_1: + if (res < 0) + { + if (res == -ENOENT) + res = 0; + break; + } + kv_read_inode(proxy, ino, [this](int res, const std::string & value, json11::Json attrs) + { + this->res = res; + this->ientry = attrs; + run(2); + }); + return; +resume_2: + if (res == -ENOENT) + { + // This shared inode is already removed + proxy->db->del(kv_inode_prefix_key(ino, "shared"), [this](int res) + { + run(3); + }); + return; +resume_3: + proxy->db->list_next(list_shared, NULL); + return; + } + real_size = ientry["size"].uint64_value(); + removed_size = ientry["removed"].uint64_value(); + opentime = (uint64_t)ientry["opentime"].number_value(); + recalc = false; + if (!real_size && !opentime || recalc_stats) + { + // Statistics are missing - recalculate statistics + recalc = true; + fprintf(stderr, "Shared volume 0x%jx misses size and removal statistics, recalculating\n", ino); + proxy->kvfs->defrag_volume(ino, true, true, [this](int res, uint64_t sz, uint64_t rm, uint64_t tm) + { + this->res = res; + this->real_size = sz; + this->removed_size = rm; + this->opentime = tm; + run(4); + }); + return; +resume_4: + if (res < 0) + { + break; + } + proxy->kvfs->update_inode(ino, true, [this](json11::Json::object & ientry) + { + ientry["size"] = real_size; + ientry["removed"] = removed_size; + ientry["opentime"] = opentime; + }, [this](int res) + { + this->res = res; + run(5); + }); + return; +resume_5: + if (res < 0) + { + fprintf(stderr, "Warning: Failed to update shared volume 0x%jx metadata: %s (code %d)\n", ino, strerror(-res), res); + } + } + if ((opentime && opentime < now.tv_sec - proxy->kvfs->volume_untouched_sec || !opentime && include_empty) && + (real_size && removed_size || include_empty) && + removed_size >= (real_size * proxy->kvfs->defrag_percent / 100)) + { + // This volume needs defrag + fprintf( + stderr, "Shared volume 0x%jx requires defragmentation: last " + "open-for-append time %s, size %s, removed %s\n", + ino, format_datetime(opentime).c_str(), format_size(real_size).c_str(), format_size(removed_size).c_str() + ); + if (!recalc || !dry_run) + { + proxy->kvfs->defrag_volume(ino, no_rm, dry_run, [this](int res, uint64_t, uint64_t, uint64_t) + { + this->res = res; + run(6); + }); + return; +resume_6: + if (res < 0) + { + break; + } + } + } + else + { + fprintf( + stderr, "Shared volume 0x%jx does not require defragmentation: last " + "open-for-append time %s, size %s, removed %s\n", + ino, format_datetime(opentime).c_str(), format_size(real_size).c_str(), format_size(removed_size).c_str() + ); + } + proxy->db->list_next(list_shared, NULL); + return; + } + proxy->db->list_close(list_shared); + auto cb = std::move(this->cb); + cb(res); + delete this; +} + +void kv_fs_state_t::defrag_all(json11::Json cfg, std::function cb) +{ + auto st = new kv_fs_defrag_all_t; + st->cb = cb; + st->proxy = proxy; + st->dry_run = cfg["dry_run"].bool_value(); + st->no_rm = cfg["no_rm"].bool_value(); + st->recalc_stats = cfg["recalc_stats"].bool_value(); + st->include_empty = cfg["include_empty"].bool_value(); + st->run(0); +} + +void kv_fs_state_t::upgrade_db(std::function cb) +{ + // In the future, FS metadata format upgrades should be added here + // Currently we only do one thing: we create missing shared inode list keys ("sharedXXX") + proxy->db->get("version", [=](int res, const std::string & ver_value) + { + if (res < 0 && res != -ENOENT) + { + cb(res); + return; + } + json11::Json ver; + if (res == 0) + { + std::string err; + ver = json11::Json::parse(ver_value, err); + if (err != "") + { + fprintf(stderr, "Invalid JSON in `version` key, value: %s, error: %s\n", ver_value.c_str(), err.c_str()); + cb(-EINVAL); + return; + } + } + if (ver.uint64_value() > 1 || ver.is_object()) + { + cb(0); + return; + } + // Create missing shared inode index keys + auto list_inodes = proxy->db->list_start("i"); + proxy->db->list_next(list_inodes, [=](int res, const std::string & key, const std::string & value) + { + if (res == -ENOENT || key.substr(0, 1) != "i" || key == "id") + { + proxy->db->list_close(list_inodes); + proxy->db->set("version", "1", [=](int res) + { + cb(0); + }, [=](int res, const std::string & value) + { + return res == -ENOENT || ver_value == value; + }); + return; + } + uint64_t inode_id = kv_key_inode(key, 1); + if (!inode_id) + { + fprintf(stderr, "Invalid inode key %s, skipping\n", key.c_str()); + } + else + { + std::string err; + auto ientry = json11::Json::parse(value, err); + if (err != "") + { + fprintf(stderr, "Invalid JSON in key %s (inode %ju), skipping\n", key.c_str(), inode_id); + } + else if (ientry["type"] == "shared") + { + proxy->db->set(kv_inode_prefix_key(inode_id, "shared"), "{}", [=](int res) + { + if (res < 0) + { + fprintf(stderr, "Error writing key %s: %s (code %d)\n", + kv_inode_prefix_key(inode_id, "shared").c_str(), strerror(-res), res); + } + proxy->db->list_next(list_inodes, NULL); + }); + return; + } + } + proxy->db->list_next(list_inodes, NULL); + }); + }); +} diff --git a/src/nfs/nfs_kv_remove.cpp b/src/nfs/nfs_kv_remove.cpp index c6a55ad9..031cd2a1 100644 --- a/src/nfs/nfs_kv_remove.cpp +++ b/src/nfs/nfs_kv_remove.cpp @@ -228,28 +228,35 @@ resume_6: return; } // (6) If regular file and inode is deleted: delete data - if ((!st->type || st->type == NF3REG) && st->ientry["nlink"].uint64_value() <= 1 && - !st->ientry["shared_ino"].uint64_value()) + if ((!st->type || st->type == NF3REG) && st->ientry["nlink"].uint64_value() <= 1) { - // Remove data - st->self->parent->cmd->loop_and_wait(st->self->parent->cmd->start_rm_data(json11::Json::object { - { "inode", INODE_NO_POOL(st->ino) }, - { "pool", (uint64_t)INODE_POOL(st->ino) }, - }), [st](const cli_result_t & r) + if (!st->ientry["shared_ino"].uint64_value()) { - if (r.err) + // Remove data + st->self->parent->cmd->loop_and_wait(st->self->parent->cmd->start_rm_data(json11::Json::object { + { "inode", INODE_NO_POOL(st->ino) }, + { "pool", (uint64_t)INODE_POOL(st->ino) }, + }), [st](const cli_result_t & r) { - fprintf(stderr, "Failed to remove inode %jx data: %s (code %d)\n", - st->ino, r.text.c_str(), r.err); - } - st->res = r.err; - nfs_kv_continue_delete(st, 7); - }); - return; + if (r.err) + { + fprintf(stderr, "Failed to remove inode %jx data: %s (code %d)\n", + st->ino, r.text.c_str(), r.err); + } + st->res = r.err; + nfs_kv_continue_delete(st, 7); + }); + return; resume_7: - auto cb = std::move(st->cb); - cb(st->res); - return; + auto cb = std::move(st->cb); + cb(st->res); + return; + } + else + { + // Record removed part of the shared inode as obsolete in statistics + st->self->parent->kvfs->volume_removed[st->ientry["shared_ino"].uint64_value()] += st->ientry["shared_alloc"].uint64_value(); + } } if (!st->res) { diff --git a/src/nfs/nfs_kv_write.cpp b/src/nfs/nfs_kv_write.cpp index 55e18bc0..199a1287 100644 --- a/src/nfs/nfs_kv_write.cpp +++ b/src/nfs/nfs_kv_write.cpp @@ -8,17 +8,18 @@ #include "nfs_proxy.h" #include "nfs_kv.h" -// FIXME: Implement shared inode defragmentator // FIXME: Implement fsck for vitastor-fs and for vitastor-kv struct nfs_kv_write_state { - nfs_client_t *self = NULL; + nfs_proxy_t *proxy = NULL; rpc_op_t *rop = NULL; uint64_t ino = 0; uint64_t offset = 0, size = 0; bool stable = false; uint8_t *buf = NULL; + uint64_t force_move_from_inode = 0; // force move from this shared inode ID + uint64_t force_move_from_offset = 0; std::function cb; // state bool allow_cache = true; @@ -46,14 +47,14 @@ struct nfs_kv_write_state } }; -#define align_down(size) ((size) & ~(st->self->parent->kvfs->pool_alignment-1)) -#define align_up(size) (((size) + st->self->parent->kvfs->pool_alignment-1) & ~(st->self->parent->kvfs->pool_alignment-1)) +#define align_down(size) ((size) & ~(st->proxy->kvfs->pool_alignment-1)) +#define align_up(size) (((size) + st->proxy->kvfs->pool_alignment-1) & ~(st->proxy->kvfs->pool_alignment-1)) static void nfs_kv_continue_write(nfs_kv_write_state *st, int state); static void allocate_shared_space(nfs_kv_write_state *st) { - auto kvfs = st->self->parent->kvfs; + auto kvfs = st->proxy->kvfs; st->shared_inode = kvfs->cur_shared_inode; if (st->new_size < 3*kvfs->pool_alignment - sizeof(shared_file_header_t)) { @@ -67,13 +68,13 @@ static void allocate_shared_space(nfs_kv_write_state *st) st->shared_offset = align_up(kvfs->cur_shared_offset + sizeof(shared_file_header_t)) - sizeof(shared_file_header_t); st->shared_alloc = sizeof(shared_file_header_t) + align_up(st->new_size); } - st->self->parent->kvfs->cur_shared_offset = st->shared_offset + st->shared_alloc; + st->proxy->kvfs->cur_shared_offset = st->shared_offset + st->shared_alloc; } -static void finish_allocate_shared(nfs_client_t *self, int res) +static void finish_allocate_shared(nfs_proxy_t *proxy, int res) { std::vector waiting; - waiting.swap(self->parent->kvfs->allocating_shared); + waiting.swap(proxy->kvfs->allocating_shared); for (auto & w: waiting) { auto st = w.st; @@ -88,31 +89,44 @@ static void finish_allocate_shared(nfs_client_t *self, int res) static void allocate_shared_inode(nfs_kv_write_state *st, int state) { - if (st->self->parent->kvfs->cur_shared_inode == 0) + if (st->proxy->kvfs->cur_shared_inode == 0) { - st->self->parent->kvfs->allocating_shared.push_back({ st, state }); - if (st->self->parent->kvfs->allocating_shared.size() > 1) + st->proxy->kvfs->allocating_shared.push_back({ st, state }); + if (st->proxy->kvfs->allocating_shared.size() > 1) { return; } - allocate_new_id(st->self, st->self->parent->default_pool_id, [st](int res, uint64_t new_id) + allocate_new_id(st->proxy, st->proxy->default_pool_id, [st](int res, uint64_t new_id) { if (res < 0) { - finish_allocate_shared(st->self, res); + finish_allocate_shared(st->proxy, res); return; } - st->self->parent->kvfs->cur_shared_inode = new_id; - st->self->parent->kvfs->cur_shared_offset = 0; - st->self->parent->db->set( + st->proxy->kvfs->cur_shared_inode = new_id; + st->proxy->kvfs->volume_touch_ctr = 0; + st->proxy->kvfs->cur_shared_offset = 0; + st->proxy->db->set( kv_inode_key(new_id), json11::Json(json11::Json::object{ { "type", "shared" } }).dump(), [st](int res) { if (res < 0) { - st->self->parent->kvfs->cur_shared_inode = 0; + st->proxy->kvfs->cur_shared_inode = 0; + finish_allocate_shared(st->proxy, res); + } + else + { + st->proxy->db->set( + kv_inode_prefix_key(st->proxy->kvfs->cur_shared_inode, "shared"), + "{}", [st](int res) + { + if (res < 0) + st->proxy->kvfs->cur_shared_inode = 0; + finish_allocate_shared(st->proxy, res); + } + ); } - finish_allocate_shared(st->self, res); }, [](int res, const std::string & old_value) { @@ -151,7 +165,7 @@ static void nfs_do_write(uint64_t ino, uint64_t offset, uint64_t size, std::func nfs_kv_continue_write(st, state); } }; - st->self->parent->cli->execute(op); + st->proxy->cli->execute(op); } static void nfs_do_unshare_write(nfs_kv_write_state *st, int state) @@ -162,7 +176,7 @@ static void nfs_do_unshare_write(nfs_kv_write_state *st, int state) { op->iov.push_back(st->aligned_buf, size); if (aligned_size > size) - op->iov.push_back(st->self->parent->kvfs->zero_block.data(), aligned_size-size); + op->iov.push_back(st->proxy->kvfs->zero_block.data(), aligned_size-size); }, st, state); } @@ -269,7 +283,7 @@ static void nfs_do_shared_read(nfs_kv_write_state *st, int state) auto pre = shared_offset-align_down(shared_offset); if (pre > 0) { - op->iov.push_back(st->self->parent->kvfs->scrap_block.data(), pre); + op->iov.push_back(st->proxy->kvfs->scrap_block.data(), pre); } op->iov.push_back(&st->shdr, sizeof(shared_file_header_t)); op->iov.push_back(st->aligned_buf, data_size); @@ -277,7 +291,7 @@ static void nfs_do_shared_read(nfs_kv_write_state *st, int state) post = align_up(post) - post; if (post > 0) { - op->iov.push_back(st->self->parent->kvfs->scrap_block.data(), post); + op->iov.push_back(st->proxy->kvfs->scrap_block.data(), post); } op->len = pre+sizeof(shared_file_header_t)+data_size+post; op->callback = [st, state](cluster_op_t *op) @@ -302,7 +316,7 @@ static void nfs_do_shared_read(nfs_kv_write_state *st, int state) nfs_kv_continue_write(st, state); } }; - st->self->parent->cli->execute(op); + st->proxy->cli->execute(op); } static void nfs_do_fsync(nfs_kv_write_state *st, int state) @@ -315,10 +329,10 @@ static void nfs_do_fsync(nfs_kv_write_state *st, int state) delete op; nfs_kv_continue_write(st, state); }; - st->self->parent->cli->execute(op); + st->proxy->cli->execute(op); } -static bool nfs_do_shared_readmodify(nfs_kv_write_state *st, int base_state, int state, bool unshare) +static bool nfs_do_shared_readmodify(nfs_kv_write_state *st, int base_state, int state) { assert(state <= base_state); if (state < base_state) goto resume_0; @@ -382,7 +396,7 @@ static void nfs_do_shared_write(nfs_kv_write_state *st, int state) if (unaligned_is_free && aligned_offset < write_offset) { // zero padding - op->iov.push_back(st->self->parent->kvfs->zero_block.data(), write_offset-aligned_offset); + op->iov.push_back(st->proxy->kvfs->zero_block.data(), write_offset-aligned_offset); } // header op->iov.push_back(&st->shdr, sizeof(shared_file_header_t)); @@ -394,10 +408,13 @@ static void nfs_do_shared_write(nfs_kv_write_state *st, int state) op->iov.push_back(st->aligned_buf, st->offset); } else - add_zero(op, st->offset, st->self->parent->kvfs->zero_block); + add_zero(op, st->offset, st->proxy->kvfs->zero_block); } // new data - op->iov.push_back(st->buf, st->size); + if (st->size > 0) + { + op->iov.push_back(st->buf, st->size); + } if (st->offset+st->size < st->new_size) { if (has_old) @@ -406,19 +423,19 @@ static void nfs_do_shared_write(nfs_kv_write_state *st, int state) op->iov.push_back(st->aligned_buf+st->offset+st->size, st->new_size-(st->offset+st->size)); } else - add_zero(op, st->offset, st->self->parent->kvfs->zero_block); + add_zero(op, st->offset, st->proxy->kvfs->zero_block); } if (unaligned_is_free && (aligned_size+aligned_offset) > (write_size+write_offset)) { // zero padding - op->iov.push_back(st->self->parent->kvfs->zero_block.data(), aligned_size+aligned_offset - (write_size+write_offset)); + op->iov.push_back(st->proxy->kvfs->zero_block.data(), aligned_size+aligned_offset - (write_size+write_offset)); } }, st, state); } static void nfs_do_align_write(nfs_kv_write_state *st, uint64_t ino, uint64_t offset, uint64_t shared_alloc, int state) { - auto alignment = st->self->parent->kvfs->pool_alignment; + auto alignment = st->proxy->kvfs->pool_alignment; uint64_t end = (offset+st->size); uint8_t *good_buf = st->buf; uint64_t good_offset = offset; @@ -467,7 +484,7 @@ static void nfs_do_align_write(nfs_kv_write_state *st, uint64_t ino, uint64_t of good_size = 0; s = s > st->size ? st->size : s; st->rmw[0] = (nfs_rmw_t){ - .parent = st->self->parent, + .parent = st->proxy, .ino = ino, .offset = offset, .buf = st->buf, @@ -496,7 +513,7 @@ static void nfs_do_align_write(nfs_kv_write_state *st, uint64_t ino, uint64_t of else good_size = 0; st->rmw[1] = (nfs_rmw_t){ - .parent = st->self->parent, + .parent = st->proxy, .ino = ino, .offset = end - s, .buf = st->buf + st->size - s, @@ -521,7 +538,7 @@ static void nfs_do_align_write(nfs_kv_write_state *st, uint64_t ino, uint64_t of op->iov.push_back(&st->shdr, sizeof(shared_file_header_t)); op->iov.push_back(good_buf, good_size); if (end_pad) - op->iov.push_back(st->self->parent->kvfs->zero_block.data(), end_pad); + op->iov.push_back(st->proxy->kvfs->zero_block.data(), end_pad); }, st, state); } st->waiting--; @@ -590,7 +607,7 @@ static void nfs_kv_extend_inode(nfs_kv_write_state *st, int state, int base_stat st->ext->cur_extend = st->ext->next_extend; st->ext->next_extend = 0; st->res2 = -EAGAIN; - st->self->parent->db->set(kv_inode_key(st->ino), new_normal_ientry(st), [st, base_state](int res) + st->proxy->db->set(kv_inode_key(st->ino), new_normal_ientry(st), [st, base_state](int res) { st->res = res; nfs_kv_continue_write(st, base_state+1); @@ -738,13 +755,13 @@ static void nfs_kv_continue_write(nfs_kv_write_state *st, int state) abort(); } resume_0: - if (!st->size) + if (!st->size && !st->force_move_from_inode) { auto cb = std::move(st->cb); cb(0); return; } - kv_read_inode(st->self->parent, st->ino, [st](int res, const std::string & value, json11::Json attrs) + kv_read_inode(st->proxy, st->ino, [st](int res, const std::string & value, json11::Json attrs) { st->res = res; st->ientry_text = value; @@ -759,22 +776,30 @@ resume_1: cb(st->res == 0 ? -EINVAL : st->res); return; } - st->was_immediate = st->self->parent->cli->get_immediate_commit(st->ino); + st->was_immediate = st->proxy->cli->get_immediate_commit(st->ino); st->new_size = st->ientry["size"].uint64_value(); if (st->new_size < st->offset + st->size) { st->new_size = st->offset + st->size; } - if (st->offset + st->size + sizeof(shared_file_header_t) < st->self->parent->kvfs->shared_inode_threshold) + if (st->offset + st->size + sizeof(shared_file_header_t) < st->proxy->kvfs->shared_inode_threshold) { - if (st->ientry["size"].uint64_value() == 0 && - st->ientry["shared_ino"].uint64_value() == 0 || + if (// Zero size, should be allocated to handle write + st->ientry["size"].uint64_value() == 0 && + st->ientry["shared_ino"].uint64_value() == 0 && + st->offset+st->size > 0 || + // Empty with non-zero size, fits shared inode threshold st->ientry["empty"].bool_value() && - (st->ientry["size"].uint64_value() + sizeof(shared_file_header_t)) < st->self->parent->kvfs->shared_inode_threshold || + (st->ientry["size"].uint64_value() + sizeof(shared_file_header_t)) < st->proxy->kvfs->shared_inode_threshold || + // Shared, does not fit currently allocated shared inode space st->ientry["shared_ino"].uint64_value() != 0 && - st->ientry["shared_alloc"].uint64_value() < sizeof(shared_file_header_t)+st->offset+st->size) + st->ientry["shared_alloc"].uint64_value() < sizeof(shared_file_header_t)+st->offset+st->size || + // Shared, requested to be moved away forcibly by defrag + st->force_move_from_inode != 0 && + st->ientry["shared_ino"].uint64_value() == st->force_move_from_inode && + st->ientry["shared_offset"].uint64_value() == st->force_move_from_offset) { - // Either empty, or shared and requires moving into a larger place (redirect-write) + // Inode requires moving into a larger place (redirect-write) allocate_shared_inode(st, 2); return; resume_2: @@ -785,7 +810,7 @@ resume_2: return; } resume_3: - if (!nfs_do_shared_readmodify(st, 3, state, false)) + if (!nfs_do_shared_readmodify(st, 3, state)) { return; } @@ -803,7 +828,7 @@ resume_4: cb(st->res); return; } - st->self->parent->db->set(kv_inode_key(st->ino), new_moved_ientry(st), [st](int res) + st->proxy->db->set(kv_inode_key(st->ino), new_moved_ientry(st), [st](int res) { st->res = res; nfs_kv_continue_write(st, 5); @@ -831,7 +856,7 @@ resume_5: cb(0); return; } - else if (st->ientry["shared_ino"].uint64_value() != 0) + else if (st->ientry["shared_ino"].uint64_value() != 0 && st->size > 0) { // Non-empty, shared, can be updated in-place nfs_do_align_write(st, st->ientry["shared_ino"].uint64_value(), @@ -846,7 +871,7 @@ resume_7: } resume_8: // We always have to change inode entry on shared writes - st->self->parent->db->set(kv_inode_key(st->ino), new_shared_ientry(st), [st](int res) + st->proxy->db->set(kv_inode_key(st->ino), new_shared_ientry(st), [st](int res) { st->res = res; nfs_kv_continue_write(st, 9); @@ -866,6 +891,12 @@ resume_9: } // Fall through for non-shared } + if (!st->size) + { + auto cb = std::move(st->cb); + cb(0); + return; + } // Unshare? if (st->ientry["shared_ino"].uint64_value() != 0) { @@ -889,7 +920,7 @@ resume_11: return; } } - st->self->parent->db->set(kv_inode_key(st->ino), new_unshared_ientry(st), [st](int res) + st->proxy->db->set(kv_inode_key(st->ino), new_unshared_ientry(st), [st](int res) { st->res = res; nfs_kv_continue_write(st, 12); @@ -910,6 +941,8 @@ resume_12: cb(st->res); return; } + // Record removed part of the shared inode as obsolete in statistics + st->proxy->kvfs->volume_removed[st->ientry["shared_ino"].uint64_value()] += st->ientry["shared_alloc"].uint64_value(); st->ientry_text = new_unshared_ientry(st); } // Non-shared write @@ -932,7 +965,7 @@ resume_14: st->ientry["size"].uint64_value() < st->new_size || st->ientry["shared_ino"].uint64_value() != 0) { - st->ext = &st->self->parent->kvfs->extends[st->ino]; + st->ext = &st->proxy->kvfs->extends[st->ino]; st->ext->refcnt++; resume_15: if (st->ext->next_extend < st->new_size) @@ -957,12 +990,12 @@ resume_16: assert(st->ext->refcnt >= 0); if (st->ext->refcnt == 0) { - st->self->parent->kvfs->extends.erase(st->ino); + st->proxy->kvfs->extends.erase(st->ino); } } else { - st->self->parent->kvfs->touch_queue.insert(st->ino); + st->proxy->kvfs->touch_queue.insert(st->ino); } if (st->res == -EAGAIN) { @@ -976,15 +1009,16 @@ resume_16: int kv_nfs3_write_proc(void *opaque, rpc_op_t *rop) { nfs_kv_write_state *st = new nfs_kv_write_state; - st->self = (nfs_client_t*)opaque; + nfs_client_t *self = (nfs_client_t*)opaque; + st->proxy = ((nfs_client_t*)opaque)->parent; st->rop = rop; WRITE3args *args = (WRITE3args*)rop->request; WRITE3res *reply = (WRITE3res*)rop->reply; st->ino = kv_fh_inode(args->file); st->offset = args->offset; st->size = (args->count > args->data.size ? args->data.size : args->count); - if (st->self->parent->trace) - fprintf(stderr, "[%d] WRITE %ju %ju+%ju\n", st->self->nfs_fd, st->ino, st->offset, st->size); + if (st->proxy->trace) + fprintf(stderr, "[%d] WRITE %ju %ju+%ju\n", self->nfs_fd, st->ino, st->offset, st->size); if (!st->ino || st->size > MAX_REQUEST_SIZE) { *reply = (WRITE3res){ .status = NFS3ERR_INVAL }; @@ -1002,7 +1036,7 @@ int kv_nfs3_write_proc(void *opaque, rpc_op_t *rop) { reply->resok.count = (unsigned)st->size; reply->resok.committed = st->stable || st->was_immediate ? FILE_SYNC : UNSTABLE; - *(uint64_t*)reply->resok.verf = st->self->parent->server_id; + *(uint64_t*)reply->resok.verf = st->proxy->server_id; } rpc_queue_reply(st->rop); delete st; @@ -1010,3 +1044,23 @@ int kv_nfs3_write_proc(void *opaque, rpc_op_t *rop) nfs_kv_continue_write(st, 0); return 1; } + +void nfs_move_inode_from(nfs_proxy_t *proxy, uint64_t ino, uint64_t shared_ino, uint64_t shared_offset, std::function cb) +{ + nfs_kv_write_state *st = new nfs_kv_write_state; + st->proxy = proxy; + st->ino = ino; + st->offset = 0; + st->size = 0; + st->force_move_from_inode = shared_ino; + st->force_move_from_offset = shared_offset; + st->buf = NULL; + st->stable = true; + st->allow_cache = false; + st->cb = [cb, st](int res) + { + cb(res, st->shared_inode != 0); + delete st; + }; + nfs_kv_continue_write(st, 0); +} diff --git a/src/nfs/nfs_proxy.cpp b/src/nfs/nfs_proxy.cpp index 65496d45..215e28f5 100644 --- a/src/nfs/nfs_proxy.cpp +++ b/src/nfs/nfs_proxy.cpp @@ -68,6 +68,32 @@ static const char* help_text = " --port use port for NFS services (default is 2049)\n" " --portmap 0 do not listen on port 111 (portmap/rpcbind, requires root)\n" "\n" + "vitastor-nfs --fs upgrade\n" + " Upgrade FS metadata. Can be run online, but server should be restarted\n" + " after upgrade.\n" + "\n" + "vitastor-nfs --fs defrag [OPTIONS] [--dry-run]\n" + " Defragment volumes used for small file storage having more than\n" + " %% of data removed. Can be run online. Options:\n" + " --volume_untouched 86400\n" + " Defragment volumes last appended to at least this number of seconds ago\n" + " --defrag_percent 50\n" + " Defragment volumes with at least this %% of removed data\n" + " --defrag_block_count 16\n" + " Read this number of pool blocks at once during defrag\n" + " --defrag_iodepth 16\n" + " Move up to this number of files in parallel during defrag\n" + " --trace\n" + " Print verbose defragmentation status\n" + " --dry-run\n" + " Skip modifications, only print status\n" + " --recalc-stats\n" + " Recalculate all volume statistics\n" + " --include-empty\n" + " Include old and empty volumes; make sure to restart NFS servers before using it\n" + " --no-rm\n" + " Move, but do not delete data\n" + "\n" "OPTIONS:\n" " --fs use VitastorFS with metadata in image \n" " --block use pseudo-FS presenting images as files\n" @@ -113,7 +139,9 @@ json11::Json::object nfs_proxy_t::parse_args(int narg, const char *args[]) else if (args[i][0] == '-' && args[i][1] == '-') { const char *opt = args[i]+2; - cfg[opt] = !strcmp(opt, "json") || !strcmp(opt, "block") || i == narg-1 ? "1" : args[++i]; + cfg[str_replace(opt, "-", "_")] = !strcmp(opt, "json") || !strcmp(opt, "block") || + !strcmp(opt, "dry-run") || !strcmp(opt, "recalc-stats") || + !strcmp(opt, "include-empty") || !strcmp(opt, "no-rm") || i == narg-1 ? "1" : args[++i]; } else { @@ -132,6 +160,10 @@ json11::Json::object nfs_proxy_t::parse_args(int narg, const char *args[]) else if (cmd.size() >= 1 && cmd[0] == "start") { } + else if (cmd.size() >= 1 && (cmd[0] == "upgrade" || cmd[0] == "defrag") && cfg["fs"].string_value() != "") + { + cfg["cmd"] = cmd[0]; + } else { printf("%s", help_text); @@ -211,6 +243,50 @@ void nfs_proxy_t::run(json11::Json cfg) kvfs = new kv_fs_state_t(); kvfs->init(this, cfg); } + if (cfg["cmd"].is_null()) + { + run_server(cfg); + } + else if (cfg["cmd"] == "defrag") + { + kvfs->defrag_all(cfg, [this](int res) { finished = true; }); + } + else if (cfg["cmd"] == "upgrade") + { + kvfs->upgrade_db([this](int res) { finished = true; }); + } + while (!finished) + { + ringloop->loop(); + ringloop->wait(); + } + // Destroy the client + cli->flush(); + if (kvfs) + { + delete kvfs; + kvfs = NULL; + } + if (blockfs) + { + delete blockfs; + blockfs = NULL; + } + if (db) + { + delete db; + db = NULL; + } + delete cli; + delete epmgr; + delete ringloop; + cli = NULL; + epmgr = NULL; + ringloop = NULL; +} + +void nfs_proxy_t::run_server(json11::Json cfg) +{ // Self-register portmap and NFS pmap.reg_ports.insert((portmap_id_t){ .prog = PMAP_PROGRAM, @@ -285,34 +361,6 @@ void nfs_proxy_t::run(json11::Json cfg) { write_pid(); } - while (!finished) - { - ringloop->loop(); - ringloop->wait(); - } - // Destroy the client - cli->flush(); - if (kvfs) - { - delete kvfs; - kvfs = NULL; - } - if (blockfs) - { - delete blockfs; - blockfs = NULL; - } - if (db) - { - delete db; - db = NULL; - } - delete cli; - delete epmgr; - delete ringloop; - cli = NULL; - epmgr = NULL; - ringloop = NULL; } void nfs_proxy_t::watch_stats() diff --git a/src/nfs/nfs_proxy.h b/src/nfs/nfs_proxy.h index 86f4eb4b..10e58736 100644 --- a/src/nfs/nfs_proxy.h +++ b/src/nfs/nfs_proxy.h @@ -66,6 +66,7 @@ public: static json11::Json::object parse_args(int narg, const char *args[]); void run(json11::Json cfg); + void run_server(json11::Json cfg); void watch_stats(); void parse_stats(etcd_kv_t & kv); void check_default_pool(); diff --git a/src/util/str_util.cpp b/src/util/str_util.cpp index 4505f2eb..505f73ff 100644 --- a/src/util/str_util.cpp +++ b/src/util/str_util.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "str_util.h" @@ -474,3 +475,13 @@ std::string realpath_str(std::string path, bool nofail) free(p); return rp; } + +std::string format_datetime(uint64_t unixtime) +{ + char buf[128]; + time_t ut = (time_t)unixtime; + tm lt; + localtime_r(&ut, <); + int len = strftime(buf, 128, "%Y-%m-%d %H:%M:%S", <); + return std::string(buf, len); +} diff --git a/src/util/str_util.h b/src/util/str_util.h index b283e7ee..c789627b 100644 --- a/src/util/str_util.h +++ b/src/util/str_util.h @@ -30,3 +30,4 @@ std::string scan_escaped(const std::string & cmd, size_t & pos, bool allow_unquo std::string auto_addslashes(const std::string & str, const char *toescape = "\\\""); std::string addslashes(const std::string & str, const char *toescape = "\\\""); std::string realpath_str(std::string path, bool nofail = true); +std::string format_datetime(uint64_t unixtime);