From: Adam Lyon-Jones Date: Tue, 10 Jun 2025 08:12:47 +0000 (+0100) Subject: test/osd: Extend ceph_test_rados_io_sequence to support copy operations X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=55c3eb2f2e3cf2fc82bd3648ea363b2955afd884;p=ceph.git test/osd: Extend ceph_test_rados_io_sequence to support copy operations - Adds support for the I/O exerciser to handle sequences involving pairs of objects, including a swap operation to tell it which object to treat as active. This is supported in interactive mode. - Adds an operation to copy data from one object to another using copy-from. This is supported in interactive mode. - Adds support for using the truncate operation to make an object bigger. - Adds a new CLI argument (--object_copy) to specify a secondary object to use. - Adds a new sequence (SEQUENCE15) to test copying from one object to another. Signed-off-by: Adam Lyon-Jones --- diff --git a/src/common/io_exerciser/DataGenerator.cc b/src/common/io_exerciser/DataGenerator.cc index 573c38714b1c..45fc5f60b2f1 100644 --- a/src/common/io_exerciser/DataGenerator.cc +++ b/src/common/io_exerciser/DataGenerator.cc @@ -226,7 +226,7 @@ bool HeaderedSeededRandomGenerator::validate(bufferlist& bufferlist, } if (!invalid_block_offsets.empty()) { - dout(0) << "Miscompare for read of " << m_model.get_oid() << + dout(0) << "Miscompare for read of " << m_model.get_primary_oid() << " offset=" << offset << " length=" << length << dendl; printDebugInformationForOffsets(offset, invalid_block_offsets, bufferlist); } diff --git a/src/common/io_exerciser/IoOp.cc b/src/common/io_exerciser/IoOp.cc index 4b9fc57ce398..4bb6fb761eb0 100644 --- a/src/common/io_exerciser/IoOp.cc +++ b/src/common/io_exerciser/IoOp.cc @@ -11,6 +11,8 @@ using BarrierOp = ceph::io_exerciser::BarrierOp; using CreateOp = ceph::io_exerciser::CreateOp; using RemoveOp = ceph::io_exerciser::RemoveOp; using ConsistencyOp = ceph::io_exerciser::ConsistencyOp; +using SwapOp = ceph::io_exerciser::SwapOp; +using CopyOp = ceph::io_exerciser::CopyOp; using SingleReadOp = ceph::io_exerciser::SingleReadOp; using DoubleReadOp = ceph::io_exerciser::DoubleReadOp; using TripleReadOp = ceph::io_exerciser::TripleReadOp; @@ -76,6 +78,22 @@ std::unique_ptr RemoveOp::generate() { std::string RemoveOp::to_string(uint64_t block_size) const { return "Remove"; } +SwapOp::SwapOp() : TestOp() {} + +std::unique_ptr SwapOp::generate() { + return std::make_unique(); +} + +std::string SwapOp::to_string(uint64_t block_size) const { return "Swap"; } + +CopyOp::CopyOp() : TestOp() {} + +std::unique_ptr CopyOp::generate() { + return std::make_unique(); +} + +std::string CopyOp::to_string(uint64_t block_size) const { return "Copy"; } + template ceph::io_exerciser::ReadWriteOp::ReadWriteOp( std::array&& offset, diff --git a/src/common/io_exerciser/IoOp.h b/src/common/io_exerciser/IoOp.h index ae58f6c40cd5..88165db8180b 100644 --- a/src/common/io_exerciser/IoOp.h +++ b/src/common/io_exerciser/IoOp.h @@ -68,6 +68,20 @@ class ConsistencyOp : public TestOp { std::string to_string(uint64_t block_size) const override; }; +class SwapOp : public TestOp { + public: + SwapOp(); + static std::unique_ptr generate(); + std::string to_string(uint64_t block_size) const override; +}; + +class CopyOp : public TestOp { + public: + CopyOp(); + static std::unique_ptr generate(); + std::string to_string(uint64_t block_size) const override; +}; + template class ReadWriteOp : public TestOp { public: @@ -266,4 +280,4 @@ class ClearWriteErrorInjectOp } }; } // namespace io_exerciser -} // namespace ceph \ No newline at end of file +} // namespace ceph diff --git a/src/common/io_exerciser/IoSequence.cc b/src/common/io_exerciser/IoSequence.cc index f855aea3e428..2634b9a38a76 100644 --- a/src/common/io_exerciser/IoSequence.cc +++ b/src/common/io_exerciser/IoSequence.cc @@ -56,6 +56,9 @@ std::ostream& ceph::io_exerciser::operator<<(std::ostream& os, case Sequence::SEQUENCE_SEQ14: os << "SEQUENCE_SEQ14"; break; + case Sequence::SEQUENCE_SEQ15: + os << "SEQUENCE_SEQ15"; + break; case Sequence::SEQUENCE_END: os << "SEQUENCE_END"; break; @@ -103,6 +106,8 @@ std::unique_ptr IoSequence::generate_sequence( return std::make_unique(obj_size_range, seed, check_consistency); case Sequence::SEQUENCE_SEQ14: return std::make_unique(obj_size_range, seed, check_consistency); + case Sequence::SEQUENCE_SEQ15: + return std::make_unique(obj_size_range, seed, check_consistency); default: break; } @@ -120,6 +125,7 @@ IoSequence::IoSequence(std::pair obj_size_range, int seed, bool check_ consistency_in_progress(false), consistency_request_sent(false), check_consistency(check_consistency), + swap(false), obj_size(min_obj_size), step(-1), seed(seed) { @@ -216,6 +222,10 @@ std::unique_ptr IoSequence::next() { if (done) { return DoneOp::generate(); } + if (swap) { + swap = false; + return SwapOp::generate(); + } if (create) { create = false; barrier = true; @@ -745,3 +755,108 @@ std::unique_ptr ceph::io_exerciser::Seq14::_next() { } return r; } + +ceph::io_exerciser::Seq15::Seq15(std::pair obj_size_range, int seed, bool check_consistency) + : IoSequence(obj_size_range, seed, check_consistency), offset(0) { + select_random_object_size(); + if (obj_size < 4) { + obj_size = 4; + } + primary_size = obj_size; + secondary_size = primary_size - 3; + } + +Sequence ceph::io_exerciser::Seq15::get_id() const { + return Sequence::SEQUENCE_SEQ15; +} + +std::string ceph::io_exerciser::Seq15::get_name() const { + return "Different permutations of writes to objects of different sizes, then copy, resize and read"; +} + +std::unique_ptr ceph::io_exerciser::Seq15::_next() { + std::unique_ptr r = BarrierOp::generate(); + + using Stage = ceph::io_exerciser::Seq15::Stage; + auto next_stage = [this]() { + stage = static_cast(static_cast(stage)+1); + }; + switch (stage) { + case Stage::WRITE_PRIMARY: + // Seq0 with writes instead of reads + length = 1 + rng(obj_size - 1); + if (offset >= obj_size) { + offset = 0; + next_stage(); + break; + } + if (offset + length > obj_size) { + r = SingleWriteOp::generate(offset, obj_size - offset); + } else { + r = SingleWriteOp::generate(offset, length); + } + offset += length; + break; + case Stage::CREATE_SECONDARY: + obj_size = secondary_size; + create = true; + r = SwapOp::generate(); + next_stage(); + break; + case Stage::WRITE_SECONDARY: + // Seq9 + if (!doneread) { + if (!donebarrier) { + donebarrier = true; + r = BarrierOp::generate(); + break; + } + doneread = true; + barrier = true; + r = SingleReadOp::generate(0, obj_size); + break; + } + length++; + if (length > obj_size - offset) { + length = 1; + offset++; + if (offset >= obj_size) { + offset = 0; + next_stage(); + break; + } + } + doneread = false; + donebarrier = false; + r = SingleWriteOp::generate(offset, length); + break; + case Stage::COPY_FROM_SECONDARY: + r = CopyOp::generate(); + next_stage(); + break; + case Stage::READ_SECONDARY: + r = SingleReadOp::generate(0, obj_size); + next_stage(); + break; + case Stage::SWAP_TO_PRIMARY: + r = SwapOp::generate(); + next_stage(); + break; + case Stage::TRUNCATE_PRIMARY: + obj_size = obj_size + 2; + r = TruncateOp::generate(obj_size); + next_stage(); + break; + case Stage::READ_PRIMARY: + r = SingleReadOp::generate(0, obj_size); + next_stage(); + break; + case Stage::DONE: + [[fallthrough]]; + default: + done = true; + break; + } + + return r; +} diff --git a/src/common/io_exerciser/IoSequence.h b/src/common/io_exerciser/IoSequence.h index 86282cabcc1f..bbf564fcdd7a 100644 --- a/src/common/io_exerciser/IoSequence.h +++ b/src/common/io_exerciser/IoSequence.h @@ -46,6 +46,7 @@ enum class Sequence { SEQUENCE_SEQ12, SEQUENCE_SEQ13, SEQUENCE_SEQ14, + SEQUENCE_SEQ15, SEQUENCE_END, SEQUENCE_BEGIN = SEQUENCE_SEQ0 @@ -87,6 +88,7 @@ class IoSequence { bool consistency_in_progress; bool consistency_request_sent; bool check_consistency; + bool swap; uint64_t obj_size; int step; int seed; @@ -305,5 +307,34 @@ class Seq14 : public IoSequence { std::string get_name() const override; std::unique_ptr _next() override; }; + +class Seq15 : public IoSequence { + private: + uint64_t offset; + uint64_t length; + uint64_t primary_size; + uint64_t secondary_size; + bool doneread = true; + bool donebarrier = false; + enum class Stage { + WRITE_PRIMARY, + CREATE_SECONDARY, + WRITE_SECONDARY, + COPY_FROM_SECONDARY, + READ_SECONDARY, + SWAP_TO_PRIMARY, + TRUNCATE_PRIMARY, + READ_PRIMARY, + DONE + }; + Stage stage; + + public: + Seq15(std::pair obj_size_range, int seed, bool check_consistency); + + Sequence get_id() const override; + std::string get_name() const override; + std::unique_ptr _next() override; +}; } // namespace io_exerciser } // namespace ceph diff --git a/src/common/io_exerciser/Model.cc b/src/common/io_exerciser/Model.cc index 6548e1eda7a8..536c406539ab 100644 --- a/src/common/io_exerciser/Model.cc +++ b/src/common/io_exerciser/Model.cc @@ -4,11 +4,28 @@ using Model = ceph::io_exerciser::Model; -Model::Model(const std::string& oid, uint64_t block_size) - : num_io(0), oid(oid), block_size(block_size) {} +Model::Model(const std::string& primary_oid, const std::string& secondary_oid, uint64_t block_size) + : num_io(0), primary_oid(primary_oid), secondary_oid(secondary_oid), block_size(block_size) {} -const uint64_t Model::get_block_size() const { return block_size; } +const std::string Model::get_primary_oid() const { return primary_oid; } + +const std::string Model::get_secondary_oid() const { return secondary_oid; } + +void Model::set_primary_oid(const std::string& new_oid) { + primary_oid = new_oid; +} -const std::string Model::get_oid() const { return oid; } +void Model::set_secondary_oid(const std::string& new_oid) { + secondary_oid = new_oid; +} + +void Model::swap_primary_secondary_oid() { + std::string old_primary; + old_primary = Model::get_primary_oid(); + Model::set_primary_oid(Model::get_secondary_oid()); + Model::set_secondary_oid(old_primary); +} + +const uint64_t Model::get_block_size() const { return block_size; } int Model::get_num_io() const { return num_io; } \ No newline at end of file diff --git a/src/common/io_exerciser/Model.h b/src/common/io_exerciser/Model.h index 5598378fe117..dc9cb50e22e1 100644 --- a/src/common/io_exerciser/Model.h +++ b/src/common/io_exerciser/Model.h @@ -29,17 +29,23 @@ class IoOp; class Model { protected: int num_io{0}; - std::string oid; + std::string primary_oid; + std::string secondary_oid; uint64_t block_size; + void set_primary_oid(const std::string& new_oid); + void set_secondary_oid(const std::string& new_oid); + public: - Model(const std::string& oid, uint64_t block_size); + Model(const std::string& primary_oid, const std::string& secondary_oid, uint64_t block_size); virtual ~Model() = default; virtual bool readyForIoOp(IoOp& op) = 0; virtual void applyIoOp(IoOp& op) = 0; - const std::string get_oid() const; + const std::string get_primary_oid() const; + const std::string get_secondary_oid() const; + void swap_primary_secondary_oid(); const uint64_t get_block_size() const; int get_num_io() const; }; diff --git a/src/common/io_exerciser/ObjectModel.cc b/src/common/io_exerciser/ObjectModel.cc index b4edf7925e2d..6cd682f4eb58 100644 --- a/src/common/io_exerciser/ObjectModel.cc +++ b/src/common/io_exerciser/ObjectModel.cc @@ -7,20 +7,20 @@ using ObjectModel = ceph::io_exerciser::ObjectModel; -ObjectModel::ObjectModel(const std::string& oid, uint64_t block_size, int seed) - : Model(oid, block_size), created(false) { +ObjectModel::ObjectModel(const std::string& primary_oid, const std::string& secondary_oid, uint64_t block_size, int seed) + : Model(primary_oid, secondary_oid, block_size), primary_created(false), secondary_created(false) { rng.seed(seed); } int ObjectModel::get_seed(uint64_t offset) const { - ceph_assert(offset < contents.size()); - return contents[offset]; + ceph_assert(offset < primary_contents.size()); + return primary_contents[offset]; } std::vector ObjectModel::get_seed_offsets(int seed) const { std::vector offsets; - for (size_t i = 0; i < contents.size(); i++) { - if (contents[i] == seed) { + for (size_t i = 0; i < primary_contents.size(); i++) { + if (primary_contents[i] == seed) { offsets.push_back(i); } } @@ -29,15 +29,15 @@ std::vector ObjectModel::get_seed_offsets(int seed) const { } std::string ObjectModel::to_string(int mask) const { - if (!created) { + if (!primary_created) { return "Object does not exist"; } std::string result = "{"; - for (uint64_t i = 0; i < contents.size(); i++) { + for (uint64_t i = 0; i < primary_contents.size(); i++) { if (i != 0) { result += ","; } - result += std::to_string(contents[i] & mask); + result += std::to_string(primary_contents[i] & mask); } result += "}"; return result; @@ -51,12 +51,14 @@ void ObjectModel::applyIoOp(IoOp& op) { }; auto verify_and_record_read_op = - [&contents = contents, &created = created, &num_io = num_io, + [&primary_contents = primary_contents, + &primary_created = primary_created, + &num_io = num_io, &reads = reads, &writes = writes](ReadWriteOp& readOp) { - ceph_assert(created); + ceph_assert(primary_created); for (int i = 0; i < N; i++) { - ceph_assert(readOp.offset[i] + readOp.length[i] <= contents.size()); + ceph_assert(readOp.offset[i] + readOp.length[i] <= primary_contents.size()); // Not allowed: read overlapping with parallel write ceph_assert(!writes.intersects(readOp.offset[i], readOp.length[i])); reads.union_insert(readOp.offset[i], readOp.length[i]); @@ -65,21 +67,23 @@ void ObjectModel::applyIoOp(IoOp& op) { }; auto verify_write_and_record_and_generate_seed = - [&generate_random, &contents = contents, &created = created, - &num_io = num_io, &reads = reads, + [&generate_random, &primary_contents = primary_contents, + &primary_created = primary_created, + &num_io = num_io, + &reads = reads, &writes = writes](ReadWriteOp writeOp) { - ceph_assert(created); + ceph_assert(primary_created); for (int i = 0; i < N; i++) { // Not allowed: write overlapping with parallel read or write ceph_assert(!reads.intersects(writeOp.offset[i], writeOp.length[i])); ceph_assert(!writes.intersects(writeOp.offset[i], writeOp.length[i])); writes.union_insert(writeOp.offset[i], writeOp.length[i]); - if (writeOp.offset[i] + writeOp.length[i] > contents.size()) { - contents.resize(writeOp.offset[i] + writeOp.length[i]); + if (writeOp.offset[i] + writeOp.length[i] > primary_contents.size()) { + primary_contents.resize(writeOp.offset[i] + writeOp.length[i]); } std::generate(std::execution::seq, - std::next(contents.begin(), writeOp.offset[i]), - std::next(contents.begin(), + std::next(primary_contents.begin(), writeOp.offset[i]), + std::next(primary_contents.begin(), writeOp.offset[i] + writeOp.length[i]), generate_random); } @@ -87,18 +91,20 @@ void ObjectModel::applyIoOp(IoOp& op) { }; auto verify_failed_write_and_record = - [&contents = contents, &created = created, &num_io = num_io, + [&primary_contents = primary_contents, + &primary_created = primary_created, + &num_io = num_io, &reads = reads, &writes = writes](ReadWriteOp writeOp) { // Ensure write should still be valid, even though we are expecting OSD // failure - ceph_assert(created); + ceph_assert(primary_created); for (int i = 0; i < N; i++) { // Not allowed: write overlapping with parallel read or write ceph_assert(!reads.intersects(writeOp.offset[i], writeOp.length[i])); ceph_assert(!writes.intersects(writeOp.offset[i], writeOp.length[i])); writes.union_insert(writeOp.offset[i], writeOp.length[i]); - ceph_assert(writeOp.offset[i] + writeOp.length[i] <= contents.size()); + ceph_assert(writeOp.offset[i] + writeOp.length[i] <= primary_contents.size()); } num_io++; }; @@ -109,31 +115,58 @@ void ObjectModel::applyIoOp(IoOp& op) { writes.clear(); break; + case OpType::Swap: { + bool temp = primary_created; + primary_created = secondary_created; + secondary_created = temp; + primary_contents.swap(secondary_contents); + reads.clear(); + writes.clear(); + } break; + + case OpType::Copy: + ceph_assert(primary_created && secondary_created); + ceph_assert(reads.empty()); + ceph_assert(writes.empty()); + // The target object may be larger than the source - however, it will be replaced by a new object rather than overwriting + // and padding the old object. Therefore, the target object should now be the same size as the source object. + secondary_contents.resize(primary_contents.size()); + std::copy(primary_contents.begin(), primary_contents.end(), secondary_contents.begin()); + break; + case OpType::Create: - ceph_assert(!created); + ceph_assert(!primary_created); ceph_assert(reads.empty()); ceph_assert(writes.empty()); - created = true; - contents.resize(static_cast(op).size); - std::generate(std::execution::seq, contents.begin(), contents.end(), + primary_created = true; + primary_contents.resize(static_cast(op).size); + std::generate(std::execution::seq, primary_contents.begin(), primary_contents.end(), generate_random); break; - case OpType::Truncate: - ceph_assert(created); + case OpType::Truncate: { + ceph_assert(primary_created); ceph_assert(reads.empty()); ceph_assert(writes.empty()); - contents.resize(static_cast(op).size); - break; + auto new_size = static_cast(op).size; + auto old_size = primary_contents.size(); + bool expand = new_size > old_size; + primary_contents.resize(new_size); + // Yes, truncate CAN be used to make an object bigger! + if (expand) { + std::generate(std::execution::seq, primary_contents.begin() + new_size, primary_contents.end(), + generate_random); + } + } break; case OpType::Remove: - ceph_assert(created); + ceph_assert(primary_created); ceph_assert(reads.empty()); ceph_assert(writes.empty()); - created = false; - contents.resize(0); + primary_created = false; + primary_contents.resize(0); break; - + case OpType::Read: { SingleReadOp& readOp = static_cast(op); verify_and_record_read_op(readOp); @@ -148,37 +181,40 @@ void ObjectModel::applyIoOp(IoOp& op) { } break; case OpType::Write: { - ceph_assert(created); + ceph_assert(primary_created); SingleWriteOp& writeOp = static_cast(op); verify_write_and_record_and_generate_seed(writeOp); } break; case OpType::Write2: { + ceph_assert(primary_created); DoubleWriteOp& writeOp = static_cast(op); verify_write_and_record_and_generate_seed(writeOp); } break; case OpType::Write3: { + ceph_assert(primary_created); TripleWriteOp& writeOp = static_cast(op); verify_write_and_record_and_generate_seed(writeOp); } break; case OpType::Append: { - ceph_assert(created); + ceph_assert(primary_created); SingleAppendOp& appendOp = static_cast(op); - appendOp.offset[0] = contents.size(); + appendOp.offset[0] = primary_contents.size(); verify_write_and_record_and_generate_seed(appendOp); } break; case OpType::FailedWrite: { - ceph_assert(created); + ceph_assert(primary_created); SingleWriteOp& writeOp = static_cast(op); verify_failed_write_and_record(writeOp); } break; case OpType::FailedWrite2: { + ceph_assert(primary_created); DoubleWriteOp& writeOp = static_cast(op); verify_failed_write_and_record(writeOp); - } break; case OpType::FailedWrite3: { + ceph_assert(primary_created); TripleWriteOp& writeOp = static_cast(op); verify_failed_write_and_record(writeOp); } break; @@ -189,9 +225,9 @@ void ObjectModel::applyIoOp(IoOp& op) { void ObjectModel::encode(ceph::buffer::list& bl) const { ENCODE_START(1, 1, bl); - encode(created, bl); - if (created) { - encode(contents, bl); + encode(primary_created, bl); + if (primary_created) { + encode(primary_contents, bl); } ENCODE_FINISH(bl); } @@ -199,11 +235,9 @@ void ObjectModel::encode(ceph::buffer::list& bl) const { void ObjectModel::decode(ceph::buffer::list::const_iterator& bl) { DECODE_START(1, bl); DECODE_OLDEST(1); - decode(created, bl); - if (created) { - decode(contents, bl); - } else { - contents.resize(0); + decode(primary_created, bl); + if (primary_created) { + decode(primary_contents, bl); } DECODE_FINISH(bl); } diff --git a/src/common/io_exerciser/ObjectModel.h b/src/common/io_exerciser/ObjectModel.h index 43d218813747..5bca4ea04f9a 100644 --- a/src/common/io_exerciser/ObjectModel.h +++ b/src/common/io_exerciser/ObjectModel.h @@ -25,8 +25,10 @@ namespace io_exerciser { class ObjectModel : public Model { private: - bool created; - std::vector contents; + bool primary_created; + bool secondary_created; + std::vector primary_contents; + std::vector secondary_contents; ceph::util::random_number_generator rng = ceph::util::random_number_generator(); @@ -43,7 +45,7 @@ class ObjectModel : public Model { interval_set writes; public: - ObjectModel(const std::string& oid, uint64_t block_size, int seed); + ObjectModel(const std::string& primary_oid, const std::string& secondary_oid, uint64_t block_size, int seed); int get_seed(uint64_t offset) const; std::vector get_seed_offsets(int seed) const; diff --git a/src/common/io_exerciser/OpType.h b/src/common/io_exerciser/OpType.h index 3c7f535839c3..af939614570e 100644 --- a/src/common/io_exerciser/OpType.h +++ b/src/common/io_exerciser/OpType.h @@ -18,6 +18,7 @@ enum class OpType { Create, // Create object and pattern with data Remove, // Remove object Consistency, // Check consistency of an object + Swap, // Swap primary and secondary object Read, // Read Read2, // Two reads in a single op Read3, // Three reads in a single op @@ -29,6 +30,7 @@ enum class OpType { FailedWrite, // A write which should fail FailedWrite2, // Two writes in one op which should fail FailedWrite3, // Three writes in one op which should fail + Copy, // Copy from primary to secondary object InjectReadError, // Op to tell OSD to inject read errors InjectWriteError, // Op to tell OSD to inject write errors ClearReadErrorInject, // Op to tell OSD to clear read error injects @@ -62,6 +64,10 @@ struct fmt::formatter { return fmt::format_to(ctx.out(), "Remove"); case ceph::io_exerciser::OpType::Consistency: return fmt::format_to(ctx.out(), "Consistency"); + case ceph::io_exerciser::OpType::Swap: + return fmt::format_to(ctx.out(), "Swap"); + case ceph::io_exerciser::OpType::Copy: + return fmt::format_to(ctx.out(), "Copy"); case ceph::io_exerciser::OpType::Read: return fmt::format_to(ctx.out(), "Read"); case ceph::io_exerciser::OpType::Read2: diff --git a/src/common/io_exerciser/RadosIo.cc b/src/common/io_exerciser/RadosIo.cc index 468b454273e1..90fc8c217c9e 100644 --- a/src/common/io_exerciser/RadosIo.cc +++ b/src/common/io_exerciser/RadosIo.cc @@ -43,15 +43,15 @@ int send_mon_command(S& s, librados::Rados& rados, const char* name, } // namespace RadosIo::RadosIo(librados::Rados& rados, boost::asio::io_context& asio, - const std::string& pool, const std::string& oid, + const std::string& pool, const std::string& primary_oid, const std::string& secondary_oid, const std::optional>& cached_shard_order, uint64_t block_size, int seed, int threads, ceph::mutex& lock, ceph::condition_variable& cond, bool is_replicated_pool, bool ec_optimizations) - : Model(oid, block_size), + : Model(primary_oid, secondary_oid, block_size), rados(rados), asio(asio), - om(std::make_unique(oid, block_size, seed)), + om(std::make_unique(primary_oid, secondary_oid, block_size, seed)), db(data_generation::DataGenerator::create_generator( data_generation::GenerationType::HeaderedSeededRandom, *om)), pool(pool), @@ -125,6 +125,10 @@ void RadosIo::applyIoOp(IoOp& op) { wait_for_io(0); break; + case OpType::Swap: + swap_primary_secondary_oid(); + break; + case OpType::Create: { start_io(); uint64_t opSize = static_cast(op).size; @@ -138,7 +142,7 @@ void RadosIo::applyIoOp(IoOp& op) { ceph_assert(ec == boost::system::errc::success); finish_io(); }; - librados::async_operate(asio.get_executor(), io, oid, + librados::async_operate(asio.get_executor(), io, primary_oid, std::move(wop), 0, nullptr, create_cb); break; } @@ -153,7 +157,7 @@ void RadosIo::applyIoOp(IoOp& op) { ceph_assert(ec == boost::system::errc::success); finish_io(); }; - librados::async_operate(asio.get_executor(), io, oid, std::move(wop), 0, + librados::async_operate(asio.get_executor(), io, primary_oid, std::move(wop), 0, nullptr, truncate_cb); break; } @@ -167,7 +171,7 @@ void RadosIo::applyIoOp(IoOp& op) { ceph_assert(ec == boost::system::errc::success); finish_io(); }; - librados::async_operate(asio.get_executor(), io, oid, + librados::async_operate(asio.get_executor(), io, primary_oid, std::move(wop), 0, nullptr, remove_cb); break; } @@ -176,7 +180,7 @@ void RadosIo::applyIoOp(IoOp& op) { start_io(); ceph_assert(cc); bool is_consistent = - cc->single_read_and_check_consistency(oid, block_size, 0, 0); + cc->single_read_and_check_consistency(primary_oid, block_size, 0, 0); if (!is_consistent) { std::stringstream strstream; cc->print_results(strstream); @@ -184,6 +188,19 @@ void RadosIo::applyIoOp(IoOp& op) { } ceph_assert(is_consistent); finish_io(); + } + + case OpType::Copy: { + start_io(); + auto op_info = std::make_shared>(); + librados::ObjectWriteOperation wop; + wop.copy_from(primary_oid.c_str(), io, io.get_last_version(), 0); + auto write_cb = [this](boost::system::error_code ec, version_t ver) { + ceph_assert(ec == boost::system::errc::success); + finish_io(); + }; + librados::async_operate(asio.get_executor(), io, secondary_oid, + std::move(wop), 0, nullptr, write_cb); break; } @@ -244,7 +261,7 @@ void RadosIo::applyReadWriteOp(IoOp& op) { } finish_io(); }; - librados::async_operate(asio.get_executor(), io, oid, + librados::async_operate(asio.get_executor(), io, primary_oid, std::move(rop), 0, nullptr, read_cb); num_io++; }; @@ -264,7 +281,7 @@ void RadosIo::applyReadWriteOp(IoOp& op) { ceph_assert(ec == boost::system::errc::success); finish_io(); }; - librados::async_operate(asio.get_executor(), io, oid, + librados::async_operate(asio.get_executor(), io, primary_oid, std::move(wop), 0, nullptr, write_cb); num_io++; }; @@ -285,7 +302,7 @@ void RadosIo::applyReadWriteOp(IoOp& op) { ceph_assert(ec != boost::system::errc::success); finish_io(); }; - librados::async_operate(asio.get_executor(), io, oid, + librados::async_operate(asio.get_executor(), io, primary_oid, std::move(wop), 0, nullptr, write_cb); num_io++; }; @@ -368,7 +385,7 @@ void RadosIo::applyInjectOp(IoOp& op) { int osd = -1; std::vector shard_order; - ceph::messaging::osd::OSDMapRequest osdMapRequest{pool, get_oid(), ""}; + ceph::messaging::osd::OSDMapRequest osdMapRequest{pool, get_primary_oid(), ""}; int rc = send_mon_command(osdMapRequest, rados, "OSDMapRequest", osdmap_inbl, &osdmap_outbl, formatter.get()); ceph_assert(rc == 0); @@ -389,7 +406,7 @@ void RadosIo::applyInjectOp(IoOp& op) { if (errorOp.type == 0) { ceph::messaging::osd::InjectECErrorRequest - injectErrorRequest{pool, oid, errorOp.shard, + injectErrorRequest{pool, primary_oid, errorOp.shard, errorOp.type, errorOp.when, errorOp.duration}; int rc = send_osd_command(osd, injectErrorRequest, rados, "InjectECErrorRequest", inject_inbl, @@ -398,7 +415,7 @@ void RadosIo::applyInjectOp(IoOp& op) { } else if (errorOp.type == 1) { ceph::messaging::osd::InjectECErrorRequest< InjectOpType::ReadMissingShard> - injectErrorRequest{pool, oid, errorOp.shard, + injectErrorRequest{pool, primary_oid, errorOp.shard, errorOp.type, errorOp.when, errorOp.duration}; int rc = send_osd_command(osd, injectErrorRequest, rados, "InjectECErrorRequest", inject_inbl, @@ -415,7 +432,7 @@ void RadosIo::applyInjectOp(IoOp& op) { if (errorOp.type == 0) { ceph::messaging::osd::InjectECErrorRequest< InjectOpType::WriteFailAndRollback> - injectErrorRequest{pool, oid, errorOp.shard, + injectErrorRequest{pool, primary_oid, errorOp.shard, errorOp.type, errorOp.when, errorOp.duration}; int rc = send_osd_command(osd, injectErrorRequest, rados, "InjectECErrorRequest", inject_inbl, @@ -423,7 +440,7 @@ void RadosIo::applyInjectOp(IoOp& op) { ceph_assert(rc == 0); } else if (errorOp.type == 3) { ceph::messaging::osd::InjectECErrorRequest - injectErrorRequest{pool, oid, errorOp.shard, + injectErrorRequest{pool, primary_oid, errorOp.shard, errorOp.type, errorOp.when, errorOp.duration}; int rc = send_osd_command(osd, injectErrorRequest, rados, "InjectECErrorRequest", inject_inbl, @@ -445,7 +462,7 @@ void RadosIo::applyInjectOp(IoOp& op) { if (errorOp.type == 0) { ceph::messaging::osd::InjectECClearErrorRequest - clearErrorInject{pool, oid, errorOp.shard, errorOp.type}; + clearErrorInject{pool, primary_oid, errorOp.shard, errorOp.type}; int rc = send_osd_command(osd, clearErrorInject, rados, "InjectECClearErrorRequest", inject_inbl, &inject_outbl, formatter.get()); @@ -453,7 +470,7 @@ void RadosIo::applyInjectOp(IoOp& op) { } else if (errorOp.type == 1) { ceph::messaging::osd::InjectECClearErrorRequest< InjectOpType::ReadMissingShard> - clearErrorInject{pool, oid, errorOp.shard, errorOp.type}; + clearErrorInject{pool, primary_oid, errorOp.shard, errorOp.type}; int rc = send_osd_command(osd, clearErrorInject, rados, "InjectECClearErrorRequest", inject_inbl, &inject_outbl, formatter.get()); @@ -471,7 +488,7 @@ void RadosIo::applyInjectOp(IoOp& op) { if (errorOp.type == 0) { ceph::messaging::osd::InjectECClearErrorRequest< InjectOpType::WriteFailAndRollback> - clearErrorInject{pool, oid, errorOp.shard, errorOp.type}; + clearErrorInject{pool, primary_oid, errorOp.shard, errorOp.type}; int rc = send_osd_command(osd, clearErrorInject, rados, "InjectECClearErrorRequest", inject_inbl, &inject_outbl, formatter.get()); @@ -479,7 +496,7 @@ void RadosIo::applyInjectOp(IoOp& op) { } else if (errorOp.type == 3) { ceph::messaging::osd::InjectECClearErrorRequest< InjectOpType::WriteOSDAbort> - clearErrorInject{pool, oid, errorOp.shard, errorOp.type}; + clearErrorInject{pool, primary_oid, errorOp.shard, errorOp.type}; int rc = send_osd_command(osd, clearErrorInject, rados, "InjectECClearErrorRequest", inject_inbl, &inject_outbl, formatter.get()); @@ -495,4 +512,4 @@ void RadosIo::applyInjectOp(IoOp& op) { fmt::format("Unsupported inject operation ({})", op.getOpType())); break; } -} \ No newline at end of file +} diff --git a/src/common/io_exerciser/RadosIo.h b/src/common/io_exerciser/RadosIo.h index ef43ad4317d8..d1464f3545cf 100644 --- a/src/common/io_exerciser/RadosIo.h +++ b/src/common/io_exerciser/RadosIo.h @@ -45,7 +45,7 @@ class RadosIo : public Model { public: RadosIo(librados::Rados& rados, boost::asio::io_context& asio, - const std::string& pool, const std::string& oid, + const std::string& pool, const std::string& primary_oid, const std::string& secondary_oid, const std::optional>& cached_shard_order, uint64_t block_size, int seed, int threads, ceph::mutex& lock, ceph::condition_variable& cond, bool is_replicated_pool, diff --git a/src/test/osd/ceph_test_rados_io_sequence/ceph_test_rados_io_sequence.cc b/src/test/osd/ceph_test_rados_io_sequence/ceph_test_rados_io_sequence.cc index 2d31bf872b60..423483438934 100644 --- a/src/test/osd/ceph_test_rados_io_sequence/ceph_test_rados_io_sequence.cc +++ b/src/test/osd/ceph_test_rados_io_sequence/ceph_test_rados_io_sequence.cc @@ -127,6 +127,9 @@ constexpr std::string_view usage[] = { "\t Run parallel test to multiple objects. First object is tested with", "\t default settings, other objects are tested with random settings", "", + "ceph_test_rados_io_sequence --object_copy ", + "\t Specify a second object to be used in copy operations", + "", "Advanced usage:", "", "ceph_test_rados_io_sequence --blocksize --km --plugin

