]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
test/osd: Added support for new plugins
authorJon Bailey <jonathan.bailey1@ibm.com>
Wed, 15 Jan 2025 13:12:40 +0000 (13:12 +0000)
committerJon Bailey <jonathan.bailey1@ibm.com>
Wed, 5 Mar 2025 16:15:01 +0000 (16:15 +0000)
Added new command line options for specifying plugins along with optional readers for them. Added arguments to JSON structures for some optional arguments to encode and decode more information about profiles. Added more choices for random selections of different plugin values and changed structure so profiles are created as part of a pool specifically, encapsulating them.

Signed-off-by: Jon Bailey <jonathan.bailey1@ibm.com>
src/common/io_exerciser/EcIoSequence.cc
src/common/io_exerciser/EcIoSequence.h
src/common/json/OSDStructures.cc
src/common/json/OSDStructures.h
src/test/osd/ceph_test_rados_io_sequence/ProgramOptionReader.h
src/test/osd/ceph_test_rados_io_sequence/ceph_test_rados_io_sequence.cc
src/test/osd/ceph_test_rados_io_sequence/ceph_test_rados_io_sequence.h

index 03732632c7b2c328ae404381c3230a10958134a8..c28008b5f05e36290977d9bcd9cfa2397f5dcac9 100644 (file)
@@ -1,5 +1,6 @@
 #include "EcIoSequence.h"
 
+#include <algorithm>
 #include <memory>
 
 using IoOp = ceph::io_exerciser::IoOp;
