// Copyright (c) Vitaliy Filippov, 2019+ // License: VNPL-1.1 (see README.md for details) // // NFS proxy over VitastorKV database - common functions #include #include "str_util.h" #include "nfs_proxy.h" #include "nfs_common.h" #include "nfs_kv.h" nfstime3 nfstime_from_str(const std::string & s) { nfstime3 t; auto p = s.find("."); if (p != std::string::npos) { t.seconds = stoull_full(s.substr(0, p), 10); t.nseconds = stoull_full(s.substr(p+1), 10); p = s.size()-p-1; for (; p < 9; p++) t.nseconds *= 10; for (; p > 9; p--) t.nseconds /= 10; } else { t.seconds = stoull_full(s, 10); t.nseconds = 0; } return t; } static std::string timespec_to_str(timespec t) { char buf[64]; snprintf(buf, sizeof(buf), "%ju.%09ju", t.tv_sec, t.tv_nsec); int l = strlen(buf); while (l > 0 && buf[l-1] == '0') l--; if (l > 0 && buf[l-1] == '.') l--; buf[l] = 0; return buf; } std::string nfstime_to_str(nfstime3 t) { return timespec_to_str((timespec){ .tv_sec = t.seconds, .tv_nsec = t.nseconds }); } std::string nfstime_now_str() { timespec t; clock_gettime(CLOCK_REALTIME, &t); return timespec_to_str(t); } int kv_map_type(const std::string & type) { return (type == "" || type == "file" ? NF3REG : (type == "dir" ? NF3DIR : (type == "blk" ? NF3BLK : (type == "chr" ? NF3CHR : (type == "link" ? NF3LNK : (type == "sock" ? NF3SOCK : (type == "fifo" ? NF3FIFO : -1))))))); } fattr3 get_kv_attributes(nfs_proxy_t *proxy, uint64_t ino, json11::Json attrs) { auto type = kv_map_type(attrs["type"].string_value()); auto mode = attrs["mode"].uint64_value(); auto nlink = attrs["nlink"].uint64_value(); nfstime3 mtime = nfstime_from_str(attrs["mtime"].string_value()); nfstime3 atime = attrs["atime"].is_null() ? mtime : nfstime_from_str(attrs["atime"].string_value()); nfstime3 ctime = attrs["ctime"].is_null() ? mtime : nfstime_from_str(attrs["ctime"].string_value()); // In theory we could store the binary structure itself, but JSON is simpler :-) return (fattr3){ .type = (type == 0 ? NF3REG : (ftype3)type), .mode = (attrs["mode"].is_null() ? (type == NF3DIR ? 0755 : 0644) : (uint32_t)mode), .nlink = (nlink == 0 ? 1 : (uint32_t)nlink), .uid = (uint32_t)attrs["uid"].uint64_value(), .gid = (uint32_t)attrs["gid"].uint64_value(), .size = (type == NF3DIR ? 4096 : attrs["size"].uint64_value()), // FIXME Counting actual used file size would require reworking statistics .used = (type == NF3DIR ? 4096 : attrs["size"].uint64_value()), .rdev = (type == NF3BLK || type == NF3CHR ? (specdata3){ (uint32_t)attrs["major"].uint64_value(), (uint32_t)attrs["minor"].uint64_value() } : (specdata3){}), .fsid = proxy->fsid, .fileid = ino, .atime = atime, .mtime = mtime, .ctime = ctime, }; } std::string kv_direntry_key(uint64_t dir_ino, const std::string & filename) { // encode as: d / char key[24] = { 0 }; snprintf(key, sizeof(key), "d-%jx/", dir_ino); int n = strnlen(key, sizeof(key)-1) - 3; if (n < 10) key[1] = '0'+n; else key[1] = 'A'+(n-10); return (char*)key + filename; } std::string kv_direntry_filename(const std::string & key) { // decode as: d / auto pos = key.find("/"); if (pos != std::string::npos) return key.substr(pos+1); return key; } std::string kv_inode_prefix_key(uint64_t ino, const char *prefix) { int max = 32+strlen(prefix); char key[max]; 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 }; snprintf(key, sizeof(key), "S%jx", ino); return key; } uint64_t kv_fh_inode(const std::string & fh) { if (fh == NFS_ROOT_HANDLE) { return 1; } else if (fh[0] == 'S') { uint64_t ino = 0; int r = sscanf(fh.c_str()+1, "%jx", &ino); if (r == 1) return ino; } return 0; } bool kv_fh_valid(const std::string & fh) { return fh == NFS_ROOT_HANDLE || fh[0] == 'S'; } void nfs_kv_procs(nfs_client_t *self) { struct rpc_service_proc_t pt[] = { {NFS_PROGRAM, NFS_V3, NFS3_NULL, nfs3_null_proc, NULL, 0, NULL, 0, self}, {NFS_PROGRAM, NFS_V3, NFS3_GETATTR, kv_nfs3_getattr_proc, (xdrproc_t)xdr_GETATTR3args, sizeof(GETATTR3args), (xdrproc_t)xdr_GETATTR3res, sizeof(GETATTR3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_SETATTR, kv_nfs3_setattr_proc, (xdrproc_t)xdr_SETATTR3args, sizeof(SETATTR3args), (xdrproc_t)xdr_SETATTR3res, sizeof(SETATTR3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_LOOKUP, kv_nfs3_lookup_proc, (xdrproc_t)xdr_LOOKUP3args, sizeof(LOOKUP3args), (xdrproc_t)xdr_LOOKUP3res, sizeof(LOOKUP3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_ACCESS, kv_nfs3_access_proc, (xdrproc_t)xdr_ACCESS3args, sizeof(ACCESS3args), (xdrproc_t)xdr_ACCESS3res, sizeof(ACCESS3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_READLINK, kv_nfs3_readlink_proc, (xdrproc_t)xdr_READLINK3args, sizeof(READLINK3args), (xdrproc_t)xdr_READLINK3res, sizeof(READLINK3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_READ, kv_nfs3_read_proc, (xdrproc_t)xdr_READ3args, sizeof(READ3args), (xdrproc_t)xdr_READ3res, sizeof(READ3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_WRITE, kv_nfs3_write_proc, (xdrproc_t)xdr_WRITE3args, sizeof(WRITE3args), (xdrproc_t)xdr_WRITE3res, sizeof(WRITE3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_CREATE, kv_nfs3_create_proc, (xdrproc_t)xdr_CREATE3args, sizeof(CREATE3args), (xdrproc_t)xdr_CREATE3res, sizeof(CREATE3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_MKDIR, kv_nfs3_mkdir_proc, (xdrproc_t)xdr_MKDIR3args, sizeof(MKDIR3args), (xdrproc_t)xdr_MKDIR3res, sizeof(MKDIR3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_SYMLINK, kv_nfs3_symlink_proc, (xdrproc_t)xdr_SYMLINK3args, sizeof(SYMLINK3args), (xdrproc_t)xdr_SYMLINK3res, sizeof(SYMLINK3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_MKNOD, kv_nfs3_mknod_proc, (xdrproc_t)xdr_MKNOD3args, sizeof(MKNOD3args), (xdrproc_t)xdr_MKNOD3res, sizeof(MKNOD3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_REMOVE, kv_nfs3_remove_proc, (xdrproc_t)xdr_REMOVE3args, sizeof(REMOVE3args), (xdrproc_t)xdr_REMOVE3res, sizeof(REMOVE3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_RMDIR, kv_nfs3_rmdir_proc, (xdrproc_t)xdr_RMDIR3args, sizeof(RMDIR3args), (xdrproc_t)xdr_RMDIR3res, sizeof(RMDIR3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_RENAME, kv_nfs3_rename_proc, (xdrproc_t)xdr_RENAME3args, sizeof(RENAME3args), (xdrproc_t)xdr_RENAME3res, sizeof(RENAME3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_LINK, kv_nfs3_link_proc, (xdrproc_t)xdr_LINK3args, sizeof(LINK3args), (xdrproc_t)xdr_LINK3res, sizeof(LINK3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_READDIR, kv_nfs3_readdir_proc, (xdrproc_t)xdr_READDIR3args, sizeof(READDIR3args), (xdrproc_t)xdr_READDIR3res, sizeof(READDIR3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_READDIRPLUS, kv_nfs3_readdirplus_proc, (xdrproc_t)xdr_READDIRPLUS3args, sizeof(READDIRPLUS3args), (xdrproc_t)xdr_READDIRPLUS3res, sizeof(READDIRPLUS3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_FSSTAT, nfs3_fsstat_proc, (xdrproc_t)xdr_FSSTAT3args, sizeof(FSSTAT3args), (xdrproc_t)xdr_FSSTAT3res, sizeof(FSSTAT3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_FSINFO, nfs3_fsinfo_proc, (xdrproc_t)xdr_FSINFO3args, sizeof(FSINFO3args), (xdrproc_t)xdr_FSINFO3res, sizeof(FSINFO3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_PATHCONF, nfs3_pathconf_proc, (xdrproc_t)xdr_PATHCONF3args, sizeof(PATHCONF3args), (xdrproc_t)xdr_PATHCONF3res, sizeof(PATHCONF3res), self}, {NFS_PROGRAM, NFS_V3, NFS3_COMMIT, nfs3_commit_proc, (xdrproc_t)xdr_COMMIT3args, sizeof(COMMIT3args), (xdrproc_t)xdr_COMMIT3res, sizeof(COMMIT3res), self}, {MOUNT_PROGRAM, MOUNT_V3, MOUNT3_NULL, nfs3_null_proc, NULL, 0, NULL, 0, self}, {MOUNT_PROGRAM, MOUNT_V3, MOUNT3_MNT, mount3_mnt_proc, (xdrproc_t)xdr_nfs_dirpath, sizeof(nfs_dirpath), (xdrproc_t)xdr_nfs_mountres3, sizeof(nfs_mountres3), self}, {MOUNT_PROGRAM, MOUNT_V3, MOUNT3_DUMP, mount3_dump_proc, NULL, 0, (xdrproc_t)xdr_nfs_mountlist, sizeof(nfs_mountlist), self}, {MOUNT_PROGRAM, MOUNT_V3, MOUNT3_UMNT, mount3_umnt_proc, (xdrproc_t)xdr_nfs_dirpath, sizeof(nfs_dirpath), NULL, 0, self}, {MOUNT_PROGRAM, MOUNT_V3, MOUNT3_UMNTALL, mount3_umntall_proc, NULL, 0, NULL, 0, self}, {MOUNT_PROGRAM, MOUNT_V3, MOUNT3_EXPORT, mount3_export_proc, NULL, 0, (xdrproc_t)xdr_nfs_exports, sizeof(nfs_exports), self}, }; for (int i = 0; i < sizeof(pt)/sizeof(pt[0]); i++) { self->proc_table.insert(pt[i]); } } void kv_fs_state_t::init(nfs_proxy_t *proxy, json11::Json cfg) { this->proxy = proxy; auto & pool_cfg = proxy->cli->st_cli.pool_config.at(proxy->default_pool_id); fs_kv_inode = cfg["fs"].uint64_value(); if (fs_kv_inode) { if (!INODE_POOL(fs_kv_inode)) { fprintf(stderr, "FS metadata inode number must include pool\n"); exit(1); } } else { for (auto & ic: proxy->cli->st_cli.inode_config) { if (ic.second.name == cfg["fs"].string_value()) { fs_kv_inode = ic.first; break; } } if (!fs_kv_inode) { fprintf(stderr, "FS metadata image \"%s\" does not exist\n", cfg["fs"].string_value().c_str()); exit(1); } } if (proxy->cli->st_cli.inode_config.find(fs_kv_inode) != proxy->cli->st_cli.inode_config.end()) { auto & name = proxy->cli->st_cli.inode_config.at(fs_kv_inode).name; if (pool_cfg.used_for_app != "fs:"+name) { fprintf(stderr, "Please mark pool as used for this file system with `vitastor-cli modify-pool --used-for-app fs:%s %s`\n", name.c_str(), pool_cfg.name.c_str()); exit(1); } } auto img_it = proxy->cli->st_cli.inode_config.lower_bound(INODE_WITH_POOL(proxy->default_pool_id+1, 0)); if (img_it != proxy->cli->st_cli.inode_config.begin()) { img_it--; if (img_it != proxy->cli->st_cli.inode_config.begin() && INODE_POOL(img_it->first) == proxy->default_pool_id) { idgen[proxy->default_pool_id].min_id = INODE_NO_POOL(img_it->first) + 1; } } readdir_getattr_parallel = cfg["readdir_getattr_parallel"].uint64_value(); if (!readdir_getattr_parallel) readdir_getattr_parallel = 8; id_alloc_batch_size = cfg["id_alloc_batch_size"].uint64_value(); if (!id_alloc_batch_size) id_alloc_batch_size = 200; touch_interval = cfg["touch_interval"].uint64_value(); 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 int open_res = 0; bool open_done = false; proxy->db = new vitastorkv_dbw_t(proxy->cli); std::map kv_cfg; for (auto & kv: cfg.object_items()) { 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; open_res = res; }); while (!open_done) { proxy->ringloop->loop(); if (open_done) break; proxy->ringloop->wait(); } if (open_res < 0) { fprintf(stderr, "Failed to open key/value filesystem metadata index: %s (code %d)\n", 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()) { shared_inode_threshold = cfg["shared_inode_threshold"].uint64_value(); } zero_block.resize(pool_block_size < 1048576 ? 1048576 : pool_block_size); scrap_block.resize(pool_block_size < 1048576 ? 1048576 : pool_block_size); touch_timer_id = proxy->epmgr->tfd->set_timer(touch_interval, true, [this](int){ touch_inodes(); }); } kv_fs_state_t::~kv_fs_state_t() { if (proxy && touch_timer_id >= 0) { proxy->epmgr->tfd->clear_timer(touch_timer_id); touch_timer_id = -1; } } void kv_fs_state_t::write_inode(inode_t ino, json11::Json value, bool hack_cache, std::function cb, std::function cas_cb) { if (!proxy->rdma_context) { proxy->db->set(kv_inode_key(ino), value.dump(), cb, cas_cb); return; } // FIXME Linux NFS RDMA transport has a bug - it corrupts the data (by offsetting it 84 bytes) // when the READ reply doesn't contain post_op_attr. So we have to fill post_op_attr with RDMA. :-( // So we at least cache it to not repeat K/V requests every read. read_hack_cache.erase(ino); proxy->db->set(kv_inode_key(ino), value.dump(), [=](int res) { if (hack_cache || res != 0) read_hack_cache.erase(ino); else read_hack_cache[ino] = value; cb(res); }, cas_cb); } void kv_fs_state_t::update_inode(inode_t ino, bool allow_cache, std::function change, std::function cb) { // FIXME: Use "update" query kv_read_inode(proxy, ino, [=](int res, const std::string & value, json11::Json attrs) { if (!res) { read_hack_cache.erase(ino); auto ientry = attrs.object_items(); change(ientry); bool *found = new bool; *found = true; json11::Json ientry_json(ientry); proxy->db->set(kv_inode_key(ino), ientry_json.dump(), [=](int res) { read_hack_cache.erase(ino); if (!*found) res = -ENOENT; delete found; if (res == -EAGAIN) 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); } void kv_fs_state_t::touch_inodes() { // Clear RDMA read fattr3 "hack" cache every second read_hack_cache.clear(); std::set q = std::move(touch_queue); for (auto ino: q) { 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); } }