case Sequence::SEQUENCE_SEQ13:
[[fallthrough]];
case Sequence::SEQUENCE_SEQ14:
+ [[fallthrough]];
+ case Sequence::SEQUENCE_SEQ15:
+ [[fallthrough]];
+ case Sequence::SEQUENCE_SEQ16:
return std::make_unique<ReadInjectSequence>(obj_size_range, seed,
sequence, km, mappinglayers, check_consistency);
case Sequence::SEQUENCE_SEQ10:
using TripleFailedWriteOp = ceph::io_exerciser::TripleFailedWriteOp;
using SingleAppendOp = ceph::io_exerciser::SingleAppendOp;
using TruncateOp = ceph::io_exerciser::TruncateOp;
+using SingleTruncateWriteOp = ceph::io_exerciser::SingleTruncateWriteOp;
+using DoubleTruncateWriteOp = ceph::io_exerciser::DoubleTruncateWriteOp;
+using TripleTruncateWriteOp = ceph::io_exerciser::TripleTruncateWriteOp;
namespace {
std::string value_to_string(uint64_t v) {
return "Truncate (size=" + value_to_string(size * block_size) + ")";
}
+template <OpType opType, int N>
+ceph::io_exerciser::TruncateWriteOp<opType, N>::TruncateWriteOp(
+ uint64_t size, std::array<uint64_t, N>&& offset,
+ std::array<uint64_t, N>&& length)
+ : TestOp<opType>(), size(size), offset(offset), length(length) {}
+
+template <OpType opType, int N>
+std::string ceph::io_exerciser::TruncateWriteOp<opType, N>::to_string(
+ uint64_t block_size) const {
+ std::string result = "TruncateWrite (size=" + value_to_string(size * block_size);
+ for (int i = 0; i < N; i++) {
+ result += ", offset" + (N > 1 ? std::to_string(i + 1) : "") + "=" +
+ value_to_string(offset[i] * block_size) + ", length" +
+ (N > 1 ? std::to_string(i + 1) : "") + "=" +
+ value_to_string(length[i] * block_size);
+ }
+ result += ")";
+ return result;
+}
+
+SingleTruncateWriteOp::SingleTruncateWriteOp(uint64_t size, uint64_t offset,
+ uint64_t length)
+ : TruncateWriteOp<OpType::TruncateWrite, 1>(size, {offset}, {length}) {}
+
+std::unique_ptr<SingleTruncateWriteOp> SingleTruncateWriteOp::generate(
+ uint64_t size, uint64_t offset, uint64_t length) {
+ return std::make_unique<SingleTruncateWriteOp>(size, offset, length);
+}
+
+DoubleTruncateWriteOp::DoubleTruncateWriteOp(uint64_t size, uint64_t offset1,
+ uint64_t length1, uint64_t offset2,
+ uint64_t length2)
+ : TruncateWriteOp<OpType::TruncateWrite2, 2>(size, {offset1, offset2},
+ {length1, length2}) {}
+
+std::unique_ptr<DoubleTruncateWriteOp> DoubleTruncateWriteOp::generate(
+ uint64_t size, uint64_t offset1, uint64_t length1, uint64_t offset2,
+ uint64_t length2) {
+ return std::make_unique<DoubleTruncateWriteOp>(size, offset1, length1,
+ offset2, length2);
+}
+
+TripleTruncateWriteOp::TripleTruncateWriteOp(uint64_t size, uint64_t offset1,
+ uint64_t length1, uint64_t offset2,
+ uint64_t length2, uint64_t offset3,
+ uint64_t length3)
+ : TruncateWriteOp<OpType::TruncateWrite3, 3>(
+ size, {offset1, offset2, offset3}, {length1, length2, length3}) {}
+
+std::unique_ptr<TripleTruncateWriteOp> TripleTruncateWriteOp::generate(
+ uint64_t size, uint64_t offset1, uint64_t length1, uint64_t offset2,
+ uint64_t length2, uint64_t offset3, uint64_t length3) {
+ return std::make_unique<TripleTruncateWriteOp>(size, offset1, length1,
+ offset2, length2, offset3,
+ length3);
+}
+
+// Explicit template instantiations
+template class ceph::io_exerciser::TruncateWriteOp<OpType::TruncateWrite, 1>;
+template class ceph::io_exerciser::TruncateWriteOp<OpType::TruncateWrite2, 2>;
+template class ceph::io_exerciser::TruncateWriteOp<OpType::TruncateWrite3, 3>;
+
SingleFailedWriteOp::SingleFailedWriteOp(uint64_t offset, uint64_t length)
: ReadWriteOp<OpType::FailedWrite, 1>({offset}, {length}) {}
uint64_t size;
};
+template <OpType opType, int numIOs>
+class TruncateWriteOp : public TestOp<opType> {
+ public:
+ uint64_t size;
+ std::array<uint64_t, numIOs> offset;
+ std::array<uint64_t, numIOs> length;
+
+ protected:
+ TruncateWriteOp(uint64_t size,
+ std::array<uint64_t, numIOs>&& offset,
+ std::array<uint64_t, numIOs>&& length);
+ std::string to_string(uint64_t block_size) const override;
+};
+
+class SingleTruncateWriteOp : public TruncateWriteOp<OpType::TruncateWrite, 1> {
+ public:
+ SingleTruncateWriteOp(uint64_t size, uint64_t offset, uint64_t length);
+ static std::unique_ptr<SingleTruncateWriteOp> generate(uint64_t size,
+ uint64_t offset,
+ uint64_t length);
+};
+
+class DoubleTruncateWriteOp : public TruncateWriteOp<OpType::TruncateWrite2, 2> {
+ public:
+ DoubleTruncateWriteOp(uint64_t size, uint64_t offset1, uint64_t length1,
+ uint64_t offset2, uint64_t length2);
+ static std::unique_ptr<DoubleTruncateWriteOp> generate(uint64_t size,
+ uint64_t offset1,
+ uint64_t length1,
+ uint64_t offset2,
+ uint64_t length2);
+};
+
+class TripleTruncateWriteOp : public TruncateWriteOp<OpType::TruncateWrite3, 3> {
+ public:
+ TripleTruncateWriteOp(uint64_t size, uint64_t offset1, uint64_t length1,
+ uint64_t offset2, uint64_t length2, uint64_t offset3,
+ uint64_t length3);
+ static std::unique_ptr<TripleTruncateWriteOp> generate(
+ uint64_t size, uint64_t offset1, uint64_t length1, uint64_t offset2,
+ uint64_t length2, uint64_t offset3, uint64_t length3);
+};
+
class SingleFailedWriteOp : public ReadWriteOp<OpType::FailedWrite, 1> {
public:
SingleFailedWriteOp(uint64_t offset, uint64_t length);
case Sequence::SEQUENCE_SEQ15:
os << "SEQUENCE_SEQ15";
break;
+ case Sequence::SEQUENCE_SEQ16:
+ os << "SEQUENCE_SEQ16";
+ break;
case Sequence::SEQUENCE_END:
os << "SEQUENCE_END";
break;
return std::make_unique<Seq14>(obj_size_range, seed, check_consistency);
case Sequence::SEQUENCE_SEQ15:
return std::make_unique<Seq15>(obj_size_range, seed, check_consistency);
+ case Sequence::SEQUENCE_SEQ16:
+ return std::make_unique<Seq16>(obj_size_range, seed, check_consistency);
default:
break;
}
[[fallthrough]];
default:
done = true;
+ remove = true;
break;
}
return r;
}
+
+
+// Seq16: Multiple TruncateWrite operations with Consistency checks
+// Each operation performs:
+// 1. Truncate to a random size between 0 and current object size
+// 2. Write a single block (size 1) at a random offset within the object
+// 3. Consistency check
+ceph::io_exerciser::Seq16::Seq16(std::pair<int, int> obj_size_range, int seed, bool check_consistency)
+ : IoSequence(obj_size_range, seed, check_consistency),
+ truncate_size(0),
+ write_offset(0),
+ operation_count(0),
+ total_operations(20),
+ truncate_write_done(false),
+ consistency_done(false) {
+ select_random_object_size();
+ setup_next_operation();
+}
+
+Sequence ceph::io_exerciser::Seq16::get_id() const {
+ return Sequence::SEQUENCE_SEQ16;
+}
+
+std::string ceph::io_exerciser::Seq16::get_name() const {
+ return "Multiple TruncateWrite operations with varying sizes and Consistency checks";
+}
+
+void ceph::io_exerciser::Seq16::setup_next_operation() {
+ // Generate random truncate size between 0 and current object size
+ truncate_size = rng(obj_size + 1);
+
+ // Generate random write offset within object size
+ // Ensure write stays within bounds (write_offset < obj_size for size 1 write)
+ write_offset = obj_size > 0 ? rng(obj_size) : 0;
+
+ truncate_write_done = false;
+ consistency_done = false;
+}
+
+std::unique_ptr<ceph::io_exerciser::IoOp> ceph::io_exerciser::Seq16::_next() {
+ // Check if we're done with all operations
+ if (operation_count >= total_operations) {
+ done = true;
+ remove = true;
+ barrier = true;
+ return BarrierOp::generate();
+ }
+
+ // Perform TruncateWrite operation with a single write of size 1
+ if (!truncate_write_done) {
+ truncate_write_done = true;
+ barrier = true;
+ return SingleTruncateWriteOp::generate(truncate_size, write_offset, 1);
+ }
+
+ // Perform Consistency check
+ if (!consistency_done) {
+ consistency_done = true;
+ barrier = true;
+
+ // Move to next operation
+ operation_count++;
+ if (operation_count < total_operations) {
+ setup_next_operation();
+ }
+
+ return ConsistencyOp::generate();
+ }
+
+ // Should not reach here
+ barrier = true;
+ return BarrierOp::generate();
+}
SEQUENCE_SEQ13,
SEQUENCE_SEQ14,
SEQUENCE_SEQ15,
+ SEQUENCE_SEQ16,
SEQUENCE_END,
SEQUENCE_BEGIN = SEQUENCE_SEQ0
std::string get_name() const override;
std::unique_ptr<IoOp> _next() override;
};
+
+class Seq16 : public IoSequence {
+ private:
+ uint64_t truncate_size;
+ uint64_t write_offset;
+ int operation_count;
+ int total_operations;
+ bool truncate_write_done;
+ bool consistency_done;
+
+ void setup_next_operation();
+
+ public:
+ Seq16(std::pair<int, int> obj_size_range, int seed, bool check_consistency);
+
+ Sequence get_id() const override;
+ std::string get_name() const override;
+ std::unique_ptr<IoOp> _next() override;
+};
} // namespace io_exerciser
} // namespace ceph
verify_write_and_record_and_generate_seed(appendOp);
} break;
+ case OpType::TruncateWrite: {
+ ceph_assert(primary_created);
+ ceph_assert(reads.empty());
+ ceph_assert(writes.empty());
+ SingleTruncateWriteOp& truncWriteOp = static_cast<SingleTruncateWriteOp&>(op);
+ auto new_size = truncWriteOp.size;
+ auto old_size = primary_contents.size();
+ bool expand = new_size > old_size;
+ primary_contents.resize(new_size);
+ if (expand) {
+ std::generate(std::execution::seq, primary_contents.begin() + old_size,
+ primary_contents.end(), generate_random);
+ }
+ // Now apply the write operations
+ for (int i = 0; i < 1; i++) {
+ ceph_assert(!reads.intersects(truncWriteOp.offset[i], truncWriteOp.length[i]));
+ ceph_assert(!writes.intersects(truncWriteOp.offset[i], truncWriteOp.length[i]));
+ writes.union_insert(truncWriteOp.offset[i], truncWriteOp.length[i]);
+ if (truncWriteOp.offset[i] + truncWriteOp.length[i] > primary_contents.size()) {
+ primary_contents.resize(truncWriteOp.offset[i] + truncWriteOp.length[i]);
+ }
+ std::generate(std::execution::seq,
+ std::next(primary_contents.begin(), truncWriteOp.offset[i]),
+ std::next(primary_contents.begin(),
+ truncWriteOp.offset[i] + truncWriteOp.length[i]),
+ generate_random);
+ }
+ num_io++;
+ } break;
+ case OpType::TruncateWrite2: {
+ ceph_assert(primary_created);
+ ceph_assert(reads.empty());
+ ceph_assert(writes.empty());
+ DoubleTruncateWriteOp& truncWriteOp = static_cast<DoubleTruncateWriteOp&>(op);
+ auto new_size = truncWriteOp.size;
+ auto old_size = primary_contents.size();
+ bool expand = new_size > old_size;
+ primary_contents.resize(new_size);
+ if (expand) {
+ std::generate(std::execution::seq, primary_contents.begin() + old_size,
+ primary_contents.end(), generate_random);
+ }
+ // Now apply the write operations
+ for (int i = 0; i < 2; i++) {
+ ceph_assert(!reads.intersects(truncWriteOp.offset[i], truncWriteOp.length[i]));
+ ceph_assert(!writes.intersects(truncWriteOp.offset[i], truncWriteOp.length[i]));
+ writes.union_insert(truncWriteOp.offset[i], truncWriteOp.length[i]);
+ if (truncWriteOp.offset[i] + truncWriteOp.length[i] > primary_contents.size()) {
+ primary_contents.resize(truncWriteOp.offset[i] + truncWriteOp.length[i]);
+ }
+ std::generate(std::execution::seq,
+ std::next(primary_contents.begin(), truncWriteOp.offset[i]),
+ std::next(primary_contents.begin(),
+ truncWriteOp.offset[i] + truncWriteOp.length[i]),
+ generate_random);
+ }
+ num_io++;
+ } break;
+ case OpType::TruncateWrite3: {
+ ceph_assert(primary_created);
+ ceph_assert(reads.empty());
+ ceph_assert(writes.empty());
+ TripleTruncateWriteOp& truncWriteOp = static_cast<TripleTruncateWriteOp&>(op);
+ auto new_size = truncWriteOp.size;
+ auto old_size = primary_contents.size();
+ bool expand = new_size > old_size;
+ primary_contents.resize(new_size);
+ if (expand) {
+ std::generate(std::execution::seq, primary_contents.begin() + old_size,
+ primary_contents.end(), generate_random);
+ }
+ // Now apply the write operations
+ for (int i = 0; i < 3; i++) {
+ ceph_assert(!reads.intersects(truncWriteOp.offset[i], truncWriteOp.length[i]));
+ ceph_assert(!writes.intersects(truncWriteOp.offset[i], truncWriteOp.length[i]));
+ writes.union_insert(truncWriteOp.offset[i], truncWriteOp.length[i]);
+ if (truncWriteOp.offset[i] + truncWriteOp.length[i] > primary_contents.size()) {
+ primary_contents.resize(truncWriteOp.offset[i] + truncWriteOp.length[i]);
+ }
+ std::generate(std::execution::seq,
+ std::next(primary_contents.begin(), truncWriteOp.offset[i]),
+ std::next(primary_contents.begin(),
+ truncWriteOp.offset[i] + truncWriteOp.length[i]),
+ generate_random);
+ }
+ num_io++;
+ } break;
+
case OpType::FailedWrite: {
ceph_assert(primary_created);
SingleWriteOp& writeOp = static_cast<SingleWriteOp&>(op);
Write3, // Three writes in a single op
Append, // Append
Truncate, // Truncate
+ TruncateWrite, // Truncate + single write in a single op
+ TruncateWrite2, // Truncate + two writes in a single op
+ TruncateWrite3, // Truncate + three writes in a single op
FailedWrite, // A write which should fail
FailedWrite2, // Two writes in one op which should fail
FailedWrite3, // Three writes in one op which should fail
return fmt::format_to(ctx.out(), "Append");
case ceph::io_exerciser::OpType::Truncate:
return fmt::format_to(ctx.out(), "Truncate");
+ case ceph::io_exerciser::OpType::TruncateWrite:
+ return fmt::format_to(ctx.out(), "TruncateWrite");
+ case ceph::io_exerciser::OpType::TruncateWrite2:
+ return fmt::format_to(ctx.out(), "TruncateWrite2");
+ case ceph::io_exerciser::OpType::TruncateWrite3:
+ return fmt::format_to(ctx.out(), "TruncateWrite3");
case ceph::io_exerciser::OpType::FailedWrite:
return fmt::format_to(ctx.out(), "FailedWrite");
case ceph::io_exerciser::OpType::FailedWrite2:
case OpType::FailedWrite3:
applyReadWriteOp(op);
break;
+ case OpType::TruncateWrite:
+ [[fallthrough]];
+ case OpType::TruncateWrite2:
+ [[fallthrough]];
+ case OpType::TruncateWrite3:
+ applyTruncateWriteOp(op);
+ break;
case OpType::InjectReadError:
[[fallthrough]];
case OpType::InjectWriteError:
}
}
+void RadosIo::applyTruncateWriteOp(IoOp& op) {
+ auto applyTruncateWriteOp = [this]<OpType opType, int N>(
+ TruncateWriteOp<opType, N> truncWriteOp) {
+ auto op_info =
+ std::make_shared<AsyncOpInfo<N>>(truncWriteOp.offset, truncWriteOp.length);
+ librados::ObjectWriteOperation wop;
+
+ // First, add the truncate operation
+ wop.truncate(truncWriteOp.size * block_size);
+
+ // Then, add the write operations
+ for (int i = 0; i < N; i++) {
+ op_info->bufferlist[i] =
+ db->generate_data(truncWriteOp.offset[i], truncWriteOp.length[i]);
+ wop.write(truncWriteOp.offset[i] * block_size,
+ op_info->bufferlist[i]);
+ }
+
+ 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, primary_oid,
+ std::move(wop), 0, nullptr, write_cb);
+ num_io++;
+ };
+
+ switch (op.getOpType()) {
+ case OpType::TruncateWrite: {
+ start_io();
+ SingleTruncateWriteOp& truncWriteOp = static_cast<SingleTruncateWriteOp&>(op);
+ applyTruncateWriteOp(truncWriteOp);
+ break;
+ }
+ case OpType::TruncateWrite2: {
+ start_io();
+ DoubleTruncateWriteOp& truncWriteOp = static_cast<DoubleTruncateWriteOp&>(op);
+ applyTruncateWriteOp(truncWriteOp);
+ break;
+ }
+ case OpType::TruncateWrite3: {
+ start_io();
+ TripleTruncateWriteOp& truncWriteOp = static_cast<TripleTruncateWriteOp&>(op);
+ applyTruncateWriteOp(truncWriteOp);
+ break;
+ }
+ default:
+ ceph_abort_msg(
+ fmt::format("Unsupported TruncateWrite operation ({})", op.getOpType()));
+ break;
+ }
+}
+
void RadosIo::applyInjectOp(IoOp& op) {
bufferlist osdmap_outbl, inject_outbl;
auto formatter = std::make_unique<JSONFormatter>(false);
private:
void applyReadWriteOp(IoOp& op);
+ void applyTruncateWriteOp(IoOp& op);
void applyInjectOp(IoOp& op);
};
} // namespace io_exerciser
using TripleWriteOp = ceph::io_exerciser::TripleWriteOp;
using SingleAppendOp = ceph::io_exerciser::SingleAppendOp;
using TruncateOp = ceph::io_exerciser::TruncateOp;
+using SingleTruncateWriteOp = ceph::io_exerciser::SingleTruncateWriteOp;
+using DoubleTruncateWriteOp = ceph::io_exerciser::DoubleTruncateWriteOp;
+using TripleTruncateWriteOp = ceph::io_exerciser::TripleTruncateWriteOp;
using SingleFailedWriteOp = ceph::io_exerciser::SingleFailedWriteOp;
using DoubleFailedWriteOp = ceph::io_exerciser::DoubleFailedWriteOp;
using TripleFailedWriteOp = ceph::io_exerciser::TripleFailedWriteOp;
ioop = SingleAppendOp::generate(length);
} else if (op == "truncate") {
ioop = TruncateOp::generate(get_numeric_token());
+ } else if (op == "truncatewrite") {
+ uint64_t size = get_numeric_token();
+ uint64_t offset = get_numeric_token();
+ uint64_t length = get_numeric_token();
+ ioop = SingleTruncateWriteOp::generate(size, offset, length);
+ } else if (op == "truncatewrite2") {
+ uint64_t size = get_numeric_token();
+ uint64_t offset1 = get_numeric_token();
+ uint64_t length1 = get_numeric_token();
+ uint64_t offset2 = get_numeric_token();
+ uint64_t length2 = get_numeric_token();
+ ioop = DoubleTruncateWriteOp::generate(size, offset1, length1, offset2, length2);
+ } else if (op == "truncatewrite3") {
+ uint64_t size = get_numeric_token();
+ uint64_t offset1 = get_numeric_token();
+ uint64_t length1 = get_numeric_token();
+ uint64_t offset2 = get_numeric_token();
+ uint64_t length2 = get_numeric_token();
+ uint64_t offset3 = get_numeric_token();
+ uint64_t length3 = get_numeric_token();
+ ioop = TripleTruncateWriteOp::generate(size, offset1, length1, offset2, length2,
+ offset3, length3);
} else if (op == "failedwrite") {
uint64_t offset = get_numeric_token();
uint64_t length = get_numeric_token();