@@ -11,7 +12,9 @@ using ReadInjectSequence = ceph::io_exerciser::ReadInjectSequence;
 bool EcIoSequence::is_supported(Sequence sequence) const { return true; }
 
 std::unique_ptr<IoSequence> EcIoSequence::generate_sequence(
-    Sequence sequence, std::pair<int, int> obj_size_range, int k, int m,
+    Sequence sequence, std::pair<int, int> obj_size_range,
+    std::optional<std::pair<int, int>> km,
+    std::optional<std::pair<std::string_view, std::string_view>> mappingLayers,
     int seed) {
   switch (sequence) {
     case Sequence::SEQUENCE_SEQ0:
@@ -42,9 +45,9 @@ std::unique_ptr<IoSequence> EcIoSequence::generate_sequence(
       [[fallthrough]];
     case Sequence::SEQUENCE_SEQ14:
       return std::make_unique<ReadInjectSequence>(obj_size_range, seed,
-                                                  sequence, k, m);
+                                                  sequence, km, mappingLayers);
     case Sequence::SEQUENCE_SEQ10:
-      return std::make_unique<Seq10>(obj_size_range, seed, k, m);
+      return std::make_unique<Seq10>(obj_size_range, seed, km, mappingLayers);
     default:
       ceph_abort_msg("Unrecognised sequence");
   }
@@ -56,26 +59,99 @@ EcIoSequence::EcIoSequence(std::pair<int, int> obj_size_range, int seed)
       clear_inject(false),
       shard_to_inject(std::nullopt) {}
 
-void EcIoSequence::select_random_data_shard_to_inject_read_error(int k, int m) {
-  shard_to_inject = rng(k - 1);
+void EcIoSequence::select_random_data_shard_to_inject_read_error(
+    std::optional<std::pair<int, int>> km,
+    std::optional<std::pair<std::string_view, std::string_view>>
+        mappingLayers) {
+  if (km) {
+    shard_to_inject = rng(km->first - 1);
+  }
+  if (mappingLayers) {
+    int count = std::ranges::count(mappingLayers->first, 'D');
+    int dataShardPosition = rng(count - 1);
+    shard_to_inject = 0;
+    for (int i = 0; i < dataShardPosition; i++) {
+      shard_to_inject =
+          std::distance(std::find(mappingLayers->first.begin(),
+                                  mappingLayers->first.end(), *shard_to_inject),
+                        mappingLayers->first.begin());
+      ceph_assert(*shard_to_inject != std::string::npos);
+    }
+  }
   setup_inject = true;
 }
 
-void EcIoSequence::select_random_data_shard_to_inject_write_error(int k,
-                                                                  int m) {
+void EcIoSequence::select_random_data_shard_to_inject_write_error(
+    std::optional<std::pair<int, int>> km,
+    std::optional<std::pair<std::string_view, std::string_view>>
+        mappingLayers) {
   // Write errors do not support injecting to the primary OSD
-  shard_to_inject = rng(1, k - 1);
+  if (km) {
+    shard_to_inject = rng(1, km->first - 1);
+  }
+  if (mappingLayers) {
+    int count = std::ranges::count(mappingLayers->first, 'D');
+    if (mappingLayers->first[0] == 'D') {
+      count--;
+    }
+    int dataShardPosition = rng(1, count - 1);
+    shard_to_inject = 0;
+    for (int i = 1; i < dataShardPosition; i++) {
+      shard_to_inject =
+          std::distance(std::find(mappingLayers->first.begin(),
+                                  mappingLayers->first.end(), *shard_to_inject),
+                        mappingLayers->first.begin());
+      ceph_assert(*shard_to_inject != std::string::npos);
+    }
+  }
   setup_inject = true;
 }
 
-void EcIoSequence::select_random_shard_to_inject_read_error(int k, int m) {
-  shard_to_inject = rng(k + m - 1);
+void EcIoSequence::select_random_shard_to_inject_read_error(
+    std::optional<std::pair<int, int>> km,
+    std::optional<std::pair<std::string_view, std::string_view>>
+        mappingLayers) {
+  if (km) {
+    shard_to_inject = rng(km->first + km->second - 1);
+  }
+  if (mappingLayers) {
+    int count = std::ranges::count(mappingLayers->first, 'D');
+    int dataShardPosition = rng(count - 1);
+    shard_to_inject = 0;
+    for (int i = 0; i < dataShardPosition; i++) {
+      shard_to_inject =
+          std::distance(std::find(mappingLayers->first.begin(),
+                                  mappingLayers->first.end(), *shard_to_inject),
+                        mappingLayers->first.begin());
+      ceph_assert(*shard_to_inject != std::string::npos);
+    }
+  }
   setup_inject = true;
 }
 
-void EcIoSequence::select_random_shard_to_inject_write_error(int k, int m) {
+void EcIoSequence::select_random_shard_to_inject_write_error(
+    std::optional<std::pair<int, int>> km,
+    std::optional<std::pair<std::string_view, std::string_view>>
+        mappingLayers) {
   // Write errors do not support injecting to the primary OSD
-  shard_to_inject = rng(1, k + m - 1);
+  if (km) {
+    shard_to_inject = rng(1, km->first + km->second - 1);
+  }
+  if (mappingLayers) {
+    int count = std::ranges::count(mappingLayers->first, 'D');
+    if (mappingLayers->first[0] == 'D') {
+      count--;
+    }
+    int dataShardPosition = rng(count - 1);
+    shard_to_inject = 0;
+    for (int i = 0; i < dataShardPosition; i++) {
+      shard_to_inject =
+          std::distance(std::find(mappingLayers->first.begin(),
+                                  mappingLayers->first.end(), *shard_to_inject),
+                        mappingLayers->first.begin());
+      ceph_assert(*shard_to_inject != std::string::npos);
+    }
+  }
   setup_inject = true;
 }
 
@@ -92,10 +168,14 @@ void EcIoSequence::generate_random_write_inject_type() {
 }
 
 ceph::io_exerciser::ReadInjectSequence::ReadInjectSequence(
-    std::pair<int, int> obj_size_range, int seed, Sequence s, int k, int m)
+    std::pair<int, int> obj_size_range,
+    int seed,
+    Sequence s,
+    std::optional<std::pair<int, int>> km,
+    std::optional<std::pair<std::string_view, std::string_view>> mappingLayers)
     : EcIoSequence(obj_size_range, seed) {
   child_sequence = IoSequence::generate_sequence(s, obj_size_range, seed);
-  select_random_data_shard_to_inject_read_error(k, m);
+  select_random_data_shard_to_inject_read_error(km, mappingLayers);
   generate_random_read_inject_type();
 }
 
@@ -104,9 +184,13 @@ Sequence ceph::io_exerciser::ReadInjectSequence::get_id() const {
 }
 
 std::string ceph::io_exerciser::ReadInjectSequence::get_name() const {
+  std::string injected_shard = "UNKNOWN";
+  if (shard_to_inject) {
+    injected_shard = std::to_string(*shard_to_inject);
+  }
+
   return child_sequence->get_name() +
-         " running with read errors injected on shard " +
-         std::to_string(*shard_to_inject);
+         " running with read errors injected on shard " + injected_shard;
 }
 
 std::unique_ptr<IoOp> ReadInjectSequence::next() {
@@ -124,6 +208,7 @@ std::unique_ptr<IoOp> ReadInjectSequence::next() {
     case OpType::Remove:
       nextOp.swap(childOp);
       switch (inject_op_type) {
+        ceph_assert(shard_to_inject.has_value());
         case InjectOpType::ReadEIO:
           return ClearReadErrorInjectOp::generate(*shard_to_inject, 0);
         case InjectOpType::ReadMissingShard:
@@ -179,8 +264,10 @@ ceph::io_exerciser::ReadInjectSequence::_next() {
   return DoneOp::generate();
 }
 
-ceph::io_exerciser::Seq10::Seq10(std::pair<int, int> obj_size_range, int seed,
-                                 int k, int m)
+ceph::io_exerciser::Seq10::Seq10(
+    std::pair<int, int> obj_size_range, int seed,
+    std::optional<std::pair<int, int>> km,
+    std::optional<std::pair<std::string_view, std::string_view>> mappingLayers)
     : EcIoSequence(obj_size_range, seed),
       offset(0),
       length(1),
@@ -192,7 +279,7 @@ ceph::io_exerciser::Seq10::Seq10(std::pair<int, int> obj_size_range, int seed,
       test_all_sizes(
           false)  // Only test obj_size(rand()) due to time constraints
 {
-  select_random_shard_to_inject_write_error(k, m);
+  select_random_shard_to_inject_write_error(km, mappingLayers);
   // We will inject specifically as part of our sequence in this sequence
   setup_inject = false;
   if (!test_all_sizes) {
index 37283b3906bdae2d16f2e17366c46d7fbcaa27fe..33d46fbbe41ab77dc0b1d965deb5dbcf3181bcb6 100644 (file)
@@ -1,3 +1,5 @@
+#pragma once
+
 #include "IoSequence.h"
 
 namespace ceph {
@@ -6,7 +8,11 @@ class EcIoSequence : public IoSequence {
  public:
   virtual bool is_supported(Sequence sequence) const override;
   static std::unique_ptr<IoSequence> generate_sequence(
-      Sequence s, std::pair<int, int> obj_size_range, int k, int m, int seed);
+      Sequence s, std::pair<int, int> obj_size_range,
+      std::optional<std::pair<int, int>> km,
+      std::optional<std::pair<std::string_view, std::string_view>>
+          mappingLayers,
+      int seed);
 
  protected:
   bool setup_inject;
@@ -18,18 +24,33 @@ class EcIoSequence : public IoSequence {
 
   // Writes cannot be sent to injected on shard zero, so selections seperated
   // out
-  void select_random_data_shard_to_inject_read_error(int k, int m);
-  void select_random_data_shard_to_inject_write_error(int k, int m);
-  void select_random_shard_to_inject_read_error(int k, int m);
-  void select_random_shard_to_inject_write_error(int k, int m);
+  void select_random_data_shard_to_inject_read_error(
+      std::optional<std::pair<int, int>> km,
+      std::optional<std::pair<std::string_view, std::string_view>>
+          mappingLayers);
+  void select_random_data_shard_to_inject_write_error(
+      std::optional<std::pair<int, int>> km,
+      std::optional<std::pair<std::string_view, std::string_view>>
+          mappingLayers);
+  void select_random_shard_to_inject_read_error(
+      std::optional<std::pair<int, int>> km,
+      std::optional<std::pair<std::string_view, std::string_view>>
+          mappingLayers);
+  void select_random_shard_to_inject_write_error(
+      std::optional<std::pair<int, int>> km,
+      std::optional<std::pair<std::string_view, std::string_view>>
+          mappingLayers);
   void generate_random_read_inject_type();
   void generate_random_write_inject_type();
 };
 
 class ReadInjectSequence : public EcIoSequence {
  public:
-  ReadInjectSequence(std::pair<int, int> obj_size_range, int seed, Sequence s,
-                     int k, int m);
+  ReadInjectSequence(
+      std::pair<int, int> obj_size_range, int seed, Sequence s,
+      std::optional<std::pair<int, int>> km,
+      std::optional<std::pair<std::string_view, std::string_view>>
+          mappingLayers);
 
   Sequence get_id() const override;
   std::string get_name() const override;
@@ -43,7 +64,10 @@ class ReadInjectSequence : public EcIoSequence {
 
 class Seq10 : public EcIoSequence {
  public:
-  Seq10(std::pair<int, int> obj_size_range, int seed, int k, int m);
+  Seq10(std::pair<int, int> obj_size_range, int seed,
+        std::optional<std::pair<int, int>> km,
+        std::optional<std::pair<std::string_view, std::string_view>>
+            mappingLayers);
 
   Sequence get_id() const override;
   std::string get_name() const override;
index aaac5f6e16938ab98174141ea8dc94a34b7f26f8..f9e3d8d75b6bad45e5d75e1c067ea73feb787aec 100644 (file)
@@ -85,12 +85,17 @@ void OSDECProfileGetReply::dump(Formatter* f) const {
   encode_json("crush-osds-per-failure-domain", crush_osds_per_failure_domain,
               f);
   encode_json("crush-root", crush_root, f);
-  encode_json("jerasure-per-chunk-alignment", jerasure_per_chunk_alignment, f);
+  encode_json("plugin", plugin, f);
   encode_json("k", k, f);
   encode_json("m", m, f);
-  encode_json("plugin", plugin, f);
-  encode_json("technique", technique, f);
+  encode_json("l", l, f);
   encode_json("w", w, f);
+  encode_json("c", c, f);
+  encode_json("packetsize", packetsize, f);
+  encode_json("technique", technique, f);
+  encode_json("layers", layers, f);
+  encode_json("mapping", mapping, f);
+  encode_json("jerasure-per-chunk-alignment", jerasure_per_chunk_alignment, f);
 }
 
 void OSDECProfileGetReply::decode_json(JSONObj* obj) {
@@ -101,24 +106,31 @@ void OSDECProfileGetReply::decode_json(JSONObj* obj) {
   JSONDecoder::decode_json("crush-osds-per-failure-domain",
                            crush_osds_per_failure_domain, obj);
   JSONDecoder::decode_json("crush-root", crush_root, obj);
-  JSONDecoder::decode_json("jerasure-per-chunk-alignment",
-                           jerasure_per_chunk_alignment, obj);
+  JSONDecoder::decode_json("plugin", plugin, obj);
   JSONDecoder::decode_json("k", k, obj);
   JSONDecoder::decode_json("m", m, obj);
-  JSONDecoder::decode_json("plugin", plugin, obj);
-  JSONDecoder::decode_json("technique", technique, obj);
+  JSONDecoder::decode_json("l", l, obj);
   JSONDecoder::decode_json("w", w, obj);
+  JSONDecoder::decode_json("c", c, obj);
+  JSONDecoder::decode_json("packetsize", packetsize, obj);
+  JSONDecoder::decode_json("technique", technique, obj);
+  JSONDecoder::decode_json("layers", layers, obj);
+  JSONDecoder::decode_json("mapping", mapping, obj);
+  JSONDecoder::decode_json("jerasure-per-chunk-alignment",
+                           jerasure_per_chunk_alignment, obj);
 }
 
 void OSDECProfileSetRequest::dump(Formatter* f) const {
   encode_json("prefix", "osd erasure-code-profile set", f);
   encode_json("name", name, f);
   encode_json("profile", profile, f);
+  encode_json("force", force, f);
 }
 
 void OSDECProfileSetRequest::decode_json(JSONObj* obj) {
   JSONDecoder::decode_json("name", name, obj);
   JSONDecoder::decode_json("profile", profile, obj);
+  JSONDecoder::decode_json("force", force, obj);
 }
 
 void OSDECPoolCreateRequest::dump(Formatter* f) const {
index 3e4528a099fc0c347eb695df8b08c97441cdb360..4fc3e307e2aaf3c627531b045e77352184779bb9 100644 (file)
@@ -69,12 +69,17 @@ struct OSDECProfileGetReply {
   int crush_num_failure_domains;
   int crush_osds_per_failure_domain;
   std::string crush_root;
-  bool jerasure_per_chunk_alignment;
+  std::string plugin;
   int k;
   int m;
-  std::string plugin;
-  std::string technique;
-  std::string w;
+  std::optional<int> l;
+  std::optional<int> w;
+  std::optional<int> c;
+  std::optional<uint64_t> packetsize;
+  std::optional<std::string> technique;
+  std::optional<std::string> layers;
+  std::optional<std::string> mapping;
+  std::optional<bool> jerasure_per_chunk_alignment;
 
   void dump(Formatter* f) const;
   void decode_json(JSONObj* obj);
@@ -83,6 +88,7 @@ struct OSDECProfileGetReply {
 struct OSDECProfileSetRequest {
   std::string name;
   std::vector<std::string> profile;
+  bool force;
 
   void dump(Formatter* f) const;
   void decode_json(JSONObj* obj);
index e226203e46d1d3cec803c0a25cc055d40d0894c3..12d926aefdc50613b2069307dcb5be18c06deb86 100644 (file)
@@ -1,10 +1,9 @@
 #pragma once
 
+#include <boost/program_options.hpp>
 #include <optional>
 #include <string>
 
-#include <boost/program_options.hpp>
-
 #include "include/ceph_assert.h"
 #include "include/random.h"
 
  *    select() function to convert the input type to the return type as well as
  *    implement how to calculate a default value if no input is given.
  *
+ * class OptionalProgramOptionReader
+ *   Redefinition of ProgramOptionalReader that returns the result as a
+ *   std::optional of the return type. Shorthand for specifying the above
+ *   with a second return type of optional<return_type> for
+ *   all optional implementations
+ *
  * ProgramOptionSelector
  *    Implementation of the above ProgramOptionReader.
  *    This is constructed with a list of options and implements the select
  *    This takes an random_number_generator so that randomness can be controlled
  *    and reproduced if desired.
  *
+ * ProgramOptionGeneratedSelector
+ *   A class for selecting an option from a list of options, similar to above.
+ *   This implements a pure virtual method generate_selections() which should be
+ *   overriden so generation code can be added to generate the selection list at
+ *   runtime, whereas ProgramOptionSelector relies on the list existing at
+ *   compile time.
+ *
  */
 
 namespace po = boost::program_options;
@@ -60,6 +72,14 @@ class ProgramOptionReader {
   std::string option_name;
 };
 
+template <typename option_type>
+class OptionalProgramOptionReader
+    : public ProgramOptionReader<option_type, std::optional<option_type>> {
+ public:
+  using ProgramOptionReader<option_type,
+                            std::optional<option_type>>::ProgramOptionReader;
+};
+
 template <typename option_type,
           int num_selections,
           const std::array< option_type,
@@ -94,6 +114,54 @@ class ProgramOptionSelector : public ProgramOptionReader<option_type> {
 
   std::optional<option_type> first_value;
 };
-}  // namespace io_sequence
+
+template <typename option_type>
+class ProgramOptionGeneratedSelector
+    : public OptionalProgramOptionReader<option_type> {
+ public:
+  ProgramOptionGeneratedSelector(ceph::util::random_number_generator<int>& rng,
+                                 po::variables_map& vm,
+                                 const std::string& option_name,
+                                 bool first_use)
+      : OptionalProgramOptionReader<option_type>(vm, option_name),
+        rng(rng),
+        first_use(first_use) {}
+
+  const std::optional<option_type> select() final {
+    if (this->force_value.has_value())
+      return *this->force_value;
+    else if (first_use)
+      return selectFirst();
+    else
+      return selectRandom();
+  }
+
+ protected:
+  virtual const std::vector<option_type> generate_selections() = 0;
+  virtual const std::optional<option_type> selectFirst() {
+    first_use = false;
+    std::vector<option_type> selection = generate_selections();
+    if (selection.size() > 0)
+      return selection[0];
+    else
+      return std::nullopt;
+  }
+
+  virtual const std::optional<option_type> selectRandom() {
+    std::vector<option_type> selection = generate_selections();
+    if (selection.size() > 0)
+      return selection[rng(selection.size() - 1)];
+    else
+      return std::nullopt;
+  }
+
+  bool is_first_use() { return first_use; }
+
+ private:
+  ceph::util::random_number_generator<int>& rng;
+
+  bool first_use;
+};
 }  // namespace tester
+}  // namespace io_sequence
 }  // namespace ceph
\ No newline at end of file
index 7f7a10f649e468056d6d8a8b35528b9b45f405a7..d645edbd0f13426793c9d1e2055de0fe88d44e27 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <boost/asio/io_context.hpp>
 #include <iostream>
+#include <map>
 #include <vector>
 
 #include "common/Formatter.h"
 #include "common/ceph_json.h"
 #include "common/debug.h"
 #include "common/dout.h"
-#include "common/split.h"
-#include "common/strtol.h" // for strict_iecstrtoll()
-#include "common/ceph_json.h"
-#include "common/Formatter.h"
-
 #include "common/io_exerciser/DataGenerator.h"
 #include "common/io_exerciser/EcIoSequence.h"
 #include "common/io_exerciser/IoOp.h"
@@ -25,6 +21,9 @@
 #include "common/json/BalancerStructures.h"
 #include "common/json/ConfigStructures.h"
 #include "common/json/OSDStructures.h"
+#include "common/split.h"
+#include "common/strtol.h"  // for strict_iecstrtoll()
+#include "erasure-code/ErasureCodePlugin.h"
 #include "fmt/format.h"
 #include "global/global_context.h"
 #include "global/global_init.h"
@@ -159,12 +158,18 @@ po::options_description get_options_description() {
       "seed", po::value<int>(), "seed for whole test")(
       "seqseed", po::value<int>(), "seed for sequence")(
       "blocksize,b", po::value<Size>(), "block size (default 2048)")(
-      "chunksize,c", po::value<Size>(), "chunk size (default 4096)")(
-      "pool,p", po::value<std::string>(), "pool name")(
+      "pool,p", po::value<std::string>(), "existing pool name")(
+      "profile", po::value<std::string>(), "existing profile name")(
       "object,o", po::value<std::string>()->default_value("test"),
-      "object name")("km", po::value<Pair>(),
-                     "k,m EC pool profile (default 2,2)")(
-      "plugin", po::value<PluginString>(), "EC plugin (isa or jerasure)")(
+      "object name")("plugin", po::value<PluginString>(), "EC plugin")(
+      "chunksize,c", po::value<Size>(), "chunk size (default 4096)")(
+      "km", po::value<Pair>(), "k,m EC pool profile (default 2,2)")(
+      "technique", po::value<std::string>(), "EC profile technique")(
+      "packetsize", po::value<uint64_t>(), "Jerasure EC profile packetsize")(
+      "w", po::value<uint64_t>(), "Jerasure EC profile w value")(
+      "c", po::value<uint64_t>(), "Shec EC profile c value")(
+      "mapping", po::value<std::string>(), "LRC EC profile mapping")(
+      "layers", po::value<std::string>(), "LRC EC profile layers")(
       "objectsize", po::value<Pair>(),
       "min,max object size in blocks (default 1,32)")(
       "threads,t", po::value<int>(),
@@ -259,11 +264,499 @@ ceph::io_sequence::tester::SelectSeqRange::select() {
   }
 }
 
+ceph::io_sequence::tester::SelectErasureTechnique::SelectErasureTechnique(
+    ceph::util::random_number_generator<int>& rng,
+    po::variables_map& vm,
+    std::string_view plugin,
+    bool first_use)
+    : ProgramOptionGeneratedSelector<std::string>(rng, vm, "technique",
+                                                  first_use),
+      rng(rng),
+      plugin(plugin) {}
+
+const std::vector<std::string>
+ceph::io_sequence::tester::SelectErasureTechnique::generate_selections() {
+  std::vector<std::string> techniques = {};
+  if (plugin == "jerasure") {
+    techniques.push_back("reed_sol_van");
+    techniques.push_back("reed_sol_r6_op");
+    techniques.push_back("cauchy_orig");
+    techniques.push_back("cauchy_good");
+    techniques.push_back("liberation");
+    techniques.push_back("blaum_roth");
+    techniques.push_back("liber8tion");
+  } else if (plugin == "isa") {
+    techniques.push_back("reed_sol_van");
+    techniques.push_back("cauchy");
+  } else if (plugin == "shec") {
+    techniques.push_back("single");
+    techniques.push_back("multiple");
+  }
+
+  return techniques;
+}
+
+ceph::io_sequence::tester::lrc::SelectMappingAndLayers::SelectMappingAndLayers(
+    ceph::util::random_number_generator<int>& rng,
+    po::variables_map& vm,
+    bool first_use)
+    : rng_seed(rng()),
+      mapping_rng{rng_seed},
+      layers_rng{rng_seed},
+      sma{mapping_rng, vm, "mapping", first_use},
+      sly{layers_rng, vm, "layers", first_use} {
+  if (sma.isForced() != sly.isForced()) {
+    ceph_abort_msg("Mapping and layers must be used together when one is used");
+  }
+}
+
+const std::pair<std::string, std::string>
+ceph::io_sequence::tester::lrc::SelectMappingAndLayers::select() {
+  return std::pair<std::string, std::string>(sma.select(), sly.select());
+}
+
+ceph::io_sequence::tester::SelectErasureKM::SelectErasureKM(
+    ceph::util::random_number_generator<int>& rng,
+    po::variables_map& vm,
+    std::string_view plugin,
+    const std::optional<std::string>& technique,
+    bool first_use)
+    : ProgramOptionGeneratedSelector<std::pair<int, int>>(rng, vm, "km",
+                                                          first_use),
+      rng(rng),
+      plugin(plugin),
+      technique(technique) {}
+
+const std::vector<std::pair<int, int>>
+ceph::io_sequence::tester::SelectErasureKM::generate_selections() {
+  std::vector<std::pair<int, int>> selection;
+
+  // Gives different spreads of k and m depending on the plugin and technique
+  if (plugin == "isa" || plugin == "clay" ||
+      (plugin == "jerasure" &&
+       (technique == "reed_sol_van" || technique == "cauchy_orig" ||
+        technique == "cauchy_good" || technique == std::nullopt))) {
+    for (int m = 1; m <= 3; m++)
+      for (int k = 2; k <= 6; k++) selection.push_back({k, m});
+  } else if (plugin == "shec" ||
+             (plugin == "jerasure" &&
+              (technique == "liberation" || technique == "blaum_roth"))) {
+    for (int m = 1; m <= 2; m++)
+      for (int k = 2; k <= 6; k++) selection.push_back({k, m});
+  } else if (plugin == "jerasure" &&
+             (technique == "reed_sol_r6_op" || technique == "liber8tion")) {
+    for (int k = 2; k <= 6; k++) selection.push_back({k, 2});
+  }
+
+  // We want increased chances of these as we will test with c=1 and c=2
+  if (plugin == "shec")
+    for (int i = 0; i < 2; i++)
+      for (int k = 3; k <= 6; k++) selection.push_back({k, 3});
+
+  // Add extra miscelaneous interesting options for testing w values
+  if (plugin == "jerasure") {
+    if (technique == "reed_sol_van")
+      // Double chance of chosing to test more w values
+      for (int i = 0; i < 2; i++) selection.push_back({6, 3});
+
+    if (technique == "liberation" || technique == "blaum_roth")
+      // Double chance of chosing to test more different w values
+      for (int i = 0; i < 2; i++) selection.push_back({6, 2});
+
+    if (technique == "liber8tion") selection.push_back({2, 2});
+  }
+
+  return selection;
+}
+
+ceph::io_sequence::tester::jerasure::SelectErasureW::SelectErasureW(
+    ceph::util::random_number_generator<int>& rng,
+    po::variables_map& vm,
+    std::string_view plugin,
+    const std::optional<std::string_view>& technique,
+    const std::optional<std::pair<int, int>>& km,
+    const std::optional<uint64_t>& packetsize,
+    bool first_use)
+    : ProgramOptionGeneratedSelector<uint64_t>(rng, vm, "w", first_use),
+      rng(rng),
+      plugin(plugin),
+      km(km),
+      packetsize(packetsize) {}
+
+const std::vector<uint64_t>
+ceph::io_sequence::tester::jerasure::SelectErasureW::generate_selections() {
+  std::vector<uint64_t> selection = {};
+
+  if (plugin != "jerasure") {
+    return selection;
+  }
+
+  if (technique && km && technique == "reed_sol_van" && km->first == 6 &&
+      km->second == 3) {
+    selection.push_back(16);
+    selection.push_back(32);
+  }
+
+  if (km && km->first == 6 && km->second == 2) {
+    if (technique && technique == "liberation") {
+      if (packetsize == 32) selection.push_back(11);
+      if (packetsize == 36) selection.push_back(13);
+    } else if (technique && technique == "blaum_roth") {
+      if (packetsize == 44) selection.push_back(7);
+      if (packetsize == 60) selection.push_back(10);
+    }
+  }
+
+  return selection;
+}
+
+ceph::io_sequence::tester::shec::SelectErasureC::SelectErasureC(
+    ceph::util::random_number_generator<int>& rng,
+    po::variables_map& vm,
+    std::string_view plugin,
+    const std::optional<std::pair<int, int>>& km,
+    bool first_use)
+    : ProgramOptionGeneratedSelector<uint64_t>(rng, vm, "c", first_use),
+      rng(rng),
+      plugin(plugin),
+      km(km) {}
+
+const std::vector<uint64_t>
+ceph::io_sequence::tester::shec::SelectErasureC::generate_selections() {
+  if (plugin != "shec") {
+    return {};
+  }
+
+  std::vector<uint64_t> selection = {};
+  selection.push_back(1);
+
+  if (km && km->first == 3 && km->second >= 3) {
+    selection.push_back(2);
+  }
+
+  return selection;
+}
+
+ceph::io_sequence::tester::jerasure::SelectErasurePacketSize::
+    SelectErasurePacketSize(ceph::util::random_number_generator<int>& rng,
+                            po::variables_map& vm,
+                            std::string_view plugin,
+                            const std::optional<std::string_view>& technique,
+                            const std::optional<std::pair<int, int>>& km,
+                            bool first_use)
+    : ProgramOptionGeneratedSelector<uint64_t>(rng, vm, "packetsize",
+                                               first_use),
+      rng(rng),
+      plugin(plugin),
+      technique(technique),
+      km(km) {}
+
+const std::vector<uint64_t> ceph::io_sequence::tester::jerasure::
+    SelectErasurePacketSize::generate_selections() {
+  std::vector<uint64_t> selection = {};
+
+  if (plugin != "jerasure") {
+    return selection;
+  }
+
+  if (technique == "cauchy_orig" ||technique == "cauchy_good" ||
+      technique == "liberation" || technique == "blaum_roth" ||
+      technique == "liber8tion") {
+    selection.push_back(32);
+  }
+
+  if (km && technique && technique == "liberation" && km->first == 6 &&
+      km->second == 2) {
+    selection.push_back(32);
+    selection.push_back(36);
+  }
+
+  if (km && technique && technique == "blaum_roth" && km->first == 6 &&
+      km->second == 2) {
+    selection.push_back(44);
+    selection.push_back(60);
+  }
+
+  if (km && technique && technique == "liber8tion" && km->first == 6 &&
+      km->second == 2) {
+    selection.push_back(92);
+  }
+
+  return selection;
+}
+
+ceph::io_sequence::tester::SelectErasureChunkSize::SelectErasureChunkSize(
+    ceph::util::random_number_generator<int>& rng,
+    po::variables_map& vm,
+    ErasureCodeInterfaceRef ec_impl,
+    bool first_use)
+    : ProgramOptionGeneratedSelector(rng, vm, "chunksize", first_use),
+      rng(rng),
+      ec_impl(ec_impl) {}
+
+const std::vector<uint64_t>
+ceph::io_sequence::tester::SelectErasureChunkSize::generate_selections() {
+  int minimum_granularity = ec_impl->get_minimum_granularity();
+  int data_chunks = ec_impl->get_data_chunk_count();
+  int minimum_chunksize =
+      ec_impl->get_chunk_size(minimum_granularity * data_chunks);
+
+  std::vector<uint64_t> choices = {};
+
+  if (4096 % minimum_chunksize == 0) {
+    choices.push_back(4096);
+  } else {
+    choices.push_back(minimum_chunksize * rng(4));
+  }
+
+  if ((64 * 1024) % minimum_chunksize == 0) {
+    choices.push_back(64 * 1024);
+  } else {
+    choices.push_back(minimum_chunksize * rng(64));
+  }
+
+  if ((256 * 1024) % minimum_chunksize == 0) {
+    choices.push_back(256 * 1024);
+  } else {
+    choices.push_back(minimum_chunksize * rng(256));
+  }
+
+  return choices;
+}
+
+ceph::io_sequence::tester::SelectErasureProfile::SelectErasureProfile(
+    boost::intrusive_ptr<CephContext> cct,
+    ceph::util::random_number_generator<int>& rng,
+    po::variables_map& vm,
+    librados::Rados& rados,
+    bool dry_run,
+    bool first_use)
+    : ProgramOptionReader(vm, "profile"),
+      cct(cct),
+      rados(rados),
+      dry_run(dry_run),
+      rng(rng),
+      vm(vm),
+      first_use(first_use),
+      spl{rng, vm, "plugin", true},
+      sml{rng, vm, true} {
+  if (isForced()) {
+    std::array<std::string, 9> disallowed_options = {
+        "pool", "km",      "technique", "packetsize", "c",
+        "w",    "mapping", "layers",    "chunksize"};
+
+    for (std::string& option : disallowed_options) {
+      if (vm.count(option) > 0) {
+        ceph_abort_msg(
+            fmt::format("{} option not allowed "
+                        "if profile is specified",
+                        option));
+      }
+    }
+  }
+}
+
+const ceph::io_sequence::tester::Profile
+ceph::io_sequence::tester::SelectErasureProfile::select() {
+  ceph::io_sequence::tester::Profile profile;
+
+  if (force_value) {
+    if (!dry_run) {
+      profile = selectExistingProfile(force_value->name);
+    }
+  } else {
+    profile.plugin = spl.select();
+
+    SelectErasureTechnique set{rng, vm, profile.plugin, first_use};
+    profile.technique = set.select();
+
+    SelectErasureKM skm{rng, vm, profile.plugin, profile.technique, first_use};
+    profile.km = skm.select();
+
+    jerasure::SelectErasurePacketSize sps{
+        rng, vm, profile.plugin, profile.technique, profile.km, first_use};
+    profile.packet_size = sps.select();
+
+    if (profile.plugin == "jerasure") {
+      jerasure::SelectErasureW ssw{rng,
+                                   vm,
+                                   profile.plugin,
+                                   profile.technique,
+                                   profile.km,
+                                   profile.packet_size,
+                                   first_use};
+      profile.w = ssw.select();
+    } else if (profile.plugin == "shec") {
+      shec::SelectErasureC ssc{rng, vm, profile.plugin, profile.km, first_use};
+      profile.c = ssc.select();
+    } else if (profile.plugin == "lrc") {
+      std::pair<std::string, std::string> mappingAndLayers = sml.select();
+      profile.mapping = mappingAndLayers.first;
+      profile.layers = mappingAndLayers.second;
+    }
+
+    ErasureCodeProfile erasureCodeProfile;
+    erasureCodeProfile["plugin"] = std::string(profile.plugin);
+    if (profile.km) {
+      erasureCodeProfile["k"] = std::to_string(profile.km->first);
+      erasureCodeProfile["m"] = std::to_string(profile.km->second);
+    }
+    if (profile.technique) {
+      erasureCodeProfile["technique"] = *profile.technique;
+    }
+    if (profile.packet_size) {
+      erasureCodeProfile["packetsize"] = std::to_string(*profile.packet_size);
+    }
+    if (profile.c) {
+      erasureCodeProfile["c"] = std::to_string(*profile.c);
+    }
+    if (profile.w) {
+      erasureCodeProfile["packetsize"] = std::to_string(*profile.w);
+    }
+    if (profile.jerasure_per_chunk_alignment) {
+      erasureCodeProfile["jerasure_per_chunk_alignment"] =
+          std::to_string(*profile.jerasure_per_chunk_alignment);
+    }
+    if (profile.mapping) {
+      erasureCodeProfile["mapping"] = *profile.mapping;
+    }
+    if (profile.layers) {
+      erasureCodeProfile["layers"] = *profile.layers;
+    }
+
+    ErasureCodePluginRegistry& instance = ErasureCodePluginRegistry::instance();
+    ErasureCodeInterfaceRef ec_impl;
+    std::stringstream ss;
+    instance.factory(std::string(profile.plugin),
+                     cct->_conf.get_val<std::string>("erasure_code_dir"),
+                     erasureCodeProfile, &ec_impl, &ss);
+    ceph_assert(ec_impl);
+
+    SelectErasureChunkSize scs{rng, vm, ec_impl, first_use};
+    profile.chunk_size = scs.select();
+
+    profile.name = fmt::format("testprofile_pl{}", profile.plugin);
+    if (profile.technique) {
+      profile.name += fmt::format("_t{}", profile.technique);
+    }
+    if (profile.km) {
+      profile.name +=
+          fmt::format("_k{}_m{}", profile.km->first, profile.km->second);
+    }
+    if (profile.packet_size) {
+      profile.name += fmt::format("_ps{}", *profile.packet_size);
+    }
+    if (profile.c) {
+      profile.name += fmt::format("_c{}", *profile.c);
+    }
+    if (profile.w) {
+      profile.name += fmt::format("_w{}", *profile.w);
+    }
+    if (profile.chunk_size) {
+      profile.name += fmt::format("_cs{}", *profile.chunk_size);
+    }
+    if (profile.mapping) {
+      profile.name += fmt::format("_ma{}", *profile.mapping);
+    }
+
+    if (!dry_run) {
+      create(profile);
+    }
+  }
+
+  first_use = false;
+
+  return profile;
+}
+
+void ceph::io_sequence::tester::SelectErasureProfile::create(
+    const ceph::io_sequence::tester::Profile& profile) {
+  bufferlist inbl, outbl;
+  auto formatter = std::make_unique<JSONFormatter>(false);
+
+  std::vector<std::string> profile_values = {
+      fmt::format("plugin={}", profile.plugin)};
+
+  if (profile.km) {
+    profile_values.push_back(fmt::format("k={}", profile.km->first));
+    profile_values.push_back(fmt::format("m={}", profile.km->second));
+  }
+  if (profile.technique)
+    profile_values.push_back(fmt::format("technique={}", profile.technique));
+  if (profile.packet_size)
+    profile_values.push_back(fmt::format("packetsize={}", profile.packet_size));
+  if (profile.c) profile_values.push_back(fmt::format("c={}", profile.c));
+  if (profile.w) profile_values.push_back(fmt::format("w={}", profile.w));
+  if (profile.mapping)
+    profile_values.push_back(fmt::format("mapping={}", profile.mapping));
+  if (profile.layers)
+    profile_values.push_back(fmt::format("layers={}", profile.layers));
+  if (profile.chunk_size)
+    profile_values.push_back(fmt::format("stripe_unit={}", profile.chunk_size));
+
+  // Crush-failure-domain only seems to be taken into account when specifying
+  // k and m values in LRC, so we set a crush step to do the same, which is
+  // what LRC does under the covers
+  if (profile.plugin == "lrc")
+    profile_values.push_back("crush-steps=[[\"chooseleaf\",\"osd\",0]]");
+  else
+    profile_values.push_back("crush-failure-domain=osd");
+
+  bool force =
+      profile.chunk_size.has_value() && (*(profile.chunk_size) % 4096 != 0);
+  ceph::messaging::osd::OSDECProfileSetRequest ecProfileSetRequest{
+      profile.name, profile_values, force};
+  int rc =
+      send_mon_command(ecProfileSetRequest, rados, "OSDECProfileSetRequest",
+                       inbl, &outbl, formatter.get());
+  ceph_assert(rc == 0);
+}
+
+const ceph::io_sequence::tester::Profile
+ceph::io_sequence::tester::SelectErasureProfile::selectExistingProfile(
+    const std::string& profile_name) {
+  int rc;
+  bufferlist inbl, outbl;
+  auto formatter = std::make_shared<JSONFormatter>(false);
+
+  ceph::messaging::osd::OSDECProfileGetRequest osdECProfileGetRequest{
+      profile_name};
+  rc = send_mon_command(osdECProfileGetRequest, rados, "OSDECProfileGetRequest",
+                        inbl, &outbl, formatter.get());
+  ceph_assert(rc == 0);
+
+  JSONParser p;
+  bool success = p.parse(outbl.c_str(), outbl.length());
+  ceph_assert(success);
+
+  ceph::messaging::osd::OSDECProfileGetReply reply;
+  reply.decode_json(&p);
+
+  ceph::io_sequence::tester::Profile profile{};
+  profile.name = profile_name;
+  profile.plugin = reply.plugin;
+  profile.km = {reply.k, reply.m};
+  profile.technique = reply.technique->c_str();
+  profile.packet_size = reply.packetsize;
+  profile.c = reply.c;
+  profile.w = reply.w;
+  profile.mapping = reply.mapping;
+  profile.layers = reply.layers;
+
+  return profile;
+}
+
 ceph::io_sequence::tester::SelectErasurePool::SelectErasurePool(
-    ceph::util::random_number_generator<int>& rng, po::variables_map vm,
-    librados::Rados& rados, bool dry_run, bool allow_pool_autoscaling,
-    bool allow_pool_balancer, bool allow_pool_deep_scrubbing,
-    bool allow_pool_scrubbing, bool test_recovery)
+    boost::intrusive_ptr<CephContext> cct,
+    ceph::util::random_number_generator<int>& rng,
+    po::variables_map& vm,
+    librados::Rados& rados,
+    bool dry_run,
+    bool allow_pool_autoscaling,
+    bool allow_pool_balancer,
+    bool allow_pool_deep_scrubbing,
+    bool allow_pool_scrubbing,
+    bool test_recovery)
     : ProgramOptionReader<std::string>(vm, "pool"),
       rados(rados),
       dry_run(dry_run),
@@ -272,90 +765,90 @@ ceph::io_sequence::tester::SelectErasurePool::SelectErasurePool(
       allow_pool_deep_scrubbing(allow_pool_deep_scrubbing),
       allow_pool_scrubbing(allow_pool_scrubbing),
       test_recovery(test_recovery),
-      skm{rng, vm, "km", true},
-      spl{rng, vm, "plugin", true},
-      scs{rng, vm, "chunksize", true} {
-  if (!skm.isForced()) {
-    if (vm.count("pool")) {
-      force_value = vm["pool"].as<std::string>();
+      first_use(true),
+      sep{cct, rng, vm, rados, dry_run, first_use} {
+  if (isForced()) {
+    std::array<std::string, 9> disallowed_options = {
+        "profile", "km",      "technique", "packetsize", "c",
+        "w",       "mapping", "layers",    "chunksize"};
+
+    for (std::string& option : disallowed_options) {
+      if (vm.count(option) > 0) {
+        ceph_abort_msg(
+            fmt::format("{} option not allowed "
+                        "if pool is specified",
+                        option));
+      }
     }
   }
 }
 
 const std::string ceph::io_sequence::tester::SelectErasurePool::select() {
-  std::pair<int, int> value;
-  if (!skm.isForced() && force_value.has_value()) {
-    int rc;
-    bufferlist inbl, outbl;
-    auto formatter = std::make_unique<JSONFormatter>(false);
+  first_use = true;
 
-    ceph::messaging::osd::OSDPoolGetRequest osdPoolGetRequest{*force_value};
-    rc = send_mon_command(osdPoolGetRequest, rados, "OSDPoolGetRequest", inbl,
-                          &outbl, formatter.get());
-    ceph_assert(rc == 0);
-
-    JSONParser p;
-    bool success = p.parse(outbl.c_str(), outbl.length());
-    ceph_assert(success);
+  std::string created_pool_name = "";
+  if (!dry_run) {
+    if (isForced()) {
+      int rc;
+      bufferlist inbl, outbl;
+      auto formatter = std::make_shared<JSONFormatter>(false);
+
+      ceph::messaging::osd::OSDPoolGetRequest osdPoolGetRequest{*force_value};
+      rc = send_mon_command(osdPoolGetRequest, rados, "OSDPoolGetRequest", inbl,
+                            &outbl, formatter.get());
+      ceph_assert(rc == 0);
 
-    ceph::messaging::osd::OSDPoolGetReply osdPoolGetReply;
-    osdPoolGetReply.decode_json(&p);
+      JSONParser p;
+      bool success = p.parse(outbl.c_str(), outbl.length());
+      ceph_assert(success);
 
-    ceph::messaging::osd::OSDECProfileGetRequest osdECProfileGetRequest{
-        osdPoolGetReply.erasure_code_profile};
-    rc = send_mon_command(osdECProfileGetRequest, rados,
-                          "OSDECProfileGetRequest", inbl, &outbl,
-                          formatter.get());
-    ceph_assert(rc == 0);
+      ceph::messaging::osd::OSDPoolGetReply osdPoolGetReply;
+      osdPoolGetReply.decode_json(&p);
 
-    success = p.parse(outbl.c_str(), outbl.length());
-    ceph_assert(success);
+      profile = sep.selectExistingProfile(osdPoolGetReply.erasure_code_profile);
+    } else {
+      created_pool_name = create();
+    }
 
-    ceph::messaging::osd::OSDECProfileGetReply reply;
-    reply.decode_json(&p);
-    k = reply.k;
-    m = reply.m;
-    return *force_value;
-  } else {
-    value = skm.select();
+    if (!dry_run) {
+      configureServices(allow_pool_autoscaling, allow_pool_balancer,
+                        allow_pool_deep_scrubbing, allow_pool_scrubbing,
+                        test_recovery);
+    }
   }
-  k = value.first;
-  m = value.second;
 
-  const std::string plugin = std::string(spl.select());
-  const uint64_t chunk_size = scs.select();
-
-  std::string pool_name = "ec_" + plugin + "_cs" + std::to_string(chunk_size) +
-                          "_k" + std::to_string(k) + "_m" + std::to_string(m);
-  if (!dry_run) {
-    create_pool(rados, pool_name, plugin, chunk_size, k, m);
-  }
-  return pool_name;
+  return force_value.value_or(created_pool_name);
 }
 
-void ceph::io_sequence::tester::SelectErasurePool::create_pool(
-    librados::Rados& rados, const std::string& pool_name,
-    const std::string& plugin, uint64_t chunk_size, int k, int m) {
+std::string ceph::io_sequence::tester::SelectErasurePool::create() {
   int rc;
   bufferlist inbl, outbl;
-  auto formatter = std::make_unique<JSONFormatter>(false);
+  auto formatter = std::make_shared<JSONFormatter>(false);
 
-  ceph::messaging::osd::OSDECProfileSetRequest ecProfileSetRequest{
-      fmt::format("testprofile-{}", pool_name),
-      {fmt::format("plugin={}", plugin), fmt::format("k={}", k),
-       fmt::format("m={}", m), fmt::format("stripe_unit={}", chunk_size),
-       fmt::format("crush-failure-domain=osd")}};
-  rc = send_mon_command(ecProfileSetRequest, rados, "OSDECProfileSetRequest",
-                        inbl, &outbl, formatter.get());
-  ceph_assert(rc == 0);
+  std::string pool_name;
+  profile = sep.select();
+  pool_name = fmt::format("testpool-pr{}", profile->name);
 
   ceph::messaging::osd::OSDECPoolCreateRequest poolCreateRequest{
-      pool_name, "erasure", 8, 8, fmt::format("testprofile-{}", pool_name)};
+      pool_name, "erasure", 8, 8, profile->name};
   rc = send_mon_command(poolCreateRequest, rados, "OSDECPoolCreateRequest",
                         inbl, &outbl, formatter.get());
   ceph_assert(rc == 0);
 
-  if (allow_pool_autoscaling) {
+  return pool_name;
+}
+
+void ceph::io_sequence::tester::SelectErasurePool::configureServices(
+    bool allow_pool_autoscaling,
+    bool allow_pool_balancer,
+    bool allow_pool_deep_scrubbing,
+    bool allow_pool_scrubbing,
+    bool test_recovery) {
+  int rc;
+  bufferlist inbl, outbl;
+  auto formatter = std::make_shared<JSONFormatter>(false);
+
+  if (!allow_pool_autoscaling) {
     ceph::messaging::osd::OSDSetRequest setNoAutoscaleRequest{"noautoscale",
                                                               std::nullopt};
     rc = send_mon_command(setNoAutoscaleRequest, rados, "OSDSetRequest", inbl,
@@ -363,13 +856,13 @@ void ceph::io_sequence::tester::SelectErasurePool::create_pool(
     ceph_assert(rc == 0);
   }
 
-  if (allow_pool_balancer) {
-    ceph::messaging::balancer::BalancerOffRequest balancerOffRequest{};
+  if (!allow_pool_balancer) {
+    ceph::messaging::balancer::BalancerOffRequest balancerOffRequest;
     rc = send_mon_command(balancerOffRequest, rados, "BalancerOffRequest", inbl,
                           &outbl, formatter.get());
     ceph_assert(rc == 0);
 
-    ceph::messaging::balancer::BalancerStatusRequest balancerStatusRequest{};
+    ceph::messaging::balancer::BalancerStatusRequest balancerStatusRequest;
     rc = send_mon_command(balancerStatusRequest, rados, "BalancerStatusRequest",
                           inbl, &outbl, formatter.get());
     ceph_assert(rc == 0);
@@ -427,8 +920,14 @@ ceph::io_sequence::tester::TestObject::TestObject(
         oid, sbs.select(), rng());
   } else {
     const std::string pool = spo.select();
-    poolK = spo.getChosenK();
-    poolM = spo.getChosenM();
+    if (!dryrun) {
+      ceph_assert(spo.getProfile());
+      poolKM = spo.getProfile()->km;
+      if (spo.getProfile()->mapping && spo.getProfile()->layers) {
+        poolMappingLayers = {*spo.getProfile()->mapping,
+                             *spo.getProfile()->layers};
+      }
+    }
 
     int threads = snt.select();
 
@@ -467,7 +966,8 @@ ceph::io_sequence::tester::TestObject::TestObject(
 
   if (testrecovery) {
     seq = ceph::io_exerciser::EcIoSequence::generate_sequence(
-        curseq, obj_size_range, poolK, poolM, seqseed.value_or(rng()));
+        curseq, obj_size_range, poolKM, poolMappingLayers,
+        seqseed.value_or(rng()));
   } else {
     seq = ceph::io_exerciser::IoSequence::generate_sequence(
         curseq, obj_size_range, seqseed.value_or(rng()));
@@ -505,7 +1005,8 @@ bool ceph::io_sequence::tester::TestObject::next() {
       } else {
         if (testrecovery) {
           seq = ceph::io_exerciser::EcIoSequence::generate_sequence(
-              curseq, obj_size_range, poolK, poolM, seqseed.value_or(rng()));
+              curseq, obj_size_range, poolKM, poolMappingLayers,
+              seqseed.value_or(rng()));
         } else {
           seq = ceph::io_exerciser::IoSequence::generate_sequence(
               curseq, obj_size_range, seqseed.value_or(rng()));
@@ -528,14 +1029,17 @@ int ceph::io_sequence::tester::TestObject::get_num_io() {
   return exerciser_model->get_num_io();
 }
 
-ceph::io_sequence::tester::TestRunner::TestRunner(po::variables_map& vm,
-                                                  librados::Rados& rados)
+ceph::io_sequence::tester::TestRunner::TestRunner(
+    boost::intrusive_ptr<CephContext> cct,
+    po::variables_map& vm,
+    librados::Rados& rados)
     : rados(rados),
       seed(vm.contains("seed") ? vm["seed"].as<int>() : time(nullptr)),
       rng(ceph::util::random_number_generator<int>(seed)),
       sbs{rng, vm, "blocksize", true},
       sos{rng, vm, "objectsize", true},
-      spo{rng,
+      spo{cct,
+          rng,
           vm,
           rados,
           vm.contains("dryrun"),
@@ -596,9 +1100,18 @@ void ceph::io_sequence::tester::TestRunner::list_sequence(bool testrecovery) {
   ceph::io_exerciser::Sequence s = ceph::io_exerciser::Sequence::SEQUENCE_BEGIN;
   std::unique_ptr<ceph::io_exerciser::IoSequence> seq;
   if (testrecovery) {
+    std::optional<ceph::io_sequence::tester::Profile> profile =
+        spo.getProfile();
+    std::optional<std::pair<int, int>> km;
+    std::optional<std::pair<std::string_view, std::string_view>> mappingLayers;
+    if (profile) {
+      km = profile->km;
+      if (profile->mapping && profile->layers) {
+        mappingLayers = {*spo.getProfile()->mapping, *spo.getProfile()->layers};
+      }
+    }
     seq = ceph::io_exerciser::EcIoSequence::generate_sequence(
-        s, obj_size_range, spo.getChosenK(), spo.getChosenM(),
-        seqseed.value_or(rng()));
+        s, obj_size_range, km, mappingLayers, seqseed.value_or(rng()));
   } else {
     seq = ceph::io_exerciser::IoSequence::generate_sequence(
         s, obj_size_range, seqseed.value_or(rng()));
@@ -633,7 +1146,7 @@ std::string ceph::io_sequence::tester::TestRunner::get_token(bool allow_eof) {
 }
 
 std::optional<std::string>
-ceph::io_sequence::tester::TestRunner ::get_optional_token() {
+ceph::io_sequence::tester::TestRunner::get_optional_token() {
   std::optional<std::string> ret = std::nullopt;
   if (tokens != split.end()) {
     ret = std::string(*tokens++);
@@ -652,7 +1165,7 @@ uint64_t ceph::io_sequence::tester::TestRunner::get_numeric_token() {
 }
 
 std::optional<uint64_t>
-ceph::io_sequence::tester::TestRunner ::get_optional_numeric_token() {
+ceph::io_sequence::tester::TestRunner::get_optional_numeric_token() {
   std::string parse_error;
   std::optional<std::string> token = get_optional_token();
   if (token) {
@@ -937,7 +1450,8 @@ int main(int argc, char** argv) {
 
   std::unique_ptr<ceph::io_sequence::tester::TestRunner> runner;
   try {
-    runner = std::make_unique<ceph::io_sequence::tester::TestRunner>(vm, rados);
+    runner =
+        std::make_unique<ceph::io_sequence::tester::TestRunner>(cct, vm, rados);
   } catch (const po::error& e) {
     return 1;
   }
index 40826f0d85948fbb3aa9e0865070e94bb9aa4eeb..986b823104e499eca1e6c8963937242c6ba87bb8 100644 (file)
@@ -1,5 +1,7 @@
+#include <boost/asio/io_context.hpp>
 #include <boost/program_options.hpp>
 #include <optional>
+#include <string>
 #include <utility>
 
 #include "ProgramOptionReader.h"
@@ -7,16 +9,12 @@
 #include "common/io_exerciser/IoSequence.h"
 #include "common/io_exerciser/Model.h"
 #include "common/split.h"
+#include "erasure-code/ErasureCodePlugin.h"
 #include "global/global_context.h"
 #include "global/global_init.h"
 #include "include/random.h"
 #include "librados/librados_asio.h"
 
-#include <boost/asio/io_context.hpp>
-#include <boost/program_options.hpp>
-
-#include <optional>
-
 /* Overview
  *
  * class SelectObjectSize
  * class SelectErasurePlugin
  *   Selects an plugin for a test
  *
+ * class SelectErasurePacketSize
+ *   Selects a packetsize to be used by jerasure
+ *
+ * class SelectErasureC
+ *   Potentially selects a C value to be used for the shec plugin
+ *
+ * class SelectErasureW
+ *   Potentially selects a W value to be used for the jerasure plugin
+ *
  * class SelectErasurePool
  *   Selects an EC pool (plugin,k and m) for a test. Also creates the
  *   pool as well.
  *
+ * class SelectErasureProfile
+ *   Selects an EC profile for a test. Will create one if no name is specified
+ *
  * class SelectSeqRange
  *   Selects a sequence or range of sequences for a test
  *
 namespace po = boost::program_options;
 
 namespace ceph {
+class ErasureCodePlugin;
+
 namespace io_sequence {
 namespace tester {
 // Choices for min and max object size
-// Choices for min and max object size
 inline static constexpr size_t objectSizeSize = 10;
 inline static constexpr std::array<std::pair<int, int>, objectSizeSize>
     objectSizeChoices = {{{1, 32},  // Default - best for boundary checking
@@ -113,54 +124,248 @@ using SelectNumThreads =
                           io_sequence::tester ::threadArraySize,
                           io_sequence::tester ::threadCountChoices>;
 
-// Choices for EC k+m profile
-inline constexpr int kmSize = 6;
-inline constexpr std::array<std::pair<int, int>, kmSize> kmChoices = {
-    {{2, 2},  // Default - reasonable coverage
-     {2, 1},
-     {2, 3},
-     {3, 2},
-     {4, 2},
-     {5, 1}}};
-
-using SelectErasureKM =
-    ProgramOptionSelector<std::pair<int, int>,
-                          io_sequence::tester ::kmSize,
-                          io_sequence::tester::kmChoices>;
-
-// Choices for EC chunk size
-inline static constexpr int chunkSizeSize = 3;
-inline static constexpr std::array<uint64_t, chunkSizeSize> chunkSizeChoices = {
-    {4 * 1024, 64 * 1024, 256 * 1024}};
-
-using SelectErasureChunkSize =
-    ProgramOptionSelector<uint64_t,
-                          io_sequence::tester ::chunkSizeSize,
-                          io_sequence::tester::chunkSizeChoices>;
+class SelectSeqRange
+    : public ProgramOptionReader<std::pair<ceph::io_exerciser ::Sequence,
+                                           ceph::io_exerciser ::Sequence>> {
+ public:
+  SelectSeqRange(po::variables_map& vm);
+  const std::pair<ceph::io_exerciser::Sequence, ceph::io_exerciser::Sequence>
+  select() override;
+};
 
 // Choices for plugin
-inline static constexpr int pluginListSize = 2;
+inline static constexpr int pluginListSize = 5;
 inline static constexpr std::array<std::string_view, pluginListSize>
-    pluginChoices = {{"jerasure", "isa"}};
+    pluginChoices = {{"jerasure", "isa", "clay", "shec", "lrc"}};
 
 using SelectErasurePlugin =
     ProgramOptionSelector<std::string_view,
                           io_sequence::tester ::pluginListSize,
                           io_sequence::tester ::pluginChoices>;
 
-class SelectSeqRange
-    : public ProgramOptionReader<std::pair<ceph::io_exerciser ::Sequence,
-                                           ceph::io_exerciser ::Sequence>> {
+class SelectErasureKM
+    : public ProgramOptionGeneratedSelector<std::pair<int, int>> {
  public:
-  SelectSeqRange(po::variables_map& vm);
-  const std::pair<ceph::io_exerciser::Sequence, ceph::io_exerciser::Sequence>
-  select() override;
+  SelectErasureKM(ceph::util::random_number_generator<int>& rng,
+                  po::variables_map& vm,
+                  std::string_view plugin,
+                  const std::optional<std::string>& technique,
+                  bool first_use);
+
+  const std::vector<std::pair<int, int>> generate_selections() override;
+
+ private:
+  ceph::util::random_number_generator<int>& rng;
+
+  std::string_view plugin;
+  std::optional<std::string> technique;
+};
+
+namespace shec {
+class SelectErasureC : public ProgramOptionGeneratedSelector<uint64_t> {
+ public:
+  SelectErasureC(ceph::util::random_number_generator<int>& rng,
+                 po::variables_map& vm,
+                 std::string_view plugin,
+                 const std::optional<std::pair<int, int>>& km,
+                 bool first_use);
+
+  const std::vector<uint64_t> generate_selections() override;
+
+ private:
+  ceph::util::random_number_generator<int>& rng;
+
+  std::string_view plugin;
+  std::optional<std::pair<int, int>> km;
+};
+}  // namespace shec
+
+namespace jerasure {
+class SelectErasureW : public ProgramOptionGeneratedSelector<uint64_t> {
+ public:
+  SelectErasureW(ceph::util::random_number_generator<int>& rng,
+                 po::variables_map& vm,
+                 std::string_view plugin,
+                 const std::optional<std::string_view>& technique,
+                 const std::optional<std::pair<int, int>>& km,
+                 const std::optional<uint64_t>& packetsize,
+                 bool first_use);
+
+  const std::vector<uint64_t> generate_selections() override;
+
+ private:
+  ceph::util::random_number_generator<int>& rng;
+
+  std::string_view plugin;
+  std::optional<std::string_view> technique;
+  std::optional<std::pair<int, int>> km;
+  std::optional<uint64_t> packetsize;
+};
+
+class SelectErasurePacketSize
+    : public ProgramOptionGeneratedSelector<uint64_t> {
+ public:
+  SelectErasurePacketSize(ceph::util::random_number_generator<int>& rng,
+                          po::variables_map& vm,
+                          std::string_view plugin,
+                          const std::optional<std::string_view>& technique,
+                          const std::optional<std::pair<int, int>>& km,
+                          bool first_use);
+
+  const std::vector<uint64_t> generate_selections() override;
+
+ private:
+  ceph::util::random_number_generator<int>& rng;
+
+  std::string_view plugin;
+  std::optional<std::string_view> technique;
+  std::optional<std::pair<int, int>> km;
+};
+}  // namespace jerasure
+
+namespace lrc {
+// Choices for lrc mappings and layers. The index selected for the mapping
+// matches what index will be chosen from the layers array.
+inline static constexpr int mappingLayerListSizes = 15;
+
+inline static std::array<std::string, mappingLayerListSizes> mappingChoices = {{
+    "_DD",
+    "_DDD",
+    "_DDDD",
+    "_DDDDD",
+    "_DDDDDD",
+    "_D_D",
+    "_D_DD",
+    "_D_DDD",
+    "_D_DDDD",
+    "_D_DDDDD",
+    "_D_D_",
+    "_D_D_D",
+    "_D_D_DD",
+    "_D_D_DDD",
+    "_D_D_DDDD",
+}};
+
+inline static std::array<std::string, mappingLayerListSizes> layerChoices = {{
+    "[[\"cDD\",\"\"]]",
+    "[[\"cDDD\",\"\"]]",
+    "[[\"cDDDD\",\"\"]]",
+    "[[\"cDDDDD\",\"\"]]",
+    "[[\"cDDDDDD\",\"\"]]",
+    "[[\"cDcD\",\"\"]]",
+    "[[\"cDcDD\",\"\"]]",
+    "[[\"cDcDDD\",\"\"]]",
+    "[[\"cDcDDDD\",\"\"]]",
+    "[[\"cDcDDDDD\",\"\"]]",
+    "[[\"cDcDc\",\"\"]]",
+    "[[\"cDcDcD\",\"\"]]",
+    "[[\"cDcDcDD\",\"\"]]",
+    "[[\"cDcDcDDD\",\"\"]]",
+    "[[\"cDcDcDDDD\",\"\"]]",
+}};
+
+using SelectMapping =
+    ProgramOptionSelector<std::string,
+                          io_sequence::tester::lrc::mappingLayerListSizes,
+                          io_sequence::tester::lrc::mappingChoices>;
+
+using SelectLayers =
+    ProgramOptionSelector<std::string,
+                          io_sequence::tester::lrc::mappingLayerListSizes,
+                          io_sequence::tester::lrc::layerChoices>;
+
+class SelectMappingAndLayers {
+ public:
+  SelectMappingAndLayers(ceph::util::random_number_generator<int>& rng,
+                         po::variables_map& vm,
+                         bool first_use);
+  const std::pair<std::string, std::string> select();
+
+ private:
+  uint64_t rng_seed;
+
+  ceph::util::random_number_generator<int> mapping_rng;
+  ceph::util::random_number_generator<int> layers_rng;
+
+  SelectMapping sma;
+  SelectLayers sly;
+};
+}  // namespace lrc
+
+class SelectErasureTechnique
+    : public ProgramOptionGeneratedSelector<std::string> {
+ public:
+  SelectErasureTechnique(ceph::util::random_number_generator<int>& rng,
+                         po::variables_map& vm,
+                         std::string_view plugin,
+                         bool first_use);
+
+  const std::vector<std::string> generate_selections() override;
+
+ private:
+  ceph::util::random_number_generator<int>& rng;
+
+  std::string_view plugin;
+};
+
+class SelectErasureChunkSize : public ProgramOptionGeneratedSelector<uint64_t> {
+ public:
+  SelectErasureChunkSize(ceph::util::random_number_generator<int>& rng,
+                         po::variables_map& vm,
+                         ErasureCodeInterfaceRef ec_impl,
+                         bool first_use);
+  const std::vector<uint64_t> generate_selections() override;
+
+ private:
+  ceph::util::random_number_generator<int>& rng;
+
+  ErasureCodeInterfaceRef ec_impl;
+};
+
+struct Profile {
+  std::string name;
+  std::string_view plugin;
+  std::optional<std::string> technique;
+  std::optional<std::pair<int, int>> km;
+  std::optional<uint64_t> packet_size;
+  std::optional<int> c;
+  std::optional<int> w;
+  std::optional<std::string> mapping;
+  std::optional<std::string> layers;
+  std::optional<uint64_t> chunk_size;
+  std::optional<bool> jerasure_per_chunk_alignment;
+};
+
+class SelectErasureProfile : public ProgramOptionReader<Profile> {
+ public:
+  SelectErasureProfile(boost::intrusive_ptr<CephContext> cct,
+                       ceph::util::random_number_generator<int>& rng,
+                       po::variables_map& vm, librados::Rados& rados,
+                       bool dry_run, bool first_use);
+  const Profile select() override;
+  void create(const Profile& profile);
+  const Profile selectExistingProfile(const std::string& profile_name);
+
+ private:
+  boost::intrusive_ptr<CephContext> cct;
+  librados::Rados& rados;
+  bool dry_run;
+  ceph::util::random_number_generator<int>& rng;
+  po::variables_map& vm;
+
+  bool first_use;
+
+  SelectErasurePlugin spl;
+  lrc::SelectMappingAndLayers sml;
+
+  std::unique_ptr<ErasureCodePlugin> erasureCode;
 };
 
 class SelectErasurePool : public ProgramOptionReader<std::string> {
  public:
-  SelectErasurePool(ceph::util::random_number_generator<int>& rng,
-                    po::variables_map vm,
+  SelectErasurePool(boost::intrusive_ptr<CephContext> cct,
+                    ceph::util::random_number_generator<int>& rng,
+                    po::variables_map& vm,
                     librados::Rados& rados,
                     bool dry_run,
                     bool allow_pool_autoscaling,
@@ -169,35 +374,37 @@ class SelectErasurePool : public ProgramOptionReader<std::string> {
                     bool allow_pool_scrubbing,
                     bool test_recovery);
   const std::string select() override;
-
-  bool get_allow_pool_autoscaling() { return allow_pool_autoscaling; }
-  bool get_allow_pool_balancer() { return allow_pool_balancer; }
-  bool get_allow_pool_deep_scrubbing() { return allow_pool_deep_scrubbing; }
-  bool get_allow_pool_scrubbing() { return allow_pool_scrubbing; }
-  int getChosenK() const { return k; }
-  int getChosenM() const { return m; }
+  std::string create();
+  void configureServices(bool allow_pool_autoscaling,
+                         bool allow_pool_balancer,
+                         bool allow_pool_deep_scrubbing,
+                         bool allow_pool_scrubbing,
+                         bool test_recovery);
+
+  inline bool get_allow_pool_autoscaling() { return allow_pool_autoscaling; }
+  inline bool get_allow_pool_balancer() { return allow_pool_balancer; }
+  inline bool get_allow_pool_deep_scrubbing() {
+    return allow_pool_deep_scrubbing;
+  }
+  inline bool get_allow_pool_scrubbing() { return allow_pool_scrubbing; }
+
+  inline std::optional<Profile> getProfile() { return profile; }
 
  private:
-  void create_pool( librados::Rados& rados,
-                    const std::string& pool_name,
-                    const std::string& plugin,
-                    uint64_t chunk_size, int k,
-                    int m);
-
- protected:
   librados::Rados& rados;
   bool dry_run;
+
   bool allow_pool_autoscaling;
   bool allow_pool_balancer;
   bool allow_pool_deep_scrubbing;
   bool allow_pool_scrubbing;
   bool test_recovery;
-  int k;
-  int m;
 
-  SelectErasureKM skm;
-  SelectErasurePlugin spl;
-  SelectErasureChunkSize scs;
+  bool first_use;
+
+  SelectErasureProfile sep;
+
+  std::optional<Profile> profile;
 };
 
 class TestObject {
@@ -235,14 +442,17 @@ class TestObject {
   ceph::util::random_number_generator<int>& rng;
   bool verbose;
   std::optional<int> seqseed;
-  int poolK;
-  int poolM;
+  std::optional<std::pair<int, int>> poolKM;
+  std::optional<std::pair<std::string_view, std::string_view>>
+      poolMappingLayers;
   bool testrecovery;
 };
 
 class TestRunner {
  public:
-  TestRunner(po::variables_map& vm, librados::Rados& rados);
+  TestRunner(boost::intrusive_ptr<CephContext> cct,
+             po::variables_map& vm,
+             librados::Rados& rados);
   ~TestRunner();
 
   bool run_test();