]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
test: Add TruncateWrite ops to IO Sequencer and Sequences using them
authorMatty Williams <Matty.Williams@ibm.com>
Thu, 11 Jun 2026 10:59:48 +0000 (11:59 +0100)
committerAlex Ainscow <aainscow@uk.ibm.com>
Fri, 24 Jul 2026 09:08:18 +0000 (10:08 +0100)
current io exerciser sequences can model standalone truncates and writes,
but cannot generate a single transaction that truncates an object and
then writes sparse regions. Add SingleTruncateWriteOp, DoubleTruncateWriteOp,
and TripleTruncateWriteOp, wire them through IoSequence, ObjectModel, and
RadosIo, and extend EC sequences/tests so truncate+write transactions can
be reproduced and validated.

Signed-off-by: Matty Williams <Matty.Williams@ibm.com>
Assisted-by: IBM Bob:Claude/GPT
src/common/io_exerciser/EcIoSequence.cc
src/common/io_exerciser/IoOp.cc
src/common/io_exerciser/IoOp.h
src/common/io_exerciser/IoSequence.cc
src/common/io_exerciser/IoSequence.h
src/common/io_exerciser/ObjectModel.cc
src/common/io_exerciser/OpType.h
src/common/io_exerciser/RadosIo.cc
src/common/io_exerciser/RadosIo.h
src/test/osd/ceph_test_rados_io_sequence/ceph_test_rados_io_sequence.cc

index 2c61a7b381c154b3750bc3e1357d32ad7184b0a9..a15287515662586210fb30fc3c6ba19001f398b1 100644 (file)
@@ -45,6 +45,10 @@ std::unique_ptr<IoSequence> EcIoSequence::generate_sequence(
     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:
index 11c1b3ca4c3debd3e38502570c8d410aecda8e9d..2d75ab1ed4dd041d6d5806a376da75b9e5035e49 100644 (file)
@@ -24,6 +24,9 @@ using DoubleFailedWriteOp = ceph::io_exerciser::DoubleFailedWriteOp;
 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) {
@@ -287,6 +290,68 @@ std::string TruncateOp::to_string(uint64_t block_size) const {
   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}) {}
 
index 603cbcb7e2ddd7c8ee8c8b8124949df303bd57ac..0c69fc04d17c3b69eb4498ae39c06705e9a19850 100644 (file)
@@ -175,6 +175,49 @@ class TruncateOp : public TestOp<OpType::Truncate> {
   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);
index 2634b9a38a7667689480292221b320cfb7da7b74..1b1bebfabb1aa437d9773c5715ecc0d462d360d7 100644 (file)
@@ -59,6 +59,9 @@ std::ostream& ceph::io_exerciser::operator<<(std::ostream& os,
     case Sequence::SEQUENCE_SEQ15:
       os << "SEQUENCE_SEQ15";
       break;
+    case Sequence::SEQUENCE_SEQ16:
+      os << "SEQUENCE_SEQ16";
+      break;
     case Sequence::SEQUENCE_END:
       os << "SEQUENCE_END";
       break;
@@ -108,6 +111,8 @@ std::unique_ptr<IoSequence> IoSequence::generate_sequence(
       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;
   }
@@ -855,8 +860,82 @@ std::unique_ptr<ceph::io_exerciser::IoOp> ceph::io_exerciser::Seq15::_next() {
       [[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();
+}
index 13b7a54e7ffcfce2282fce3f05907b719f41b139..3c17bbeba958f790281b087bcdb42f397a1e2baf 100644 (file)
@@ -48,6 +48,7 @@ enum class Sequence {
   SEQUENCE_SEQ13,
   SEQUENCE_SEQ14,
   SEQUENCE_SEQ15,
+  SEQUENCE_SEQ16,
 
   SEQUENCE_END,
   SEQUENCE_BEGIN = SEQUENCE_SEQ0
@@ -337,5 +338,24 @@ class Seq15 : public IoSequence {
   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
index cd9f741065a73f448b69be0b2ee4e4955215dab0..bc20d1a1b1ee3c2dd9488d1d518e93bc8c1ca142 100644 (file)
@@ -212,6 +212,94 @@ void ObjectModel::applyIoOp(IoOp& op) {
       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);
index af939614570ea1dc174698c7e4f9d2520a33c654..9e2ba97fa635586a44de031256c660c066a10dc5 100644 (file)
@@ -27,6 +27,9 @@ enum class OpType {
   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
@@ -84,6 +87,12 @@ struct fmt::formatter<ceph::io_exerciser::OpType> {
         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:
index f98348ce3e8b7d7a5ad1cc5b5cb4d8cbb9136885..a8ea9b05a539e1356356bb9caefa719d9fc66ede 100644 (file)
@@ -236,6 +236,13 @@ void RadosIo::applyIoOp(IoOp& op) {
     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:
@@ -408,6 +415,59 @@ void RadosIo::applyReadWriteOp(IoOp& op) {
   }
 }
 
+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);
index d79847dfa86b9ada754cff5ba705eab2f5041656..248e13b81ef2606cc4d5580fbc4a85c5445b35c3 100644 (file)
@@ -81,6 +81,7 @@ class RadosIo : public Model {
 
  private:
   void applyReadWriteOp(IoOp& op);
+  void applyTruncateWriteOp(IoOp& op);
   void applyInjectOp(IoOp& op);
 };
 }  // namespace io_exerciser
index 0297312c65625f4703180b6c7abca90544add087..17d1a224f0e09d7d7d5b2418074cd48a23ff1f6c 100644 (file)
@@ -51,6 +51,9 @@ using DoubleWriteOp = ceph::io_exerciser::DoubleWriteOp;
 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;
@@ -1465,6 +1468,28 @@ bool ceph::io_sequence::tester::TestRunner::run_interactive_test() {
       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();