]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
src/test/osd: Split reader classes for program options into a seperate class
authorJon Bailey <jonathan.bailey1@ibm.com>
Fri, 10 Jan 2025 13:24:04 +0000 (13:24 +0000)
committerJon Bailey <jonathan.bailey1@ibm.com>
Wed, 5 Mar 2025 16:15:01 +0000 (16:15 +0000)
Split the program option readers out into a seperate class from the main logic controlling the operationg of ceph_test_rados_io_sequence

Signed-off-by: Jon Bailey <jonathan.bailey1@ibm.com>
src/test/osd/ceph_test_rados_io_sequence/ProgramOptionReader.h [new file with mode: 0644]
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

diff --git a/src/test/osd/ceph_test_rados_io_sequence/ProgramOptionReader.h b/src/test/osd/ceph_test_rados_io_sequence/ProgramOptionReader.h
new file mode 100644 (file)
index 0000000..e226203
--- /dev/null
@@ -0,0 +1,99 @@
+#pragma once
+
+#include <optional>
+#include <string>
+
+#include <boost/program_options.hpp>
+
+#include "include/ceph_assert.h"
+#include "include/random.h"
+
+/* Overview
+ *
+ * class ProgramOptionReader
+ *    Base class that is constructed using a variable_map from
+ *    boost::program_options and the name of an option.
+ *    This class extracts the named option from the variable map into the
+ *    force_value variable.
+ *    It is necissairy to override this class and implement the pure virtual
+ *    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.
+ *
+ * ProgramOptionSelector
+ *    Implementation of the above ProgramOptionReader.
+ *    This is constructed with a list of options and implements the select
+ *    function to return randomly from this list when called and no argument is
+ *    given in the variables_map.
+ *    The first time this is called, we will always return the first item of the
+ *    list when select_first is true and no value is found in the variables_map.
+ *    This takes an random_number_generator so that randomness can be controlled
+ *    and reproduced if desired.
+ *
+ */
+
+namespace po = boost::program_options;
+
+namespace ceph {
+namespace io_sequence {
+namespace tester {
+template <typename option_type,
+          typename return_type = option_type>
+class ProgramOptionReader {
+ public:
+  ProgramOptionReader(po::variables_map& vm,
+                      const std::string& option_name)
+      : option_name(option_name) {
+    if (vm.count(option_name) > 0) {
+      force_value = vm[option_name].as<option_type>();
+    }
+  }
+
+  virtual ~ProgramOptionReader() = default;
+
+  bool isForced() { return force_value.has_value(); }
+
+  virtual const return_type select() = 0;
+
+ protected:
+  std::optional<option_type> force_value;
+
+  std::string option_name;
+};
+
+template <typename option_type,
+          int num_selections,
+          const std::array< option_type,
+                            num_selections>& selections_array>
+class ProgramOptionSelector : public ProgramOptionReader<option_type> {
+ public:
+  ProgramOptionSelector(ceph::util::random_number_generator<int>& rng,
+                        po::variables_map& vm,
+                        const std::string& option_name,
+                        bool select_first)
+      : ProgramOptionReader<option_type>(vm, option_name), rng(rng) {
+    if (select_first) {
+      ceph_assert(selections_array.size() > 0);
+      first_value = selections_array[0];
+    }
+  }
+
+  virtual ~ProgramOptionSelector() = default;
+
+  virtual const option_type select() override {
+    if (this->force_value.has_value()) {
+      return *this->force_value;
+    } else if (first_value.has_value()) {
+      return *std::exchange(first_value, std::nullopt);
+    } else {
+      return selections_array[rng(num_selections - 1)];
+    }
+  }
+
+ protected:
+  ceph::util::random_number_generator<int>& rng;
+
+  std::optional<option_type> first_value;
+};
+}  // namespace io_sequence
+}  // namespace tester
+}  // namespace ceph
\ No newline at end of file
index 3212e399cca8b3b4269f2f3ad0a2da20597782cf..7f7a10f649e468056d6d8a8b35528b9b45f405a7 100644 (file)
@@ -219,7 +219,8 @@ int parse_io_seq_options(po::variables_map& vm, int argc, char** argv) {
 
 template <typename S>
 int send_mon_command(S& s, librados::Rados& rados, const char* name,
-                     ceph::buffer::list& inbl, ceph::buffer::list* outbl, Formatter* f) {
+                     ceph::buffer::list& inbl, ceph::buffer::list* outbl,
+                     Formatter* f) {
   std::ostringstream oss;
   encode_json(name, s, f);
   f->flush(oss);
@@ -229,52 +230,10 @@ int send_mon_command(S& s, librados::Rados& rados, const char* name,
 
 }  // namespace
 
-template <typename T, int N, const std::array<T, N>& Ts>
-ceph::io_sequence::tester::ProgramOptionSelector<T, N, Ts>::
-    ProgramOptionSelector(ceph::util::random_number_generator<int>& rng,
-                          po::variables_map vm, const std::string& option_name,
-                          bool set_forced, bool select_first)
-    : rng(rng), option_name(option_name) {
-  if (set_forced && vm.count(option_name)) {
-    force_value = vm[option_name].as<T>();
-  }
-  if (select_first) {
-    ceph_assert(choices.size() > 0);
-    first_value = choices[0];
-  }
-}
-
-template <typename T, int N, const std::array<T, N>& Ts>
-bool ceph::io_sequence::tester::ProgramOptionSelector<T, N, Ts>::isForced() {
-  return force_value.has_value();
-}
-
-template <typename T, int N, const std::array<T, N>& Ts>
-const T ceph::io_sequence::tester::ProgramOptionSelector<T, N, Ts>::choose() {
-  if (force_value.has_value()) {
-    return *force_value;
-  } else if (first_value.has_value()) {
-    return *std::exchange(first_value, std::nullopt);
-  } else {
-    return choices[rng(N - 1)];
-  }
-}
-
-ceph::io_sequence::tester::SelectObjectSize::SelectObjectSize(
-    ceph::util::random_number_generator<int>& rng, po::variables_map vm)
-    : ProgramOptionSelector(rng, vm, "objectsize", true, true) {}
-
-ceph::io_sequence::tester::SelectBlockSize::SelectBlockSize(
-    ceph::util::random_number_generator<int>& rng, po::variables_map vm)
-    : ProgramOptionSelector(rng, vm, "blocksize", true, true) {}
-
-ceph::io_sequence::tester::SelectNumThreads::SelectNumThreads(
-    ceph::util::random_number_generator<int>& rng, po::variables_map vm)
-    : ProgramOptionSelector(rng, vm, "threads", true, true) {}
-
-ceph::io_sequence::tester::SelectSeqRange::SelectSeqRange(
-    ceph::util::random_number_generator<int>& rng, po::variables_map vm)
-    : ProgramOptionSelector(rng, vm, "sequence", false, false) {
+ceph::io_sequence::tester::SelectSeqRange::SelectSeqRange(po::variables_map& vm)
+    : ProgramOptionReader<std::pair<ceph::io_exerciser::Sequence,
+                                    ceph::io_exerciser::Sequence>>(vm,
+                                                                   "sequence") {
   if (vm.count(option_name)) {
     ceph::io_exerciser::Sequence s =
         static_cast<ceph::io_exerciser::Sequence>(vm["sequence"].as<int>());
@@ -291,7 +250,7 @@ ceph::io_sequence::tester::SelectSeqRange::SelectSeqRange(
 }
 
 const std::pair<ceph::io_exerciser::Sequence, ceph::io_exerciser::Sequence>
-ceph::io_sequence::tester::SelectSeqRange::choose() {
+ceph::io_sequence::tester::SelectSeqRange::select() {
   if (force_value.has_value()) {
     return *force_value;
   } else {
@@ -300,24 +259,12 @@ ceph::io_sequence::tester::SelectSeqRange::choose() {
   }
 }
 
-ceph::io_sequence::tester::SelectErasureKM::SelectErasureKM(
-    ceph::util::random_number_generator<int>& rng, po::variables_map vm)
-    : ProgramOptionSelector(rng, vm, "km", true, true) {}
-
-ceph::io_sequence::tester::SelectErasurePlugin::SelectErasurePlugin(
-    ceph::util::random_number_generator<int>& rng, po::variables_map vm)
-    : ProgramOptionSelector(rng, vm, "plugin", true, false) {}
-
-ceph::io_sequence::tester::SelectErasureChunkSize::SelectErasureChunkSize(
-    ceph::util::random_number_generator<int>& rng, po::variables_map vm)
-    : ProgramOptionSelector(rng, vm, "chunksize", true, true) {}
-
-ceph::io_sequence::tester::SelectECPool::SelectECPool(
+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)
-    : ProgramOptionSelector(rng, vm, "pool", false, false),
+    : ProgramOptionReader<std::string>(vm, "pool"),
       rados(rados),
       dry_run(dry_run),
       allow_pool_autoscaling(allow_pool_autoscaling),
@@ -325,9 +272,9 @@ ceph::io_sequence::tester::SelectECPool::SelectECPool(
       allow_pool_deep_scrubbing(allow_pool_deep_scrubbing),
       allow_pool_scrubbing(allow_pool_scrubbing),
       test_recovery(test_recovery),
-      skm(SelectErasureKM(rng, vm)),
-      spl(SelectErasurePlugin(rng, vm)),
-      scs(SelectErasureChunkSize(rng, vm)) {
+      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>();
@@ -335,7 +282,7 @@ ceph::io_sequence::tester::SelectECPool::SelectECPool(
   }
 }
 
-const std::string ceph::io_sequence::tester::SelectECPool::choose() {
+const std::string ceph::io_sequence::tester::SelectErasurePool::select() {
   std::pair<int, int> value;
   if (!skm.isForced() && force_value.has_value()) {
     int rc;
@@ -370,13 +317,13 @@ const std::string ceph::io_sequence::tester::SelectECPool::choose() {
     m = reply.m;
     return *force_value;
   } else {
-    value = skm.choose();
+    value = skm.select();
   }
   k = value.first;
   m = value.second;
 
-  const std::string plugin = std::string(spl.choose());
-  const uint64_t chunk_size = scs.choose();
+  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);
@@ -386,7 +333,7 @@ const std::string ceph::io_sequence::tester::SelectECPool::choose() {
   return pool_name;
 }
 
-void ceph::io_sequence::tester::SelectECPool::create_pool(
+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) {
   int rc;
@@ -456,22 +403,20 @@ void ceph::io_sequence::tester::SelectECPool::create_pool(
     ceph::messaging::config::ConfigSetRequest configSetBluestoreDebugRequest{
         "global", "bluestore_debug_inject_read_err", "true", std::nullopt};
     rc = send_mon_command(configSetBluestoreDebugRequest, rados,
-                          "ConfigSetRequest", inbl, &outbl,
-                          formatter.get());
+                          "ConfigSetRequest", inbl, &outbl, formatter.get());
     ceph_assert(rc == 0);
 
     ceph::messaging::config::ConfigSetRequest configSetMaxMarkdownRequest{
         "global", "osd_max_markdown_count", "99999999", std::nullopt};
-    rc =
-        send_mon_command(configSetMaxMarkdownRequest, rados, "ConfigSetRequest",
-                         inbl, &outbl, formatter.get());
+    rc = send_mon_command(configSetMaxMarkdownRequest, rados,
+                          "ConfigSetRequest", inbl, &outbl, formatter.get());
     ceph_assert(rc == 0);
   }
 }
 
 ceph::io_sequence::tester::TestObject::TestObject(
     const std::string oid, librados::Rados& rados,
-    boost::asio::io_context& asio, SelectBlockSize& sbs, SelectECPool& spo,
+    boost::asio::io_context& asio, SelectBlockSize& sbs, SelectErasurePool& spo,
     SelectObjectSize& sos, SelectNumThreads& snt, SelectSeqRange& ssr,
     ceph::util::random_number_generator<int>& rng, ceph::mutex& lock,
     ceph::condition_variable& cond, bool dryrun, bool verbose,
@@ -479,13 +424,13 @@ ceph::io_sequence::tester::TestObject::TestObject(
     : rng(rng), verbose(verbose), seqseed(seqseed), testrecovery(testrecovery) {
   if (dryrun) {
     exerciser_model = std::make_unique<ceph::io_exerciser::ObjectModel>(
-        oid, sbs.choose(), rng());
+        oid, sbs.select(), rng());
   } else {
-    const std::string pool = spo.choose();
+    const std::string pool = spo.select();
     poolK = spo.getChosenK();
     poolM = spo.getChosenM();
 
-    int threads = snt.choose();
+    int threads = snt.select();
 
     bufferlist inbl, outbl;
     auto formatter = std::make_unique<JSONFormatter>(false);
@@ -510,14 +455,14 @@ ceph::io_sequence::tester::TestObject::TestObject(
     }
 
     exerciser_model = std::make_unique<ceph::io_exerciser::RadosIo>(
-        rados, asio, pool, oid, cached_shard_order, sbs.choose(), rng(),
+        rados, asio, pool, oid, cached_shard_order, sbs.select(), rng(),
         threads, lock, cond);
     dout(0) << "= " << oid << " pool=" << pool << " threads=" << threads
             << " blocksize=" << exerciser_model->get_block_size() << " ="
             << dendl;
   }
-  obj_size_range = sos.choose();
-  seq_range = ssr.choose();
+  obj_size_range = sos.select();
+  seq_range = ssr.select();
   curseq = seq_range.first;
 
   if (testrecovery) {
@@ -588,8 +533,8 @@ ceph::io_sequence::tester::TestRunner::TestRunner(po::variables_map& vm,
     : rados(rados),
       seed(vm.contains("seed") ? vm["seed"].as<int>() : time(nullptr)),
       rng(ceph::util::random_number_generator<int>(seed)),
-      sbs{rng, vm},
-      sos{rng, vm},
+      sbs{rng, vm, "blocksize", true},
+      sos{rng, vm, "objectsize", true},
       spo{rng,
           vm,
           rados,
@@ -599,8 +544,8 @@ ceph::io_sequence::tester::TestRunner::TestRunner(po::variables_map& vm,
           vm.contains("allow_pool_deep_scrubbing"),
           vm.contains("allow_pool_scrubbing"),
           vm.contains("test_recovery")},
-      snt{rng, vm},
-      ssr{rng, vm} {
+      snt{rng, vm, "threads", true},
+      ssr{vm} {
   dout(0) << "Test using seed " << seed << dendl;
 
   verbose = vm.contains("verbose");
@@ -647,7 +592,7 @@ void ceph::io_sequence::tester::TestRunner::help() {
 
 void ceph::io_sequence::tester::TestRunner::list_sequence(bool testrecovery) {
   // List seqeunces
-  std::pair<int, int> obj_size_range = sos.choose();
+  std::pair<int, int> obj_size_range = sos.select();
   ceph::io_exerciser::Sequence s = ceph::io_exerciser::Sequence::SEQUENCE_BEGIN;
   std::unique_ptr<ceph::io_exerciser::IoSequence> seq;
   if (testrecovery) {
@@ -742,9 +687,9 @@ bool ceph::io_sequence::tester::TestRunner::run_interactive_test() {
 
   if (dryrun) {
     model = std::make_unique<ceph::io_exerciser::ObjectModel>(
-        object_name, sbs.choose(), rng());
+        object_name, sbs.select(), rng());
   } else {
-    const std::string pool = spo.choose();
+    const std::string pool = spo.select();
 
     bufferlist inbl, outbl;
     auto formatter = std::make_unique<JSONFormatter>(false);
@@ -762,7 +707,7 @@ bool ceph::io_sequence::tester::TestRunner::run_interactive_test() {
     reply.decode_json(&p);
 
     model = std::make_unique<ceph::io_exerciser::RadosIo>(
-        rados, asio, pool, object_name, reply.acting, sbs.choose(), rng(),
+        rados, asio, pool, object_name, reply.acting, sbs.select(), rng(),
         1,  // 1 thread
         lock, cond);
   }
index 98ebb58985ec0fd51aad818fc792b1a71bcbaaf2..40826f0d85948fbb3aa9e0865070e94bb9aa4eeb 100644 (file)
@@ -2,6 +2,7 @@
 #include <optional>
 #include <utility>
 
+#include "ProgramOptionReader.h"
 #include "common/io_exerciser/IoOp.h"
 #include "common/io_exerciser/IoSequence.h"
 #include "common/io_exerciser/Model.h"
 #include <optional>
 
 /* Overview
- *
- * class ProgramOptionSelector
- *   Base class for selector objects below with common code for
- *   selecting options
  *
  * class SelectObjectSize
  *   Selects min and max object sizes for a test
  *
+ * class SelectBlockSize
+ *   Selects a block size for a test
+ *
+ * class SelectNumThreads
+ *   Selects number of threads for a test
+ *
  * class SelectErasureKM
  *   Selects an EC k and m value for a test
  *
+ * class SelectErasureChunkSize
+ *   Selects a chunk size/stripe unit for the test
+ *
  * class SelectErasurePlugin
  *   Selects an plugin for a test
  *
- * class SelectECPool
+ * class SelectErasurePool
  *   Selects an EC pool (plugin,k and m) for a test. Also creates the
  *   pool as well.
  *
- * class SelectBlockSize
- *   Selects a block size for a test
- *
- * class SelectNumThreads
- *   Selects number of threads for a test
- *
  * class SelectSeqRange
  *   Selects a sequence or range of sequences for a test
  *
+ * class SelectErasurePool
+ *   Selects a pool name for a test
+ *
  * class TestObject
  *   Runs a test against an object, generating IOSequence
  *   and applying them to an IoExerciser
  *
+ * class TestRunner
+ *   Determines test type to run, creates and orchestrates automated and
+ *   interactive tests as well as creating a test object for each automated test
+ *   we want to run in parallel
+ *
  * main
  *   Run sequences of I/O with data integrity checking to
  *   one or more objects in parallel. Without arguments
 namespace po = boost::program_options;
 
 namespace ceph {
-namespace io_sequence::tester {
+namespace io_sequence {
+namespace tester {
 // Choices for min and max object size
-inline constexpr size_t objectSizeSize = 10;
-inline constexpr std::array<std::pair<int, int>, objectSizeSize>
+// 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
                           {12, 14},
                           {28, 30},
@@ -76,18 +86,33 @@ inline constexpr std::array<std::pair<int, int>, objectSizeSize>
                           {83, 83},
                           {97, 97}}};
 
+using SelectObjectSize =
+    ProgramOptionSelector<std::pair<int, int>,
+                          io_sequence::tester ::objectSizeSize,
+                          io_sequence::tester ::objectSizeChoices>;
+
 // Choices for block size
-inline constexpr int blockSizeSize = 5;
-inline constexpr std::array<uint64_t, blockSizeSize> blockSizeChoices = {
+inline static constexpr int blockSizeSize = 5;
+inline static constexpr std::array<uint64_t, blockSizeSize> blockSizeChoices = {
     {2048,  // Default - test boundaries for EC 4K chunk size
      512, 3767, 4096, 32768}};
 
+using SelectBlockSize =
+    ProgramOptionSelector<uint64_t,
+                          io_sequence::tester ::blockSizeSize,
+                          io_sequence::tester ::blockSizeChoices>;
+
 // Choices for number of threads
-inline constexpr int threadArraySize = 4;
-inline constexpr std::array<int, threadArraySize> threadCountChoices = {
+inline static constexpr int threadArraySize = 4;
+inline static constexpr std::array<int, threadArraySize> threadCountChoices = {
     {1,  // Default
      2, 4, 8}};
 
+using SelectNumThreads =
+    ProgramOptionSelector<int,
+                          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 = {
@@ -98,115 +123,52 @@ inline constexpr std::array<std::pair<int, int>, kmSize> kmChoices = {
      {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 constexpr int chunkSizeSize = 3;
-inline constexpr std::array<uint64_t, chunkSizeSize> chunkSizeChoices = {
+inline static constexpr int chunkSizeSize = 3;
+inline static constexpr std::array<uint64_t, chunkSizeSize> chunkSizeChoices = {
     {4 * 1024, 64 * 1024, 256 * 1024}};
 
-// Choices for plugin
-inline constexpr int pluginListSize = 2;
-inline constexpr std::array<std::string_view, pluginListSize> pluginChoices = {
-    {"jerasure", "isa"}};
-
-inline constexpr std::array<
-    std::pair<ceph::io_exerciser::Sequence, ceph::io_exerciser::Sequence>, 0>
-    sequencePairs = {{}};
-
-inline constexpr std::array<std::string, 0> poolChoices = {{}};
-
-template <typename T, int N, const std::array<T, N>& Ts>
-class ProgramOptionSelector {
- public:
-  ProgramOptionSelector(ceph::util::random_number_generator<int>& rng,
-                        po::variables_map vm, const std::string& option_name,
-                        bool set_forced, bool select_first);
-  virtual ~ProgramOptionSelector() = default;
-  bool isForced();
-  virtual const T choose();
-
- protected:
-  ceph::util::random_number_generator<int>& rng;
-  static constexpr std::array<T, N> choices = Ts;
-
-  std::optional<T> force_value;
-  std::optional<T> first_value;
-
-  std::string option_name;
-};
+using SelectErasureChunkSize =
+    ProgramOptionSelector<uint64_t,
+                          io_sequence::tester ::chunkSizeSize,
+                          io_sequence::tester::chunkSizeChoices>;
 
-class SelectObjectSize
-    : public ProgramOptionSelector<std::pair<int, int>,
-                                   io_sequence::tester::objectSizeSize,
-                                   io_sequence::tester::objectSizeChoices> {
- public:
-  SelectObjectSize(ceph::util::random_number_generator<int>& rng,
-                   po::variables_map vm);
-};
+// Choices for plugin
+inline static constexpr int pluginListSize = 2;
+inline static constexpr std::array<std::string_view, pluginListSize>
+    pluginChoices = {{"jerasure", "isa"}};
 
-class SelectBlockSize
-    : public ProgramOptionSelector<uint64_t, io_sequence::tester::blockSizeSize,
-                                   io_sequence::tester::blockSizeChoices> {
- public:
-  SelectBlockSize(ceph::util::random_number_generator<int>& rng,
-                  po::variables_map vm);
-};
-
-class SelectNumThreads
-    : public ProgramOptionSelector<int, io_sequence::tester::threadArraySize,
-                                   io_sequence::tester::threadCountChoices> {
- public:
-  SelectNumThreads(ceph::util::random_number_generator<int>& rng,
-                   po::variables_map vm);
-};
+using SelectErasurePlugin =
+    ProgramOptionSelector<std::string_view,
+                          io_sequence::tester ::pluginListSize,
+                          io_sequence::tester ::pluginChoices>;
 
 class SelectSeqRange
-    : public ProgramOptionSelector<
-          std::pair<ceph::io_exerciser::Sequence, ceph::io_exerciser::Sequence>,
-          0, io_sequence::tester::sequencePairs> {
+    : public ProgramOptionReader<std::pair<ceph::io_exerciser ::Sequence,
+                                           ceph::io_exerciser ::Sequence>> {
  public:
-  SelectSeqRange(ceph::util::random_number_generator<int>& rng,
-                 po::variables_map vm);
-
+  SelectSeqRange(po::variables_map& vm);
   const std::pair<ceph::io_exerciser::Sequence, ceph::io_exerciser::Sequence>
-  choose() override;
-};
-
-class SelectErasureKM
-    : public ProgramOptionSelector<std::pair<int, int>,
-                                   io_sequence::tester::kmSize,
-                                   io_sequence::tester::kmChoices> {
- public:
-  SelectErasureKM(ceph::util::random_number_generator<int>& rng,
-                  po::variables_map vm);
-};
-
-class SelectErasurePlugin
-    : public ProgramOptionSelector<std::string_view,
-                                   io_sequence::tester::pluginListSize,
-                                   io_sequence::tester::pluginChoices> {
- public:
-  SelectErasurePlugin(ceph::util::random_number_generator<int>& rng,
-                      po::variables_map vm);
-};
-
-class SelectErasureChunkSize
-    : public ProgramOptionSelector<uint64_t, io_sequence::tester::chunkSizeSize,
-                                   io_sequence::tester::chunkSizeChoices> {
- public:
-  SelectErasureChunkSize(ceph::util::random_number_generator<int>& rng,
-                         po::variables_map vm);
+  select() override;
 };
 
-class SelectECPool
-    : public ProgramOptionSelector<std::string, 0,
-                                   io_sequence::tester::poolChoices> {
+class SelectErasurePool : public ProgramOptionReader<std::string> {
  public:
-  SelectECPool(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);
-  const std::string choose() override;
+  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);
+  const std::string select() override;
 
   bool get_allow_pool_autoscaling() { return allow_pool_autoscaling; }
   bool get_allow_pool_balancer() { return allow_pool_balancer; }
@@ -216,9 +178,11 @@ class SelectECPool
   int getChosenM() const { return m; }
 
  private:
-  void create_pool(librados::Rados& rados, const std::string& pool_name,
-                   const std::string& plugin, uint64_t chunk_size, int k,
-                   int m);
+  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;
@@ -238,16 +202,21 @@ class SelectECPool
 
 class TestObject {
  public:
-  TestObject(const std::string oid, librados::Rados& rados,
+  TestObject(const std::string oid,
+             librados::Rados& rados,
              boost::asio::io_context& asio,
              ceph::io_sequence::tester::SelectBlockSize& sbs,
-             ceph::io_sequence::tester::SelectECPool& spl,
+             ceph::io_sequence::tester::SelectErasurePool& spl,
              ceph::io_sequence::tester::SelectObjectSize& sos,
              ceph::io_sequence::tester::SelectNumThreads& snt,
              ceph::io_sequence::tester::SelectSeqRange& ssr,
-             ceph::util::random_number_generator<int>& rng, ceph::mutex& lock,
-             ceph::condition_variable& cond, bool dryrun, bool verbose,
-             std::optional<int> seqseed, bool testRecovery);
+             ceph::util::random_number_generator<int>& rng,
+             ceph::mutex& lock,
+             ceph::condition_variable& cond,
+             bool dryrun,
+             bool verbose,
+             std::optional<int> seqseed,
+             bool testRecovery);
 
   int get_num_io();
   bool readyForIo();
@@ -285,7 +254,7 @@ class TestRunner {
 
   ceph::io_sequence::tester::SelectBlockSize sbs;
   ceph::io_sequence::tester::SelectObjectSize sos;
-  ceph::io_sequence::tester::SelectECPool spo;
+  ceph::io_sequence::tester::SelectErasurePool spo;
   ceph::io_sequence::tester::SelectNumThreads snt;
   ceph::io_sequence::tester::SelectSeqRange ssr;
 
@@ -334,5 +303,6 @@ class TestRunner {
   void help();
   void list_sequence(bool testrecovery);
 };
-}  // namespace io_sequence::tester
+}  // namespace tester
+}  // namespace io_sequence
 }  // namespace ceph