", @@ -158,6 +161,8 @@ constexpr std::string_view usage[] = { "\t are specified with unit of blocksize. Supported commands:", "\t\t create ", "\t\t remove", + "\t\t swap", + "\t\t copy", "\t\t read|write|failedwrite ", "\t\t read2|write2|failedwrite2 ", "\t\t read3|write3|failedwrite3 ", @@ -181,7 +186,9 @@ po::options_description get_options_description() { "pool,p", po::value(), "existing pool name")( "profile", po::value(), "existing profile name")( "object,o", po::value()->default_value("test"), - "object name")("plugin", po::value(), "EC plugin")( + "primary object name")( + "object_copy", po::value()->default_value("test_copy"), "secondary object name")( + "plugin", po::value(), "EC plugin")( "chunksize,c", po::value(), "chunk size (default 4096)")( "km", po::value(), "k,m EC pool profile (default 2,2)")( "technique", po::value(), "EC profile technique")( @@ -195,7 +202,7 @@ po::options_description get_options_description() { "threads,t", po::value(), "number of threads of I/O per object (default 1)")( "parallel,p", po::value()->default_value(1), - "number of objects to exercise in parallel")( + "number of objects (or object pairs) to exercise in parallel")( "testrecovery", "Inject errors during sequences to test recovery processes of OSDs")( "checkconsistency", @@ -1011,7 +1018,7 @@ void ceph::io_sequence::tester::SelectErasurePool::configureServices( } ceph::io_sequence::tester::TestObject::TestObject( - const std::string oid, librados::Rados& rados, + const std::string primary_oid, const std::string secondary_oid, librados::Rados& rados, boost::asio::io_context& asio, SelectBlockSize& sbs, SelectErasurePool& spo, SelectObjectSize& sos, SelectNumThreads& snt, SelectSeqRange& ssr, ceph::util::random_number_generator& rng, ceph::mutex& lock, @@ -1021,7 +1028,7 @@ ceph::io_sequence::tester::TestObject::TestObject( testrecovery(testrecovery), checkconsistency(checkconsistency) { if (dryrun) { exerciser_model = std::make_unique( - oid, sbs.select(), rng()); + primary_oid, secondary_oid, sbs.select(), rng()); } else { const std::string pool = spo.select(); if (!dryrun) { @@ -1045,10 +1052,18 @@ ceph::io_sequence::tester::TestObject::TestObject( if (!spo.get_allow_pool_autoscaling() && !spo.get_allow_pool_balancer() && !spo.get_allow_pool_deep_scrubbing() && !spo.get_allow_pool_scrubbing()) { - ceph::messaging::osd::OSDMapRequest osdMapRequest{pool, oid, ""}; - int rc = send_mon_command(osdMapRequest, rados, "OSDMapRequest", inbl, - &outbl, formatter.get()); - ceph_assert(rc == 0); + { + ceph::messaging::osd::OSDMapRequest osdMapRequest{pool, primary_oid, ""}; + int rc = send_mon_command(osdMapRequest, rados, "OSDMapRequest", inbl, + &outbl, formatter.get()); + ceph_assert(rc == 0); + } + { + ceph::messaging::osd::OSDMapRequest osdMapRequest{pool, secondary_oid, ""}; + int rc = send_mon_command(osdMapRequest, rados, "OSDMapRequest", inbl, + &outbl, formatter.get()); + ceph_assert(rc == 0); + } JSONParser p; bool success = p.parse(outbl.c_str(), outbl.length()); @@ -1060,7 +1075,7 @@ ceph::io_sequence::tester::TestObject::TestObject( } exerciser_model = std::make_unique( - rados, asio, pool, oid, cached_shard_order, sbs.select(), rng(), + rados, asio, pool, primary_oid, secondary_cached_shard_order, sbs.select(), rng(), threads, lock, cond, spo.is_replicated_pool(), spo.get_allow_pool_ec_optimizations()); dout(0) << "= " << oid << " pool=" << pool << " threads=" << threads @@ -1082,7 +1097,7 @@ ceph::io_sequence::tester::TestObject::TestObject( op = seq->next(); done = false; - dout(0) << "== " << exerciser_model->get_oid() << " " << curseq << " " + dout(0) << "== " << exerciser_model->get_primary_oid() << " " << curseq << " " << seq->get_name_with_seqseed() << " ==" << dendl; } @@ -1093,11 +1108,11 @@ bool ceph::io_sequence::tester::TestObject::readyForIo() { bool ceph::io_sequence::tester::TestObject::next() { if (!done) { if (verbose) { - dout(0) << exerciser_model->get_oid() << " Step " << seq->get_step() + dout(0) << exerciser_model->get_primary_oid() << " Step " << seq->get_step() << ": " << op->to_string(exerciser_model->get_block_size()) << dendl; } else { - dout(5) << exerciser_model->get_oid() << " Step " << seq->get_step() + dout(5) << exerciser_model->get_primary_oid() << " Step " << seq->get_step() << ": " << op->to_string(exerciser_model->get_block_size()) << dendl; } @@ -1106,7 +1121,7 @@ bool ceph::io_sequence::tester::TestObject::next() { curseq = seq->getNextSupportedSequenceId(); if (curseq >= seq_range.second) { done = true; - dout(0) << exerciser_model->get_oid() + dout(0) << exerciser_model->get_primary_oid() << " Number of IOs = " << exerciser_model->get_num_io() << dendl; } else { @@ -1119,7 +1134,7 @@ bool ceph::io_sequence::tester::TestObject::next() { curseq, obj_size_range, seqseed.value_or(rng()), checkconsistency); } - dout(0) << "== " << exerciser_model->get_oid() << " " << curseq << " " + dout(0) << "== " << exerciser_model->get_primary_oid() << " " << curseq << " " << seq->get_name_with_seqseed() << " ==" << dendl; op = seq->next(); } @@ -1168,8 +1183,9 @@ ceph::io_sequence::tester::TestRunner::TestRunner( if (vm.contains("seqseed")) { seqseed = vm["seqseed"].as(); } - num_objects = vm["parallel"].as(); - object_name = vm["object"].as(); + num_object_pairs = vm["parallel"].as(); + primary_object_name = vm["object"].as(); + secondary_object_name = vm["object_copy"].as(); interactive = vm.contains("interactive"); testrecovery = vm.contains("testrecovery"); checkconsistency = vm.contains("checkconsistency"); @@ -1320,17 +1336,25 @@ bool ceph::io_sequence::tester::TestRunner::run_interactive_test() { if (dryrun) { model = std::make_unique( - object_name, sbs.select(), rng()); + primary_object_name, secondary_object_name, sbs.select(), rng()); } else { const std::string pool = spo.select(); bufferlist inbl, outbl; auto formatter = std::make_unique(false); - ceph::messaging::osd::OSDMapRequest osd_map_request{pool, object_name, ""}; + { + ceph::messaging::osd::OSDMapRequest osd_map_request{pool, primary_object_name, ""}; int rc = send_mon_command(osd_map_request, rados, "OSDMapRequest", inbl, &outbl, formatter.get()); ceph_assert(rc == 0); + } + { + ceph::messaging::osd::OSDMapRequest osdMapRequest{pool, secondary_object_name, ""}; + int rc = send_mon_command(osdMapRequest, rados, "OSDMapRequest", inbl, + &outbl, formatter.get()); + ceph_assert(rc == 0); + } JSONParser p; bool success = p.parse(outbl.c_str(), outbl.length()); @@ -1340,7 +1364,7 @@ bool ceph::io_sequence::tester::TestRunner::run_interactive_test() { osd_map_reply.decode_json(&p); model = std::make_unique( - rados, asio, pool, object_name, osd_map_reply.acting, sbs.select(), rng(), + rados, asio, pool, primary_object_name, secondary_object_name, osd_map_reply.acting, sbs.select(), rng(), 1, // 1 thread lock, cond, spo.is_replicated_pool(), spo.get_allow_pool_ec_optimizations()); @@ -1354,6 +1378,10 @@ bool ceph::io_sequence::tester::TestRunner::run_interactive_test() { uint64_t duration = get_numeric_token(); dout(0) << "Sleep " << duration << dendl; sleep(duration); + } else if (op == "swap") { + ioop = ceph::io_exerciser::SwapOp::generate(); + } else if (op == "copy") { + ioop = ceph::io_exerciser::CopyOp::generate(); } else if (op == "create") { ioop = ceph::io_exerciser::CreateOp::generate(get_numeric_token()); } else if (op == "remove" || op == "delete") { @@ -1463,7 +1491,7 @@ bool ceph::io_sequence::tester::TestRunner::run_interactive_test() { << dendl; } if (ioop) { - dout(0) << ioop->to_string(model->get_block_size()) << dendl; + dout(0) << model->get_primary_oid() << " " << ioop->to_string(model->get_block_size()) << dendl; model->applyIoOp(*ioop); done = ioop->getOpType() == ceph::io_exerciser::OpType::Done; if (!done) { @@ -1481,17 +1509,20 @@ bool ceph::io_sequence::tester::TestRunner::run_automated_test() { std::vector> test_objects; - for (int obj = 0; obj < num_objects; obj++) { - std::string name; + for (int obj = 0; obj < num_object_pairs; obj++) { + std::string primary_name; + std::string secondary_name; if (obj == 0) { - name = object_name; + primary_name = primary_object_name; + secondary_name = secondary_object_name; } else { - name = object_name + std::to_string(obj); + primary_name = primary_object_name + std::to_string(obj); + secondary_name = secondary_object_name + std::to_string(obj); } try { test_objects.push_back( std::make_shared( - name, rados, asio, sbs, spo, sos, snt, ssr, rng, lock, cond, + primary_name, secondary_name, rados, asio, sbs, spo, sos, snt, ssr, rng, lock, cond, dryrun, verbose, seqseed, testrecovery, checkconsistency)); } catch (const std::runtime_error &e) { diff --git a/src/test/osd/ceph_test_rados_io_sequence/ceph_test_rados_io_sequence.h b/src/test/osd/ceph_test_rados_io_sequence/ceph_test_rados_io_sequence.h index a874cfa924a6..3d8f5e71e184 100644 --- a/src/test/osd/ceph_test_rados_io_sequence/ceph_test_rados_io_sequence.h +++ b/src/test/osd/ceph_test_rados_io_sequence/ceph_test_rados_io_sequence.h @@ -444,7 +444,8 @@ class SelectErasurePool : public ProgramOptionReader { class TestObject { public: - TestObject(const std::string oid, + TestObject(const std::string primary_oid, + const std::string secondary_oid, librados::Rados& rados, boost::asio::io_context& asio, ceph::io_sequence::tester::SelectBlockSize& sbs, @@ -531,8 +532,9 @@ class TestRunner { bool show_sequence; bool show_help; - int num_objects; - std::string object_name; + int num_object_pairs; + std::string primary_object_name; + std::string secondary_object_name; std::string line; ceph::split split = ceph::split("");