From: Jason Dillaman Date: Tue, 1 Dec 2020 15:26:49 +0000 (-0500) Subject: librbd/migration: generic raw snapshot format support X-Git-Tag: v16.1.0~382^2~9 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=499c564703a4055954ade38f3c0548c8562f4bca;p=ceph.git librbd/migration: generic raw snapshot format support The RawFormat will be tweaked to support the new RawSnapshot in a future commit, but the basic structure will allow for the eventual support of sparse snapshot data formats (i.e. rbd export-diff). Signed-off-by: Jason Dillaman --- diff --git a/src/librbd/CMakeLists.txt b/src/librbd/CMakeLists.txt index 59078597908..d27059178f8 100644 --- a/src/librbd/CMakeLists.txt +++ b/src/librbd/CMakeLists.txt @@ -137,6 +137,7 @@ set(librbd_internal_srcs migration/NativeFormat.cc migration/OpenSourceImageRequest.cc migration/RawFormat.cc + migration/RawSnapshot.cc migration/S3Stream.cc migration/SourceSpecBuilder.cc migration/Utils.cc diff --git a/src/librbd/migration/RawSnapshot.cc b/src/librbd/migration/RawSnapshot.cc new file mode 100644 index 00000000000..3ba7881d05c --- /dev/null +++ b/src/librbd/migration/RawSnapshot.cc @@ -0,0 +1,218 @@ +// -*- mode:c++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "librbd/migration/RawSnapshot.h" +#include "common/dout.h" +#include "common/errno.h" +#include "librbd/ImageCtx.h" +#include "librbd/Utils.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/io/ReadResult.h" +#include "librbd/migration/SourceSpecBuilder.h" +#include "librbd/migration/StreamInterface.h" + +namespace librbd { +namespace migration { + +namespace { + +const std::string NAME_KEY{"name"}; + +} // anonymous namespace + +#define dout_subsys ceph_subsys_rbd +#undef dout_prefix +#define dout_prefix *_dout << "librbd::migration::RawSnapshot::OpenRequest " \ + << this << " " << __func__ << ": " + +template +struct RawSnapshot::OpenRequest { + RawSnapshot* raw_snapshot; + Context* on_finish; + + OpenRequest(RawSnapshot* raw_snapshot, Context* on_finish) + : raw_snapshot(raw_snapshot), on_finish(on_finish) { + } + + void send() { + open_stream(); + } + + void open_stream() { + auto cct = raw_snapshot->m_image_ctx->cct; + ldout(cct, 10) << dendl; + + auto ctx = util::create_context_callback< + OpenRequest, &OpenRequest::handle_open_stream>(this); + raw_snapshot->m_stream->open(ctx); + } + + void handle_open_stream(int r) { + auto cct = raw_snapshot->m_image_ctx->cct; + ldout(cct, 10) << "r=" << r << dendl; + + if (r < 0) { + lderr(cct) << "failed to open stream: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + get_image_size(); + } + + void get_image_size() { + auto cct = raw_snapshot->m_image_ctx->cct; + ldout(cct, 10) << dendl; + + auto ctx = util::create_context_callback< + OpenRequest, &OpenRequest::handle_get_image_size>(this); + raw_snapshot->m_stream->get_size(&raw_snapshot->m_snap_info.size, ctx); + } + + void handle_get_image_size(int r) { + auto cct = raw_snapshot->m_image_ctx->cct; + ldout(cct, 10) << "r=" << r << dendl; + + if (r < 0) { + lderr(cct) << "failed to open stream: " << cpp_strerror(r) << dendl; + close_stream(r); + return; + } + + finish(0); + } + + void close_stream(int r) { + auto cct = raw_snapshot->m_image_ctx->cct; + ldout(cct, 10) << dendl; + + auto ctx = new LambdaContext([this, r](int) { + handle_close_stream(r); + }); + raw_snapshot->m_stream->close(ctx); + } + + void handle_close_stream(int r) { + auto cct = raw_snapshot->m_image_ctx->cct; + ldout(cct, 10) << "r=" << r << dendl; + + raw_snapshot->m_stream.reset(); + + finish(r); + } + + void finish(int r) { + auto cct = raw_snapshot->m_image_ctx->cct; + ldout(cct, 10) << "r=" << r << dendl; + + on_finish->complete(r); + delete this; + } +}; + +#undef dout_prefix +#define dout_prefix *_dout << "librbd::migration::RawSnapshot: " << this \ + << " " << __func__ << ": " + +template +RawSnapshot::RawSnapshot(I* image_ctx, + const json_spirit::mObject& json_object, + const SourceSpecBuilder* source_spec_builder, + uint64_t index) + : m_image_ctx(image_ctx), m_json_object(json_object), + m_source_spec_builder(source_spec_builder), m_index(index), + m_snap_info({}, {}, 0, {}, 0, 0, {}) { + auto cct = m_image_ctx->cct; + ldout(cct, 10) << dendl; +} + +template +void RawSnapshot::open(SnapshotInterface* previous_snapshot, + Context* on_finish) { + auto cct = m_image_ctx->cct; + ldout(cct, 10) << dendl; + + // special-case for treating the HEAD revision as a snapshot + if (m_index != CEPH_NOSNAP) { + auto& name_val = m_json_object[NAME_KEY]; + if (name_val.type() == json_spirit::str_type) { + m_snap_info.name = name_val.get_str(); + } else if (name_val.type() == json_spirit::null_type) { + uuid_d uuid_gen; + uuid_gen.generate_random(); + + m_snap_info.name = uuid_gen.to_string(); + } else { + lderr(cct) << "invalid snapshot name" << dendl; + on_finish->complete(-EINVAL); + return; + } + } + + int r = m_source_spec_builder->build_stream(m_json_object, &m_stream); + if (r < 0) { + lderr(cct) << "failed to build migration stream handler" << cpp_strerror(r) + << dendl; + on_finish->complete(r); + return; + } + + auto req = new OpenRequest(this, on_finish); + req->send(); +} + +template +void RawSnapshot::close(Context* on_finish) { + auto cct = m_image_ctx->cct; + ldout(cct, 10) << dendl; + + if (!m_stream) { + on_finish->complete(0); + return; + } + + m_stream->close(on_finish); +} + +template +void RawSnapshot::read(io::AioCompletion* aio_comp, + io::Extents&& image_extents, + io::ReadResult&& read_result, int op_flags, + int read_flags, + const ZTracer::Trace &parent_trace) { + auto cct = m_image_ctx->cct; + ldout(cct, 20) << "image_extents=" << image_extents << dendl; + + aio_comp->read_result = std::move(read_result); + aio_comp->read_result.set_image_extents(image_extents); + + aio_comp->set_request_count(1); + auto ctx = new io::ReadResult::C_ImageReadRequest(aio_comp, + 0, image_extents); + + // raw directly maps the image-extent IO down to a byte IO extent + m_stream->read(std::move(image_extents), &ctx->bl, ctx); +} + +template +void RawSnapshot::list_snap(io::Extents&& image_extents, + int list_snaps_flags, + io::SparseExtents* sparse_extents, + const ZTracer::Trace &parent_trace, + Context* on_finish) { + auto cct = m_image_ctx->cct; + ldout(cct, 20) << "image_extents=" << image_extents << dendl; + + // raw does support sparse extents so list the full IO extent as a delta + for (auto& [image_offset, image_length] : image_extents) { + sparse_extents->insert(image_offset, image_length, + {io::SPARSE_EXTENT_STATE_DATA, image_length}); + } + + on_finish->complete(0); +} + +} // namespace migration +} // namespace librbd + +template class librbd::migration::RawSnapshot; diff --git a/src/librbd/migration/RawSnapshot.h b/src/librbd/migration/RawSnapshot.h new file mode 100644 index 00000000000..9f76d687824 --- /dev/null +++ b/src/librbd/migration/RawSnapshot.h @@ -0,0 +1,75 @@ +// -*- mode:c++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_MIGRATION_RAW_SNAPSHOT_H +#define CEPH_LIBRBD_MIGRATION_RAW_SNAPSHOT_H + +#include "include/buffer_fwd.h" +#include "include/int_types.h" +#include "common/zipkin_trace.h" +#include "librbd/Types.h" +#include "librbd/io/Types.h" +#include "librbd/migration/SnapshotInterface.h" +#include "json_spirit/json_spirit.h" +#include + +namespace librbd { + +struct ImageCtx; + +namespace migration { + +template struct SourceSpecBuilder; +struct StreamInterface; + +template +class RawSnapshot : public SnapshotInterface { +public: + static RawSnapshot* create( + ImageCtx* image_ctx, const json_spirit::mObject& json_object, + const SourceSpecBuilder* source_spec_builder, uint64_t index) { + return new RawSnapshot(image_ctx, json_object, source_spec_builder, index); + } + + RawSnapshot(ImageCtxT* image_ctx, const json_spirit::mObject& json_object, + const SourceSpecBuilder* source_spec_builder, + uint64_t index); + RawSnapshot(const RawSnapshot&) = delete; + RawSnapshot& operator=(const RawSnapshot&) = delete; + + void open(SnapshotInterface* previous_snapshot, Context* on_finish) override; + void close(Context* on_finish) override; + + const SnapInfo& get_snap_info() const override { + return m_snap_info; + } + + void read(io::AioCompletion* aio_comp, io::Extents&& image_extents, + io::ReadResult&& read_result, int op_flags, int read_flags, + const ZTracer::Trace &parent_trace) override; + + void list_snap(io::Extents&& image_extents, int list_snaps_flags, + io::SparseExtents* sparse_extents, + const ZTracer::Trace &parent_trace, + Context* on_finish) override; + +private: + struct OpenRequest; + + ImageCtxT* m_image_ctx; + json_spirit::mObject m_json_object; + const SourceSpecBuilder* m_source_spec_builder; + uint64_t m_index = 0; + + SnapInfo m_snap_info; + + std::shared_ptr m_stream; + +}; + +} // namespace migration +} // namespace librbd + +extern template class librbd::migration::RawSnapshot; + +#endif // CEPH_LIBRBD_MIGRATION_RAW_SNAPSHOT_H diff --git a/src/librbd/migration/SnapshotInterface.h b/src/librbd/migration/SnapshotInterface.h new file mode 100644 index 00000000000..9990802c594 --- /dev/null +++ b/src/librbd/migration/SnapshotInterface.h @@ -0,0 +1,48 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_MIGRATION_SNAPSHOT_INTERFACE_H +#define CEPH_LIBRBD_MIGRATION_SNAPSHOT_INTERFACE_H + +#include "include/buffer_fwd.h" +#include "include/int_types.h" +#include "common/zipkin_trace.h" +#include "librbd/Types.h" +#include "librbd/io/Types.h" +#include + +struct Context; + +namespace librbd { + +namespace io { +struct AioCompletion; +struct ReadResult; +} // namespace io + +namespace migration { + +struct SnapshotInterface { + virtual ~SnapshotInterface() { + } + + virtual void open(SnapshotInterface* previous_snapshot, + Context* on_finish) = 0; + virtual void close(Context* on_finish) = 0; + + virtual const SnapInfo& get_snap_info() const = 0; + + virtual void read(io::AioCompletion* aio_comp, io::Extents&& image_extents, + io::ReadResult&& read_result, int op_flags, int read_flags, + const ZTracer::Trace &parent_trace) = 0; + + virtual void list_snap(io::Extents&& image_extents, int list_snaps_flags, + io::SparseExtents* sparse_extents, + const ZTracer::Trace &parent_trace, + Context* on_finish) = 0; +}; + +} // namespace migration +} // namespace librbd + +#endif // CEPH_LIBRBD_MIGRATION_SNAPSHOT_INTERFACE_H diff --git a/src/librbd/migration/SourceSpecBuilder.cc b/src/librbd/migration/SourceSpecBuilder.cc index 4565833a93a..526522fba60 100644 --- a/src/librbd/migration/SourceSpecBuilder.cc +++ b/src/librbd/migration/SourceSpecBuilder.cc @@ -9,6 +9,7 @@ #include "librbd/migration/S3Stream.h" #include "librbd/migration/NativeFormat.h" #include "librbd/migration/RawFormat.h" +#include "librbd/migration/RawSnapshot.h" #define dout_subsys ceph_subsys_rbd #undef dout_prefix @@ -73,6 +74,32 @@ int SourceSpecBuilder::build_format( return 0; } +template +int SourceSpecBuilder::build_snapshot( + const json_spirit::mObject& source_spec_object, uint64_t index, + std::shared_ptr* snapshot) const { + auto cct = m_image_ctx->cct; + ldout(cct, 10) << dendl; + + auto type_value_it = source_spec_object.find(TYPE_KEY); + if (type_value_it == source_spec_object.end() || + type_value_it->second.type() != json_spirit::str_type) { + lderr(cct) << "failed to locate snapshot type value" << dendl; + return -EINVAL; + } + + auto& type = type_value_it->second.get_str(); + if (type == "raw") { + snapshot->reset(RawSnapshot::create(m_image_ctx, source_spec_object, + this, index)); + } else { + lderr(cct) << "unknown or unsupported format type '" << type << "'" + << dendl; + return -ENOSYS; + } + return 0; +} + template int SourceSpecBuilder::build_stream( const json_spirit::mObject& source_spec_object, diff --git a/src/librbd/migration/SourceSpecBuilder.h b/src/librbd/migration/SourceSpecBuilder.h index 341679e078e..191cb1cbdd3 100644 --- a/src/librbd/migration/SourceSpecBuilder.h +++ b/src/librbd/migration/SourceSpecBuilder.h @@ -19,6 +19,7 @@ struct ImageCtx; namespace migration { struct FormatInterface; +struct SnapshotInterface; struct StreamInterface; template @@ -33,6 +34,10 @@ public: int build_format(const json_spirit::mObject& format_object, bool import_only, std::unique_ptr* format) const; + int build_snapshot(const json_spirit::mObject& source_spec_object, + uint64_t index, + std::shared_ptr* snapshot) const; + int build_stream(const json_spirit::mObject& source_spec_object, std::shared_ptr* stream) const; diff --git a/src/test/librbd/CMakeLists.txt b/src/test/librbd/CMakeLists.txt index 45039108b75..56f7fb3697a 100644 --- a/src/test/librbd/CMakeLists.txt +++ b/src/test/librbd/CMakeLists.txt @@ -93,6 +93,7 @@ set(unittest_librbd_srcs migration/test_mock_HttpClient.cc migration/test_mock_HttpStream.cc migration/test_mock_RawFormat.cc + migration/test_mock_RawSnapshot.cc migration/test_mock_S3Stream.cc migration/test_mock_Utils.cc mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc diff --git a/src/test/librbd/migration/test_mock_RawSnapshot.cc b/src/test/librbd/migration/test_mock_RawSnapshot.cc new file mode 100644 index 00000000000..3ce4b5c9daa --- /dev/null +++ b/src/test/librbd/migration/test_mock_RawSnapshot.cc @@ -0,0 +1,255 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "test/librbd/test_support.h" +#include "test/librbd/mock/migration/MockStreamInterface.h" +#include "include/rbd_types.h" +#include "common/ceph_mutex.h" +#include "librbd/migration/FileStream.h" +#include "librbd/migration/RawSnapshot.h" +#include "librbd/migration/SourceSpecBuilder.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "json_spirit/json_spirit.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace migration { + +template<> +struct SourceSpecBuilder { + + MOCK_CONST_METHOD2(build_stream, int(const json_spirit::mObject&, + std::shared_ptr*)); + +}; + +} // namespace migration +} // namespace librbd + +#include "librbd/migration/RawSnapshot.cc" + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::WithArgs; + +namespace librbd { +namespace migration { + +using ::testing::Invoke; + +class TestMockMigrationRawSnapshot : public TestMockFixture { +public: + typedef RawSnapshot MockRawSnapshot; + typedef SourceSpecBuilder MockSourceSpecBuilder; + + librbd::ImageCtx *m_image_ctx; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx)); + + json_spirit::mObject stream_obj; + stream_obj["type"] = "file"; + json_object["stream"] = stream_obj; + } + + void expect_build_stream(MockSourceSpecBuilder& mock_source_spec_builder, + MockStreamInterface* mock_stream_interface, int r) { + EXPECT_CALL(mock_source_spec_builder, build_stream(_, _)) + .WillOnce(WithArgs<1>(Invoke([mock_stream_interface, r] + (std::shared_ptr* ptr) { + ptr->reset(mock_stream_interface); + return r; + }))); + } + + void expect_stream_open(MockStreamInterface& mock_stream_interface, int r) { + EXPECT_CALL(mock_stream_interface, open(_)) + .WillOnce(Invoke([r](Context* ctx) { ctx->complete(r); })); + } + + void expect_stream_close(MockStreamInterface& mock_stream_interface, int r) { + EXPECT_CALL(mock_stream_interface, close(_)) + .WillOnce(Invoke([r](Context* ctx) { ctx->complete(r); })); + } + + void expect_stream_get_size(MockStreamInterface& mock_stream_interface, + uint64_t size, int r) { + EXPECT_CALL(mock_stream_interface, get_size(_, _)) + .WillOnce(Invoke([size, r](uint64_t* out_size, Context* ctx) { + *out_size = size; + ctx->complete(r); + })); + } + + void expect_stream_read(MockStreamInterface& mock_stream_interface, + const io::Extents& byte_extents, + const bufferlist& bl, int r) { + EXPECT_CALL(mock_stream_interface, read(byte_extents, _, _)) + .WillOnce(WithArgs<1, 2>(Invoke([bl, r] + (bufferlist* out_bl, Context* ctx) { + *out_bl = bl; + ctx->complete(r); + }))); + } + + json_spirit::mObject json_object; +}; + +TEST_F(TestMockMigrationRawSnapshot, OpenClose) { + MockTestImageCtx mock_image_ctx(*m_image_ctx); + + InSequence seq; + MockSourceSpecBuilder mock_source_spec_builder; + + auto mock_stream_interface = new MockStreamInterface(); + expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0); + + expect_stream_open(*mock_stream_interface, 0); + expect_stream_get_size(*mock_stream_interface, 123, 0); + + expect_stream_close(*mock_stream_interface, 0); + + json_object["name"] = "snap1"; + MockRawSnapshot mock_raw_snapshot(&mock_image_ctx, json_object, + &mock_source_spec_builder, 1); + + C_SaferCond ctx1; + mock_raw_snapshot.open(nullptr, &ctx1); + ASSERT_EQ(0, ctx1.wait()); + + auto snap_info = mock_raw_snapshot.get_snap_info(); + ASSERT_EQ("snap1", snap_info.name); + ASSERT_EQ(123, snap_info.size); + + C_SaferCond ctx2; + mock_raw_snapshot.close(&ctx2); + ASSERT_EQ(0, ctx2.wait()); +} + +TEST_F(TestMockMigrationRawSnapshot, OpenError) { + MockTestImageCtx mock_image_ctx(*m_image_ctx); + + InSequence seq; + MockSourceSpecBuilder mock_source_spec_builder; + + auto mock_stream_interface = new MockStreamInterface(); + expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0); + + expect_stream_open(*mock_stream_interface, -ENOENT); + + MockRawSnapshot mock_raw_snapshot(&mock_image_ctx, json_object, + &mock_source_spec_builder, 0); + + C_SaferCond ctx; + mock_raw_snapshot.open(nullptr, &ctx); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockMigrationRawSnapshot, GetSizeError) { + MockTestImageCtx mock_image_ctx(*m_image_ctx); + + InSequence seq; + MockSourceSpecBuilder mock_source_spec_builder; + + auto mock_stream_interface = new MockStreamInterface(); + expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0); + + expect_stream_open(*mock_stream_interface, 0); + expect_stream_get_size(*mock_stream_interface, 0, -EINVAL); + + expect_stream_close(*mock_stream_interface, 0); + + MockRawSnapshot mock_raw_snapshot(&mock_image_ctx, json_object, + &mock_source_spec_builder, 0); + + C_SaferCond ctx; + mock_raw_snapshot.open(nullptr, &ctx); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockMigrationRawSnapshot, Read) { + MockTestImageCtx mock_image_ctx(*m_image_ctx); + + InSequence seq; + MockSourceSpecBuilder mock_source_spec_builder; + + auto mock_stream_interface = new MockStreamInterface(); + expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0); + + expect_stream_open(*mock_stream_interface, 0); + expect_stream_get_size(*mock_stream_interface, 0, 0); + + bufferlist expect_bl; + expect_bl.append(std::string(123, '1')); + expect_stream_read(*mock_stream_interface, {{123, 123}}, expect_bl, 0); + + expect_stream_close(*mock_stream_interface, 0); + + MockRawSnapshot mock_raw_snapshot(&mock_image_ctx, json_object, + &mock_source_spec_builder, 0); + + C_SaferCond ctx1; + mock_raw_snapshot.open(nullptr, &ctx1); + ASSERT_EQ(0, ctx1.wait()); + + C_SaferCond ctx2; + auto aio_comp = io::AioCompletion::create_and_start( + &ctx2, m_image_ctx, io::AIO_TYPE_READ); + bufferlist bl; + io::ReadResult read_result{&bl}; + mock_raw_snapshot.read(aio_comp, {{123, 123}}, std::move(read_result), 0, 0, + {}); + ASSERT_EQ(123, ctx2.wait()); + ASSERT_EQ(expect_bl, bl); + + C_SaferCond ctx3; + mock_raw_snapshot.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +TEST_F(TestMockMigrationRawSnapshot, ListSnap) { + MockTestImageCtx mock_image_ctx(*m_image_ctx); + + InSequence seq; + MockSourceSpecBuilder mock_source_spec_builder; + + auto mock_stream_interface = new MockStreamInterface(); + expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0); + + expect_stream_open(*mock_stream_interface, 0); + expect_stream_get_size(*mock_stream_interface, 0, 0); + + expect_stream_close(*mock_stream_interface, 0); + + MockRawSnapshot mock_raw_snapshot(&mock_image_ctx, json_object, + &mock_source_spec_builder, 0); + + C_SaferCond ctx1; + mock_raw_snapshot.open(nullptr, &ctx1); + ASSERT_EQ(0, ctx1.wait()); + + C_SaferCond ctx2; + io::SparseExtents sparse_extents; + mock_raw_snapshot.list_snap({{0, 123}}, 0, &sparse_extents, {}, &ctx2); + ASSERT_EQ(0, ctx2.wait()); + + C_SaferCond ctx3; + mock_raw_snapshot.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +} // namespace migration +} // namespace librbd