diff options
Diffstat (limited to 'src/test/librbd/migration/test_mock_QCOWFormat.cc')
-rw-r--r-- | src/test/librbd/migration/test_mock_QCOWFormat.cc | 1259 |
1 files changed, 1259 insertions, 0 deletions
diff --git a/src/test/librbd/migration/test_mock_QCOWFormat.cc b/src/test/librbd/migration/test_mock_QCOWFormat.cc new file mode 100644 index 000000000..6e7225d22 --- /dev/null +++ b/src/test/librbd/migration/test_mock_QCOWFormat.cc @@ -0,0 +1,1259 @@ +// -*- 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/QCOWFormat.h" +#include "librbd/migration/SourceSpecBuilder.h" +#include "acconfig.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<librbd::MockTestImageCtx> { + + MOCK_CONST_METHOD2(build_stream, int(const json_spirit::mObject&, + std::shared_ptr<StreamInterface>*)); + +}; + +} // namespace migration + +bool operator==(const SnapInfo& lhs, const SnapInfo& rhs) { + return (lhs.name == rhs.name && + lhs.size == rhs.size); +} + +} // namespace librbd + +#include "librbd/migration/QCOWFormat.cc" + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::ReturnRef; +using ::testing::WithArg; +using ::testing::WithArgs; + +namespace librbd { +namespace migration { + +using ::testing::Invoke; + +class TestMockMigrationQCOWFormat : public TestMockFixture { +public: + typedef QCOWFormat<MockTestImageCtx> MockQCOWFormat; + typedef SourceSpecBuilder<MockTestImageCtx> 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<StreamInterface>* 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_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); + }))); + } + + void expect_probe_header(MockStreamInterface& mock_stream_interface, + uint32_t magic, uint32_t version, int r) { + magic = htobe32(magic); + version = htobe32(version); + + bufferlist probe_bl; + probe_bl.append(std::string_view(reinterpret_cast<char*>(&magic), 4)); + probe_bl.append(std::string_view(reinterpret_cast<char*>(&version), 4)); + expect_stream_read(mock_stream_interface, {{0, 8}}, probe_bl, r); + } + + void expect_read_header(MockStreamInterface& mock_stream_interface, + uint32_t snapshot_count, int r) { + QCowHeader qcow_header; + memset(&qcow_header, 0, sizeof(qcow_header)); + qcow_header.magic = htobe32(QCOW_MAGIC); + qcow_header.version = htobe32(2); + qcow_header.size = htobe64(1<<30); + qcow_header.cluster_bits = htobe32(16); + qcow_header.l1_size = htobe32(2); + qcow_header.l1_table_offset = htobe64(1<<20); + if (snapshot_count > 0) { + qcow_header.nb_snapshots = htobe32(snapshot_count); + qcow_header.snapshots_offset = htobe64(1<<21); + } + + bufferlist header_bl; + header_bl.append(std::string_view(reinterpret_cast<char*>(&qcow_header), + sizeof(qcow_header))); + expect_stream_read(mock_stream_interface, {{0, sizeof(qcow_header)}}, + header_bl, r); + } + + void expect_read_l1_table(MockStreamInterface& mock_stream_interface, + std::optional<std::vector<uint64_t>>&& l1_table, + int r) { + bufferlist l1_table_bl; + if (!l1_table) { + l1_table.emplace(2); + } + + l1_table->resize(2); + for (size_t idx = 0; idx < l1_table->size(); ++idx) { + (*l1_table)[idx] = htobe64((*l1_table)[idx]); + } + + l1_table_bl.append( + std::string_view(reinterpret_cast<char*>(l1_table->data()), 16)); + expect_stream_read(mock_stream_interface, {{1<<20, 16}}, l1_table_bl, r); + } + + void expect_read_l2_table(MockStreamInterface& mock_stream_interface, + uint64_t l2_table_offset, + std::optional<std::vector<uint64_t>>&& l2_table, + int r) { + size_t l2_table_size = 1<<(16 - 3); // cluster_bit - 3 bits for offset + bufferlist l2_table_bl; + if (!l2_table) { + l2_table.emplace(l2_table_size); + } + + l2_table->resize(l2_table_size); + for (size_t idx = 0; idx < l2_table->size(); ++idx) { + (*l2_table)[idx] = htobe64((*l2_table)[idx]); + } + + l2_table_bl.append( + std::string_view(reinterpret_cast<char*>(l2_table->data()), + l2_table->size() * sizeof(uint64_t))); + expect_stream_read(mock_stream_interface, + {{l2_table_offset, l2_table->size() * sizeof(uint64_t)}}, + l2_table_bl, r); + } + + void expect_read_cluster(MockStreamInterface& mock_stream_interface, + uint64_t cluster_offset, uint32_t offset, + const bufferlist& bl, int r) { + uint32_t cluster_size = 1<<16; + + bufferlist cluster_bl; + if (offset > 0) { + cluster_size -= offset; + cluster_bl.append_zero(offset); + } + + cluster_size -= bl.length(); + cluster_bl.append(bl); + + if (cluster_size > 0) { + cluster_bl.append_zero(cluster_size); + } + + expect_stream_read(mock_stream_interface, + {{cluster_offset, cluster_bl.length()}}, cluster_bl, r); + } + + void expect_open(MockStreamInterface& mock_stream_interface, + std::optional<std::vector<uint64_t>>&& l1_table) { + expect_stream_open(mock_stream_interface, 0); + + expect_probe_header(mock_stream_interface, QCOW_MAGIC, 2, 0); + + expect_read_header(mock_stream_interface, 0, 0); + + expect_read_l1_table(mock_stream_interface, std::move(l1_table), 0); + } + + void expect_read_snapshot_header(MockStreamInterface& mock_stream_interface, + const std::string& id, + const std::string& name, + uint64_t l1_table_offset, + uint64_t* snapshot_offset, int r) { + QCowSnapshotHeader snapshot_header; + memset(&snapshot_header, 0, sizeof(snapshot_header)); + snapshot_header.id_str_size = htobe16(id.size()); + snapshot_header.name_size = htobe16(name.size()); + snapshot_header.extra_data_size = htobe32(16); + snapshot_header.l1_size = htobe32(1); + snapshot_header.l1_table_offset = htobe64(l1_table_offset); + + bufferlist snapshot_header_bl; + snapshot_header_bl.append( + std::string_view(reinterpret_cast<char*>(&snapshot_header), + sizeof(snapshot_header))); + expect_stream_read(mock_stream_interface, + {{*snapshot_offset, sizeof(snapshot_header)}}, + snapshot_header_bl, r); + *snapshot_offset += sizeof(snapshot_header); + } + + void expect_read_snapshot_header_extra( + MockStreamInterface& mock_stream_interface, + const std::string& id, const std::string& name, uint64_t size, + uint64_t* snapshot_offset, int r) { + QCowSnapshotExtraData snapshot_header_extra; + memset(&snapshot_header_extra, 0, sizeof(snapshot_header_extra)); + snapshot_header_extra.disk_size = htobe64(size); + + bufferlist snapshot_header_extra_bl; + snapshot_header_extra_bl.append( + std::string_view(reinterpret_cast<char*>(&snapshot_header_extra), 16)); + snapshot_header_extra_bl.append(id); + snapshot_header_extra_bl.append(name); + expect_stream_read(mock_stream_interface, + {{*snapshot_offset, 16 + id.size() + name.size()}}, + snapshot_header_extra_bl, r); + + *snapshot_offset += 16 + id.size() + name.size(); + *snapshot_offset = p2roundup(*snapshot_offset, static_cast<uint64_t>(8)); + } + + void expect_read_snapshot_l1_table(MockStreamInterface& mock_stream_interface, + uint64_t l1_table_offset, int r) { + uint64_t l2_table_cluster = htobe64(l1_table_offset); + + bufferlist snapshot_l1_table_bl; + snapshot_l1_table_bl.append( + std::string_view(reinterpret_cast<char*>(&l2_table_cluster), 8)); + expect_stream_read(mock_stream_interface, {{l1_table_offset, 8}}, + snapshot_l1_table_bl, r); + } + + + void expect_read_snapshot(MockStreamInterface& mock_stream_interface, + const std::string& id, const std::string& name, + uint64_t size, uint64_t l1_table_offset, + uint64_t* snapshot_offset) { + expect_read_snapshot_header(mock_stream_interface, id, name, + l1_table_offset, snapshot_offset, 0); + expect_read_snapshot_header_extra(mock_stream_interface, id, name, + size, snapshot_offset, 0); + expect_read_snapshot_l1_table(mock_stream_interface, l1_table_offset, 0); + } + + json_spirit::mObject json_object; +}; + +TEST_F(TestMockMigrationQCOWFormat, OpenCloseV1) { + 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); + + int expected_open_ret_val = 0; + QCowHeaderV1 qcow_header; + memset(&qcow_header, 0, sizeof(qcow_header)); + qcow_header.magic = htobe32(QCOW_MAGIC); + qcow_header.version = htobe32(1); + qcow_header.size = htobe64(1<<30); + qcow_header.l1_table_offset = htobe64(1<<20); + qcow_header.cluster_bits = 16; + qcow_header.l2_bits = 13; + + bufferlist probe_bl; + probe_bl.append(std::string_view(reinterpret_cast<char*>(&qcow_header), 8)); + expect_stream_read(*mock_stream_interface, {{0, 8}}, probe_bl, 0); + +#ifdef WITH_RBD_MIGRATION_FORMAT_QCOW_V1 + + bufferlist header_bl; + header_bl.append(std::string_view(reinterpret_cast<char*>(&qcow_header), + sizeof(qcow_header))); + expect_stream_read(*mock_stream_interface, {{0, sizeof(qcow_header)}}, + header_bl, 0); + + bufferlist l1_table_bl; + l1_table_bl.append_zero(16); + expect_stream_read(*mock_stream_interface, {{1<<20, 16}}, l1_table_bl, 0); + +#else // WITH_RBD_MIGRATION_FORMAT_QCOW_V1 + + expected_open_ret_val = -ENOTSUP; + +#endif // WITH_RBD_MIGRATION_FORMAT_QCOW_V1 + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(expected_open_ret_val, ctx1.wait()); + + C_SaferCond ctx2; + mock_qcow_format.close(&ctx2); + ASSERT_EQ(0, ctx2.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, OpenCloseV2) { + 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_open(*mock_stream_interface, {}); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(0, ctx1.wait()); + + C_SaferCond ctx2; + mock_qcow_format.close(&ctx2); + ASSERT_EQ(0, ctx2.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, ProbeInvalidMagic) { + 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_probe_header(*mock_stream_interface, 0, 2, 0); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(-EINVAL, ctx1.wait()); + + C_SaferCond ctx2; + mock_qcow_format.close(&ctx2); + ASSERT_EQ(0, ctx2.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, ProbeInvalidVersion) { + 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_probe_header(*mock_stream_interface, QCOW_MAGIC, 0, 0); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(-EINVAL, ctx1.wait()); + + C_SaferCond ctx2; + mock_qcow_format.close(&ctx2); + ASSERT_EQ(0, ctx2.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, ProbeError) { + 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_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, -EIO); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(-EIO, ctx1.wait()); + + C_SaferCond ctx2; + mock_qcow_format.close(&ctx2); + ASSERT_EQ(0, ctx2.wait()); +} + +#ifdef WITH_RBD_MIGRATION_FORMAT_QCOW_V1 + +TEST_F(TestMockMigrationQCOWFormat, ReadHeaderV1Error) { + 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_probe_header(*mock_stream_interface, QCOW_MAGIC, 1, 0); + + QCowHeaderV1 qcow_header; + memset(&qcow_header, 0, sizeof(qcow_header)); + qcow_header.magic = htobe32(QCOW_MAGIC); + qcow_header.version = htobe32(1); + qcow_header.size = htobe64(1<<30); + qcow_header.l1_table_offset = htobe64(1<<20); + qcow_header.cluster_bits = 16; + qcow_header.l2_bits = 13; + + bufferlist header_bl; + header_bl.append(std::string_view(reinterpret_cast<char*>(&qcow_header), + sizeof(qcow_header))); + expect_stream_read(*mock_stream_interface, {{0, sizeof(qcow_header)}}, + header_bl, -EPERM); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(-EPERM, ctx1.wait()); + + C_SaferCond ctx2; + mock_qcow_format.close(&ctx2); + ASSERT_EQ(0, ctx2.wait()); +} + +#endif // WITH_RBD_MIGRATION_FORMAT_QCOW_V1 + +TEST_F(TestMockMigrationQCOWFormat, ReadHeaderV2Error) { + 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_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0); + + expect_read_header(*mock_stream_interface, 0, -EIO); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(-EIO, ctx1.wait()); + + C_SaferCond ctx2; + mock_qcow_format.close(&ctx2); + ASSERT_EQ(0, ctx2.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, ReadL1TableError) { + 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_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0); + + expect_read_header(*mock_stream_interface, 0, 0); + + expect_read_l1_table(*mock_stream_interface, {}, -EPERM); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(-EPERM, ctx1.wait()); + + C_SaferCond ctx2; + mock_qcow_format.close(&ctx2); + ASSERT_EQ(0, ctx2.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, Snapshots) { + 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_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0); + + expect_read_header(*mock_stream_interface, 2, 0); + + uint64_t snapshot_offset = 1<<21; + expect_read_snapshot(*mock_stream_interface, "1", "snap1", 1<<29, 1<<22, + &snapshot_offset); + expect_read_snapshot(*mock_stream_interface, "2", "snap2", 1<<29, 1<<22, + &snapshot_offset); + + expect_read_l1_table(*mock_stream_interface, {}, 0); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(0, ctx1.wait()); + + FormatInterface::SnapInfos snap_infos; + C_SaferCond ctx2; + mock_qcow_format.get_snapshots(&snap_infos, &ctx2); + ASSERT_EQ(0, ctx2.wait()); + + FormatInterface::SnapInfos expected_snap_infos{ + {1, {"snap1", cls::rbd::UserSnapshotNamespace{}, 1<<29, {}, 0, 0, {}}}, + {2, {"snap2", cls::rbd::UserSnapshotNamespace{}, 1<<29, {}, 0, 0, {}}}}; + ASSERT_EQ(expected_snap_infos, snap_infos); + + C_SaferCond ctx3; + mock_qcow_format.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, SnapshotHeaderError) { + 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_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0); + + expect_read_header(*mock_stream_interface, 2, 0); + + uint64_t snapshot_offset = 1<<21; + expect_read_snapshot_header(*mock_stream_interface, "1", "snap1", + 1<<22, &snapshot_offset, -EINVAL); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(-EINVAL, ctx1.wait()); + + C_SaferCond ctx2; + mock_qcow_format.close(&ctx2); + ASSERT_EQ(0, ctx2.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, SnapshotExtraError) { + 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_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0); + + expect_read_header(*mock_stream_interface, 2, 0); + + uint64_t snapshot_offset = 1<<21; + expect_read_snapshot_header(*mock_stream_interface, "1", "snap1", + 1<<22, &snapshot_offset, 0); + expect_read_snapshot_header_extra(*mock_stream_interface, "1", "snap1", + 1<<29, &snapshot_offset, -EINVAL); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(-EINVAL, ctx1.wait()); + + C_SaferCond ctx2; + mock_qcow_format.close(&ctx2); + ASSERT_EQ(0, ctx2.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, SnapshotL1TableError) { + 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_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0); + + expect_read_header(*mock_stream_interface, 2, 0); + + uint64_t snapshot_offset = 1<<21; + expect_read_snapshot_header(*mock_stream_interface, "1", "snap1", + 1<<22, &snapshot_offset, 0); + expect_read_snapshot_header_extra(*mock_stream_interface, "1", "snap1", + 1<<29, &snapshot_offset, 0); + expect_read_snapshot_l1_table(*mock_stream_interface, 1<<22, -EINVAL); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(-EINVAL, ctx1.wait()); + + C_SaferCond ctx2; + mock_qcow_format.close(&ctx2); + ASSERT_EQ(0, ctx2.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, GetImageSize) { + 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_open(*mock_stream_interface, {}); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(0, ctx1.wait()); + + uint64_t size = 0; + C_SaferCond ctx2; + mock_qcow_format.get_image_size(CEPH_NOSNAP, &size, &ctx2); + ASSERT_EQ(0, ctx2.wait()); + ASSERT_EQ(1<<30, size); + + C_SaferCond ctx3; + mock_qcow_format.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, GetImageSizeSnap) { + 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_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0); + + expect_read_header(*mock_stream_interface, 1, 0); + + uint64_t snapshot_offset = 1<<21; + expect_read_snapshot(*mock_stream_interface, "1", "snap1", 1<<29, 1<<22, + &snapshot_offset); + + expect_read_l1_table(*mock_stream_interface, {}, 0); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(0, ctx1.wait()); + + uint64_t size = 0; + C_SaferCond ctx2; + mock_qcow_format.get_image_size(1U, &size, &ctx2); + ASSERT_EQ(0, ctx2.wait()); + ASSERT_EQ(1<<29, size); + + C_SaferCond ctx3; + mock_qcow_format.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, GetImageSizeSnapDNE) { + 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_open(*mock_stream_interface, {}); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(0, ctx1.wait()); + + uint64_t size = 0; + C_SaferCond ctx2; + mock_qcow_format.get_image_size(1U, &size, &ctx2); + ASSERT_EQ(-ENOENT, ctx2.wait()); + + C_SaferCond ctx3; + mock_qcow_format.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, 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_open(*mock_stream_interface, {{1ULL<<40}}); + + expect_read_l2_table(*mock_stream_interface, 1ULL<<40, + {{0, 1ULL<<32}}, 0); + + bufferlist expect_bl; + expect_bl.append(std::string(123, '1')); + expect_read_cluster(*mock_stream_interface, 1ULL<<32, 123, expect_bl, 0); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&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}; + ASSERT_TRUE(mock_qcow_format.read(aio_comp, CEPH_NOSNAP, {{65659, 123}}, + std::move(read_result), 0, 0, {})); + ASSERT_EQ(123, ctx2.wait()); + ASSERT_EQ(expect_bl, bl); + + C_SaferCond ctx3; + mock_qcow_format.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, ReadL1DNE) { + 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_open(*mock_stream_interface, {}); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&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}; + ASSERT_TRUE(mock_qcow_format.read(aio_comp, CEPH_NOSNAP, {{234, 123}}, + std::move(read_result), 0, 0, {})); + ASSERT_EQ(123, ctx2.wait()); + bufferlist expect_bl; + expect_bl.append_zero(123); + ASSERT_EQ(expect_bl, bl); + + C_SaferCond ctx3; + mock_qcow_format.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, ReadL2DNE) { + 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_open(*mock_stream_interface, {{1ULL<<40}}); + + expect_read_l2_table(*mock_stream_interface, 1ULL<<40, + {{0, 1ULL<<32}}, 0); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&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}; + ASSERT_TRUE(mock_qcow_format.read(aio_comp, CEPH_NOSNAP, {{234, 123}}, + std::move(read_result), 0, 0, {})); + ASSERT_EQ(123, ctx2.wait()); + bufferlist expect_bl; + expect_bl.append_zero(123); + ASSERT_EQ(expect_bl, bl); + + C_SaferCond ctx3; + mock_qcow_format.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, ReadZero) { + 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_open(*mock_stream_interface, {{1ULL<<40}}); + + expect_read_l2_table(*mock_stream_interface, 1ULL<<40, + {{0, QCOW_OFLAG_ZERO}}, 0); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&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}; + ASSERT_TRUE(mock_qcow_format.read(aio_comp, CEPH_NOSNAP, {{65659, 123}}, + std::move(read_result), 0, 0, {})); + ASSERT_EQ(123, ctx2.wait()); + bufferlist expect_bl; + expect_bl.append_zero(123); + ASSERT_EQ(expect_bl, bl); + + C_SaferCond ctx3; + mock_qcow_format.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, ReadSnap) { + 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_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0); + + expect_read_header(*mock_stream_interface, 1, 0); + + uint64_t snapshot_offset = 1<<21; + expect_read_snapshot(*mock_stream_interface, "1", "snap1", 1<<29, 1<<22, + &snapshot_offset); + + expect_read_l1_table(*mock_stream_interface, {}, 0); + + expect_read_l2_table(*mock_stream_interface, 1<<22, + {{0, 1ULL<<32}}, 0); + + bufferlist expect_bl; + expect_bl.append(std::string(123, '1')); + expect_read_cluster(*mock_stream_interface, 1ULL<<32, 123, expect_bl, 0); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&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}; + ASSERT_TRUE(mock_qcow_format.read(aio_comp, 1, {{65659, 123}}, + std::move(read_result), 0, 0, {})); + ASSERT_EQ(123, ctx2.wait()); + ASSERT_EQ(expect_bl, bl); + + C_SaferCond ctx3; + mock_qcow_format.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, ReadSnapDNE) { + 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_open(*mock_stream_interface, {{1ULL<<40}}); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&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}; + ASSERT_TRUE(mock_qcow_format.read(aio_comp, 1, {{65659, 123}}, + std::move(read_result), 0, 0, {})); + ASSERT_EQ(-ENOENT, ctx2.wait()); + + C_SaferCond ctx3; + mock_qcow_format.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, ReadClusterCacheHit) { + 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_open(*mock_stream_interface, {{1ULL<<40}}); + + expect_read_l2_table(*mock_stream_interface, 1ULL<<40, + {{0, 1ULL<<32}}, 0); + + bufferlist expect_bl1; + expect_bl1.append(std::string(123, '1')); + + bufferlist expect_bl2; + expect_bl2.append(std::string(234, '2')); + + bufferlist cluster_bl; + cluster_bl.append_zero(123); + cluster_bl.append(expect_bl1); + cluster_bl.append_zero(234); + cluster_bl.append(expect_bl2); + + expect_read_cluster(*mock_stream_interface, 1ULL<<32, 0, cluster_bl, 0); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(0, ctx1.wait()); + + C_SaferCond ctx2; + auto aio_comp1 = io::AioCompletion::create_and_start( + &ctx2, m_image_ctx, io::AIO_TYPE_READ); + bufferlist bl1; + io::ReadResult read_result1{&bl1}; + ASSERT_TRUE(mock_qcow_format.read(aio_comp1, CEPH_NOSNAP, {{65659, 123}}, + std::move(read_result1), 0, 0, {})); + ASSERT_EQ(123, ctx2.wait()); + ASSERT_EQ(expect_bl1, bl1); + + C_SaferCond ctx3; + auto aio_comp2 = io::AioCompletion::create_and_start( + &ctx3, m_image_ctx, io::AIO_TYPE_READ); + bufferlist bl2; + io::ReadResult read_result2{&bl2}; + ASSERT_TRUE(mock_qcow_format.read(aio_comp2, CEPH_NOSNAP, {{66016, 234}}, + std::move(read_result2), 0, 0, {})); + ASSERT_EQ(234, ctx3.wait()); + ASSERT_EQ(expect_bl2, bl2); + + C_SaferCond ctx4; + mock_qcow_format.close(&ctx4); + ASSERT_EQ(0, ctx4.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, ReadClusterError) { + 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_open(*mock_stream_interface, {{1ULL<<40}}); + + expect_read_l2_table(*mock_stream_interface, 1ULL<<40, + {{0, 1ULL<<32}}, 0); + + bufferlist expect_bl; + expect_bl.append(std::string(123, '1')); + expect_read_cluster(*mock_stream_interface, 1ULL<<32, 123, expect_bl, -EIO); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&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}; + ASSERT_TRUE(mock_qcow_format.read(aio_comp, CEPH_NOSNAP, {{65659, 123}}, + std::move(read_result), 0, 0, {})); + ASSERT_EQ(-EIO, ctx2.wait()); + + C_SaferCond ctx3; + mock_qcow_format.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, ReadL2TableError) { + 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_open(*mock_stream_interface, {{1ULL<<40}}); + + expect_read_l2_table(*mock_stream_interface, 1ULL<<40, + {{0, 1ULL<<32}}, -EIO); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&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}; + ASSERT_TRUE(mock_qcow_format.read(aio_comp, CEPH_NOSNAP, {{65659, 123}}, + std::move(read_result), 0, 0, {})); + ASSERT_EQ(-EIO, ctx2.wait()); + + C_SaferCond ctx3; + mock_qcow_format.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +TEST_F(TestMockMigrationQCOWFormat, ListSnaps) { + 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_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0); + + expect_read_header(*mock_stream_interface, 2, 0); + + uint64_t snapshot_offset = 1<<21; + expect_read_snapshot(*mock_stream_interface, "1", "snap1", 1<<29, 1<<22, + &snapshot_offset); + expect_read_snapshot(*mock_stream_interface, "2", "snap2", 1<<29, 1<<23, + &snapshot_offset); + + expect_read_l1_table(*mock_stream_interface, {{1ULL<<28}}, 0); + + io::SparseExtents sparse_extents_1; + sparse_extents_1.insert(0, 196608, {io::SPARSE_EXTENT_STATE_DATA, 196608}); + expect_read_l2_table(*mock_stream_interface, 1ULL<<22, + {{1ULL<<23, 1ULL<<24, 1ULL<<25}}, 0); + + io::SparseExtents sparse_extents_2; + sparse_extents_2.insert(0, 65536, {io::SPARSE_EXTENT_STATE_DATA, 65536}); + sparse_extents_2.insert(65536, 65536, + {io::SPARSE_EXTENT_STATE_ZEROED, 65536}); + expect_read_l2_table(*mock_stream_interface, 1ULL<<23, + {{1ULL<<26, QCOW_OFLAG_ZERO, 1ULL<<25}}, 0); + + io::SparseExtents sparse_extents_head; + sparse_extents_head.insert(131072, 65536, + {io::SPARSE_EXTENT_STATE_DATA, 65536}); + expect_read_l2_table(*mock_stream_interface, 1ULL<<28, + {{1ULL<<26, QCOW_OFLAG_ZERO, 1ULL<<27}}, 0); + + expect_stream_close(*mock_stream_interface, 0); + + MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object, + &mock_source_spec_builder); + + C_SaferCond ctx1; + mock_qcow_format.open(&ctx1); + ASSERT_EQ(0, ctx1.wait()); + + C_SaferCond ctx2; + io::SnapshotDelta snapshot_delta; + mock_qcow_format.list_snaps({{0, 196608}}, {1, CEPH_NOSNAP}, 0, + &snapshot_delta, {}, &ctx2); + ASSERT_EQ(0, ctx2.wait()); + + io::SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{1, 1}] = sparse_extents_1; + expected_snapshot_delta[{CEPH_NOSNAP, 2}] = sparse_extents_2; + expected_snapshot_delta[{CEPH_NOSNAP, CEPH_NOSNAP}] = sparse_extents_head; + ASSERT_EQ(expected_snapshot_delta, snapshot_delta); + + C_SaferCond ctx3; + mock_qcow_format.close(&ctx3); + ASSERT_EQ(0, ctx3.wait()); +} + +} // namespace migration +} // namespace librbd |