From 835929128234187379fc19019bf76b39c10ff4c9 Mon Sep 17 00:00:00 2001 From: Xun Chen Date: Mon, 26 Jan 2026 21:08:03 +0800 Subject: [PATCH] Implement create_snapshot interface for ImageFile Signed-off-by: Xun Chen --- src/image_file.cpp | 54 +++++++- src/image_file.h | 8 +- src/overlaybd/lsmt/file.cpp | 38 +++++- src/overlaybd/lsmt/file.h | 8 ++ src/overlaybd/lsmt/index.cpp | 18 ++- src/overlaybd/lsmt/index.h | 1 + src/test/image_service_test.cpp | 230 +++++++++++++++++++++++++++++++- 7 files changed, 341 insertions(+), 16 deletions(-) diff --git a/src/image_file.cpp b/src/image_file.cpp index df3169b9..654c5242 100644 --- a/src/image_file.cpp +++ b/src/image_file.cpp @@ -334,12 +334,12 @@ LSMT::IFileRO *ImageFile::open_lowers(std::vector &l return NULL; photon::join_handle *ths[PARALLEL_LOAD_INDEX]; - std::vector files; + std::vector files; // layer0 ... layerN-1 files.resize(lowers.size(), nullptr); auto n = std::min(PARALLEL_LOAD_INDEX, (int)lowers.size()); LOG_DEBUG("create ` photon threads to open lowers", n); - ParallelOpenTask tm(files, lowers.size(), lowers); + ParallelOpenTask tm(files, lowers.size(), lowers); // layer0 ... layerN-1 for (auto i = 0; i < n; ++i) { ths[i] = photon::thread_enable_join(photon::thread_create11(&do_parallel_open_files, this, tm)); @@ -443,7 +443,7 @@ int ImageFile::init_image_file() { ImageConfigNS::UpperConfig upper; bool record_no_download = false; bool has_error = false; - auto lowers = conf.lowers(); + auto lowers = conf.lowers(); // layer0 ... layerN-1 auto concurrency = image_service.global_conf.prefetchConfig().concurrency(); if (conf.accelerationLayer() && !conf.recordTracePath().empty()) { @@ -554,3 +554,51 @@ void ImageFile::set_failed(const Ts &...xs) { m_exception = estring().appends(xs...); } } + +int ImageFile::create_snapshot(const char *config_path) { + // load new config file to get the snapshot layer path + // open new upper layer + // restack() current RW layer as snapshot layer + if(!m_lower_file || !m_upper_file) + LOG_ERROR_RETURN(0, -1, "Lower or upper layer is NULL."); + + ImageConfigNS::ImageConfig new_cfg; + LSMT::IFileRW *upper_file = nullptr; + + LOG_INFO("Load new config `.", config_path); + if (!new_cfg.ParseJSON(config_path)) { + LOG_ERROR_RETURN(0, -1, "Error parse new config json: `.", config_path); + } + + auto upper = new_cfg.upper(); + auto lowers = new_cfg.lowers(); + // if(lowers[lowers.size()-1].file() != conf.upper().data()) + // LOG_ERROR_RETURN(0, -1, "The last lower layer(`) should be the same as old upper layer(`) after restack.", lowers[lowers.size()-1].file(), conf.upper().data()); + if(upper.index() == conf.upper().index() || upper.data() == conf.upper().data()) + LOG_ERROR_RETURN(0, -1, "The new upper layer(`, `) should be different from the old upper layer(`, `).", upper.data(), upper.index(), conf.upper().data(), conf.upper().index()); + + upper_file = open_upper(upper); + if (!upper_file) + LOG_ERROR_RETURN(0, -1, "Open upper layer failed."); + + if(((LSMT::IFileRW *)m_file)->restack(upper_file) != 0) + LOG_ERRNO_RETURN(0, -1, "Restack new rwlayer failed."); + + if(m_upper_file) { + // transfer the sealed layer from m_upper_file to m_lower_file before m_upper_file is destructed + auto sealed = ((LSMT::IFileRW *)m_upper_file)->get_file(0); + ((LSMT::IFileRO *)m_lower_file)->insert_file(sealed); + ((LSMT::IFileRW *)m_upper_file)->clear_files(); + delete m_upper_file; + } + // set m_lower_file->m_index = m_file->m_index->m_backing_index because m_file is not responsible for the destruction of m_backing_index + auto combo_index = (LSMT::IComboIndex *)((LSMT::IFileRW *)m_file)->index(); // m_file->m_index + ((LSMT::IFileRO *)m_lower_file)->index(combo_index->backing_index()); + // set m_file->m_index->m_index0 = upper_file->m_index + auto upper_file_index = (LSMT::IMemoryIndex0 *)((LSMT::IFileRW *)upper_file)->index(); // upper_file->m_index + combo_index->front_index(upper_file_index); + + m_upper_file = upper_file; + + return 0; +} diff --git a/src/image_file.h b/src/image_file.h index 12e12aac..d1f3a63e 100644 --- a/src/image_file.h +++ b/src/image_file.h @@ -119,13 +119,7 @@ class ImageFile : public photon::fs::ForwardFile { int compact(IFile *as); - int create_snapshot(const char *config_path) { - // load new config file to get the snapshot layer path - // open new upper layer - // restack() current RW layer as snapshot layer - LOG_INFO("call create_snapshot, dev_id: `", m_dev_id); - return 0; - } + int create_snapshot(const char *config_path); private: Prefetcher *m_prefetcher = nullptr; diff --git a/src/overlaybd/lsmt/file.cpp b/src/overlaybd/lsmt/file.cpp index 8b0b728d..d1b3421a 100644 --- a/src/overlaybd/lsmt/file.cpp +++ b/src/overlaybd/lsmt/file.cpp @@ -500,6 +500,20 @@ class LSMTReadOnlyFile : public IFileRW { return (IMemoryIndex0 *)m_index; } + virtual int index(const IMemoryIndex *index) override { + if(!index || !index->buffer()) { + errno = EINVAL; + LOG_ERROR("Invalid index!"); + return -1; + } + if (m_index != nullptr) { + delete m_index; + m_index = nullptr; + } + m_index = (IMemoryIndex *)index; + return 0; + } + virtual int close() override { safe_delete(m_index); if (m_file_ownership) { @@ -523,6 +537,23 @@ class LSMTReadOnlyFile : public IFileRW { return m_files; } + virtual IFile *get_file(size_t file_idx) const override { + if (file_idx >= m_files.size()) { + LOG_ERROR_RETURN(0, nullptr, "file_idx out of range."); + } + return m_files[file_idx]; + } + + virtual int insert_file(IFile * file) override { + m_files.insert(m_files.begin(), file); + return 0; + } + + virtual int clear_files() override { + m_files.clear(); + return 0; + } + template inline void forward(void *&buffer, T1 &offset, T2 &count, T3 step) { (char *&)buffer += step * ALIGNMENT; @@ -1038,6 +1069,9 @@ class LSMTFile : public LSMTReadOnlyFile { m_findex = u->m_findex; m_vsize = u->m_vsize; ((IComboIndex *)m_index)->commit_index0(); + + delete fseal; + delete gc_layer; return 0; } @@ -1333,7 +1367,7 @@ static SegmentMapping *do_load_index(IFile *file, HeaderTrailer *pheader_trailer auto ret = file->fstat(&stat); if (ret < 0) LOG_ERRNO_RETURN(0, nullptr, "failed to stat file."); - assert(pht->is_sparse_rw() == false); + assert(trailer || pht->is_sparse_rw() == false); uint64_t index_bytes; if (trailer) { if (!pht->is_data_file()) @@ -1738,7 +1772,7 @@ static IMemoryIndex *load_merge_index(vector &files, vector &uuid } } - std::reverse(files.begin(), files.end()); + std::reverse(files.begin(), files.end()); // reverse files: layerN-1 ... layer0 std::reverse(tm.indexes.begin(), tm.indexes.end()); std::reverse(uuid.begin(), uuid.end()); auto pmi = merge_memory_indexes((const IMemoryIndex **)&tm.indexes[0], tm.indexes.size()); diff --git a/src/overlaybd/lsmt/file.h b/src/overlaybd/lsmt/file.h index 4c6dbe0b..f49f995f 100644 --- a/src/overlaybd/lsmt/file.h +++ b/src/overlaybd/lsmt/file.h @@ -46,11 +46,19 @@ class IFileRO : public photon::fs::VirtualReadOnlyFile { virtual IMemoryIndex *index() const = 0; + virtual int index(const IMemoryIndex *index) = 0; + // return uuid of m_files[layer_idx]; virtual int get_uuid(UUID &out, size_t layer_idx = 0) const = 0; virtual std::vector get_lower_files() const = 0; + virtual IFile *get_file(size_t file_idx) const = 0; + + virtual int insert_file(IFile * file) = 0; + + virtual int clear_files() = 0; + virtual ssize_t seek_data(off_t begin, off_t end, std::vector &segs) = 0; virtual int flatten(IFile *as) = 0; diff --git a/src/overlaybd/lsmt/index.cpp b/src/overlaybd/lsmt/index.cpp index af8303a7..6e77fdb6 100644 --- a/src/overlaybd/lsmt/index.cpp +++ b/src/overlaybd/lsmt/index.cpp @@ -617,6 +617,7 @@ class Index0 : public IComboIndex { virtual const IMemoryIndex0 *front_index() const override { return this; } + UNIMPLEMENTED(int front_index(const IMemoryIndex0 *fi) override); UNIMPLEMENTED(size_t vsize() const override); UNIMPLEMENTED(int commit_index0() override); }; @@ -649,6 +650,19 @@ class ComboIndex : public Index0 { } } + virtual int front_index(const IMemoryIndex0 *fi) override { + if (!fi) { + errno = EINVAL; + LOG_ERROR("Invalid index!"); + return -1; + } + if (m_ownership && m_index0 != nullptr) { // !!! + delete m_index0; + m_index0 = nullptr; + } + m_index0 = (Index0 *)fi; + return 0; + } virtual const IMemoryIndex0 *front_index() const override { return this->m_index0; } @@ -759,7 +773,9 @@ class ComboIndex : public Index0 { p.tag = 0; merged_index->insert(p); } - delete m_backing_index; + if(m_ownership) { // !!! + delete m_backing_index; + } m_backing_index = (Index*)(merged_index->make_read_only_index()); // set ownership=false delete merged_index; LOG_INFO("rebuild backing index done. {count: `}", m_backing_index->size()); diff --git a/src/overlaybd/lsmt/index.h b/src/overlaybd/lsmt/index.h index 748d448a..ce6931c4 100644 --- a/src/overlaybd/lsmt/index.h +++ b/src/overlaybd/lsmt/index.h @@ -149,6 +149,7 @@ class IComboIndex : public IMemoryIndex0 { // backing index must NOT be IMemoryIndex0! virtual int backing_index(const IMemoryIndex *bi) = 0; virtual const IMemoryIndex *backing_index() const = 0; + virtual int front_index(const IMemoryIndex0 *fi) = 0; virtual const IMemoryIndex0 *front_index() const = 0; // dump index0 which needs to compact diff --git a/src/test/image_service_test.cpp b/src/test/image_service_test.cpp index f665b042..020c5e2b 100644 --- a/src/test/image_service_test.cpp +++ b/src/test/image_service_test.cpp @@ -26,6 +26,7 @@ #include "photon/net/curl.h" #include "../version.h" #include +#include #include #include @@ -34,6 +35,7 @@ #include "../image_service.h" #include "../image_file.h" #include "../tools/comm_func.h" +#include "../overlaybd/lsmt/file.h" char *test_ua = nullptr; @@ -258,7 +260,7 @@ TEST_F(DevIDRegisterTest, register_dev_id) { delete imagefile3; } -class SnapshotTest : public DevIDRegisterTest { +class HTTPServerTest : public DevIDRegisterTest { public: virtual void SetUp() override { global_config_content = R"delimiter({ @@ -288,7 +290,7 @@ class SnapshotTest : public DevIDRegisterTest { } }; -TEST_F(SnapshotTest, http_server) { +TEST_F(HTTPServerTest, http_server) { ImageFile* imgfile = imgservice->create_image_file(image_config_path.c_str(), "123"); EXPECT_NE(imgfile, nullptr); @@ -296,7 +298,229 @@ TEST_F(SnapshotTest, http_server) { EXPECT_EQ(request_snapshot("http://localhost:9862/snapshot?V#RNWQC&*@#"), 400); EXPECT_EQ(request_snapshot("http://localhost:9862/snapshot?dev_id=&config=/tmp/overlaybd/config.json"), 400); EXPECT_EQ(request_snapshot("http://localhost:9862/snapshot?dev_id=456&config=/tmp/overlaybd/config.json"), 404); - EXPECT_EQ(request_snapshot("http://localhost:9862/snapshot?dev_id=123&config=/tmp/overlaybd/config.json"), 200); + EXPECT_EQ(request_snapshot("http://localhost:9862/snapshot?dev_id=123&config=/tmp/overlaybd/config.json"), 500); + + delete imgfile; +} + +#define PREADV_SINGLE(file, buf, count, offset) ({ \ + struct iovec iov = { .iov_base = (void *)buf, .iov_len = count }; \ + (file)->preadv(&iov, 1, offset); \ +}) + +#define PWRITEV_SINGLE(file, buf, count, offset) ({ \ + struct iovec iov = { .iov_base = (void *)buf, .iov_len = count }; \ + (file)->pwritev(&iov, 1, offset); \ +}) + +class CreateSnapshotTest : public DevIDRegisterTest { +public: + const std::string new_image_config_path = test_dir + "/new_image_config.json"; + std::string new_image_config_content = R"delimiter({ + "lowers" : [ + { + "file" : "/opt/overlaybd/baselayers/ext4_64" + }, + { + "file" : "/tmp/overlaybd/data0.lsmt" + } + ], + "upper": { + "index": "/tmp/overlaybd/index1.lsmt", + "data": "/tmp/overlaybd/data1.lsmt" + } +})delimiter"; + virtual void SetUp() override { + image_config_content = R"delimiter({ + "lowers" : [ + { + "file" : "/opt/overlaybd/baselayers/ext4_64" + } + ], + "upper": { + "index": "/tmp/overlaybd/index0.lsmt", + "data": "/tmp/overlaybd/data0.lsmt" + } +})delimiter"; + + DevIDRegisterTest::SetUp(); + + system(("echo \'" + new_image_config_content + "\' > " + new_image_config_path).c_str()); + LOG_INFO("New image config file: "); + system(("cat " + new_image_config_path).c_str()); + + srand(154574045); + } + + void create_file_rw(char *data_name, char *index_name, bool sparse = false) { + auto fdata = photon::fs::open_localfile_adaptor(data_name, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU); + auto findex = photon::fs::open_localfile_adaptor(index_name, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU); + LSMT::LayerInfo args(fdata, findex); + args.sparse_rw = sparse; + args.virtual_size = 64 << 20; + auto file = LSMT::create_file_rw(args, true); + delete file; + } +}; + +TEST_F(CreateSnapshotTest, create_snapshot) { + // imagefile0->pwrite( buf, 0, 1MB) + // imagefile0->restack(xxx) //s config.v1.json.new + // imagefile1->pread(buf1, 0, 1MB) imagefile0->pread(buf0...) + create_file_rw("/tmp/overlaybd/data0.lsmt", "/tmp/overlaybd/index0.lsmt"); + create_file_rw("/tmp/overlaybd/data1.lsmt", "/tmp/overlaybd/index1.lsmt"); + + ImageFile* imgfile0 = imgservice->create_image_file(image_config_path.c_str(), ""); + EXPECT_NE(imgfile0, nullptr); + + auto len = 1 << 20; + ssize_t ret; + ALIGNED_MEM4K(buf, len); + ALIGNED_MEM4K(buf0, len); + ALIGNED_MEM4K(buf1, len); + + for (auto i = 0; i < len; i++) { + auto j = rand() % 256; + buf[i] = j; + } + ret = PWRITEV_SINGLE(imgfile0, buf, len, 0); + EXPECT_EQ(ret, len); + + EXPECT_EQ(imgfile0->create_snapshot(new_image_config_path.c_str()), 0); + + ImageFile* imgfile1 = imgservice->create_image_file(new_image_config_path.c_str(), ""); + EXPECT_NE(imgfile1, nullptr); + + std::cout << "create_snapshot & verify" << std::endl; + ret = PREADV_SINGLE(imgfile0, buf0, len, 0); + EXPECT_EQ(ret, len); + ret = PREADV_SINGLE(imgfile1, buf1, len, 0); + EXPECT_EQ(ret, len); + for(auto i = 0; i < len; i++) { + EXPECT_EQ(buf0[i], buf1[i]); + EXPECT_EQ(buf0[i], buf[i]); + } + + for (auto i = 0; i < len / 2; i++) { + auto j = rand() % 256; + buf[i] = j; + } + ret = PWRITEV_SINGLE(imgfile0, buf, len / 2, len / 4); + EXPECT_EQ(ret, len / 2); + ret = PWRITEV_SINGLE(imgfile1, buf, len / 2, len / 4); + EXPECT_EQ(ret, len / 2); + + std::cout << "verify file after pwrite" << std::endl; + ret = PREADV_SINGLE(imgfile0, buf0, len, 0); + EXPECT_EQ(ret, len); + ret = PREADV_SINGLE(imgfile1, buf1, len, 0); + EXPECT_EQ(ret, len); + for(auto i = 0; i < len; i++) { + EXPECT_EQ(buf0[i], buf1[i]); + if(i >= len / 4 && i < len / 2 + len / 4) + EXPECT_EQ(buf0[i], buf[i - len / 4]); + } + + delete imgfile0; + delete imgfile1; +} + +TEST_F(CreateSnapshotTest, create_snapshot_sparse) { + create_file_rw("/tmp/overlaybd/data0.lsmt", "/tmp/overlaybd/index0.lsmt", true); + create_file_rw("/tmp/overlaybd/data1.lsmt", "/tmp/overlaybd/index1.lsmt", true); + + ImageFile* imgfile0 = imgservice->create_image_file(image_config_path.c_str(), ""); + EXPECT_NE(imgfile0, nullptr); + + auto len = 1 << 20; + ssize_t ret; + ALIGNED_MEM4K(buf, len); + ALIGNED_MEM4K(buf0, len); + ALIGNED_MEM4K(buf1, len); + + for (auto i = 0; i < len; i++) { + auto j = rand() % 256; + buf[i] = j; + } + ret = PWRITEV_SINGLE(imgfile0, buf, len, 0); + EXPECT_EQ(ret, len); + + EXPECT_EQ(imgfile0->create_snapshot(new_image_config_path.c_str()), 0); + + ImageFile* imgfile1 = imgservice->create_image_file(new_image_config_path.c_str(), ""); + EXPECT_NE(imgfile1, nullptr); + + std::cout << "create_snapshot & verify" << std::endl; + ret = PREADV_SINGLE(imgfile0, buf0, len, 0); + EXPECT_EQ(ret, len); + ret = PREADV_SINGLE(imgfile1, buf1, len, 0); + EXPECT_EQ(ret, len); + for(auto i = 0; i < len; i++) { + EXPECT_EQ(buf0[i], buf1[i]); + EXPECT_EQ(buf0[i], buf[i]); + } + + for (auto i = 0; i < len / 2; i++) { + auto j = rand() % 256; + buf[i] = j; + } + ret = PWRITEV_SINGLE(imgfile0, buf, len / 2, len / 4); + EXPECT_EQ(ret, len / 2); + ret = PWRITEV_SINGLE(imgfile1, buf, len / 2, len / 4); + EXPECT_EQ(ret, len / 2); + + std::cout << "verify file after pwrite" << std::endl; + ret = PREADV_SINGLE(imgfile0, buf0, len, 0); + EXPECT_EQ(ret, len); + ret = PREADV_SINGLE(imgfile1, buf1, len, 0); + EXPECT_EQ(ret, len); + for(auto i = 0; i < len; i++) { + EXPECT_EQ(buf0[i], buf1[i]); + if(i >= len / 4 && i < len / 2 + len / 4) + EXPECT_EQ(buf0[i], buf[i - len / 4]); + } + + delete imgfile0; + delete imgfile1; +} + +TEST_F(CreateSnapshotTest, create_snapshot_failed) { + create_file_rw("/tmp/overlaybd/data0.lsmt", "/tmp/overlaybd/index0.lsmt"); + ImageFile* imgfile = imgservice->create_image_file(image_config_path.c_str(), ""); + EXPECT_NE(imgfile, nullptr); + + std::cout << "set wrong new upper layer in config file" << std::endl; + new_image_config_content = R"delimiter({ + "lowers" : [ + { + "file" : "/opt/overlaybd/baselayers/ext4_64" + }, + { + "file" : "/tmp/overlaybd/data0.lsmt" + } + ], + "upper": { + "index": "/tmp/overlaybd/index1.lsmt", + "data": "/tmp/overlaybd/data0.lsmt" + } +})delimiter"; + system(("echo \'" + new_image_config_content + "\' > " + new_image_config_path).c_str()); + EXPECT_EQ(imgfile->create_snapshot(new_image_config_path.c_str()), -1); + + delete imgfile; + + std::cout << "create snapshot for imgfile with only RO layers" << std::endl; + image_config_content = R"delimiter({ + "lowers" : [ + { + "file" : "/opt/overlaybd/baselayers/ext4_64" + } + ] +})delimiter"; + system(("echo \'" + image_config_content + "\' > " + image_config_path).c_str()); + imgfile = imgservice->create_image_file(image_config_path.c_str(), ""); + EXPECT_NE(imgfile, nullptr); + EXPECT_EQ(imgfile->create_snapshot(new_image_config_path.c_str()), -1); delete imgfile; }