]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
RADOS: add neorados command line exerciser
authorAdam C. Emerson <aemerson@redhat.com>
Wed, 14 Aug 2019 00:38:16 +0000 (20:38 -0400)
committerAdam C. Emerson <aemerson@redhat.com>
Mon, 4 Nov 2019 20:51:38 +0000 (15:51 -0500)
Just a clean, small interface to help me debug and test out individual
function calls.

Signed-off-by: Adam C. Emerson <aemerson@redhat.com>
src/tools/CMakeLists.txt
src/tools/neorados.cc [new file with mode: 0644]

index f224bc7f46c6403db02194cc78d4fbacf048abac..d2d1c13b1f9ec7e982f7fbe6824a6c0881fb9aed 100644 (file)
@@ -16,6 +16,14 @@ else()
 endif()
 install(TARGETS rados DESTINATION bin)
 
+set(neorados_srcs
+  neorados.cc)
+add_executable(neorados ${neorados_srcs})
+
+target_link_libraries(neorados libRADOS global Boost::coroutine ${CMAKE_DL_LIBS})
+#install(TARGETS neorados DESTINATION bin)
+
+
 if(WITH_TESTS)
 add_executable(ceph_scratchtool scratchtool.c)
 target_link_libraries(ceph_scratchtool librados global)
diff --git a/src/tools/neorados.cc b/src/tools/neorados.cc
new file mode 100644 (file)
index 0000000..6bfd2fb
--- /dev/null
@@ -0,0 +1,368 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2019 Red Hat <contact@redhat.com>
+ * Author: Adam C. Emerson <aemerson@redhat.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation.  See file COPYING.
+ *
+ */
+
+#define BOOST_COROUTINES_NO_DEPRECATION_WARNING
+
+#include <algorithm>
+#include <cassert>
+#include <iostream>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <vector>
+
+#include <boost/asio.hpp>
+#include <boost/asio/spawn.hpp>
+#include <boost/io/ios_state.hpp>
+#include <boost/program_options.hpp>
+#include <boost/system/system_error.hpp>
+
+#include <fmt/format.h>
+#include <fmt/ostream.h>
+
+#include "include/buffer.h" // :(
+
+#include "include/RADOS/RADOS.hpp"
+
+using namespace std::literals;
+
+namespace ba = boost::asio;
+namespace bs = boost::system;
+namespace R = RADOS;
+
+std::string verstr(const std::tuple<uint32_t, uint32_t, uint32_t>& v) {
+  const auto [maj, min, p] = v;
+  return fmt::format("v{}.{}.{}", maj, min, p);
+}
+
+template<typename V>
+void printseq(const V& v, std::ostream& m) {
+  std::for_each(v.cbegin(), v.cend(),
+               [&m](const auto& e) {
+                 fmt::print(m, "{}\n", e);
+               });
+}
+
+template<typename V, typename F>
+void printseq(const V& v, std::ostream& m, F&& f) {
+  std::for_each(v.cbegin(), v.cend(),
+               [&m, &f](const auto& e) {
+                 fmt::print(m, "{}\n", f(e));
+               });
+}
+
+std::int64_t lookup_pool(R::RADOS& r, const std::string& pname,
+                        ba::yield_context y) {
+  bs::error_code ec;
+  auto p = r.lookup_pool(pname, y[ec]);
+  if (ec)
+    throw bs::system_error(
+      ec, fmt::format("when looking up '{}'", pname));
+  return p;
+}
+
+
+void lspools(R::RADOS& r, const std::vector<std::string>&,
+            ba::yield_context y) {
+  const auto l = r.list_pools(y);
+  printseq(l, std::cout, [](const auto& p) -> const std::string& {
+                          return p.second;
+                        });
+}
+
+
+void ls(R::RADOS& r, const std::vector<std::string>& p, ba::yield_context y) {
+  const auto& pname = p[0];
+  const auto pool = lookup_pool(r, pname, y);
+
+  std::vector<R::Entry> ls;
+  R::Cursor next = R::Cursor::begin();
+  bs::error_code ec;
+  do {
+    r.enumerate_objects(pool, next, R::Cursor::end(),
+                       1000, {}, &ls, &next, y[ec], R::all_nspaces);
+    if (ec)
+      throw bs::system_error(ec, fmt::format("when listing {}", pname));
+    printseq(ls, std::cout);
+    ls.clear();
+  } while (next != R::Cursor::end());
+}
+
+void mkpool(R::RADOS& r, const std::vector<std::string>& p,
+           ba::yield_context y) {
+  const auto& pname = p[0];
+  bs::error_code ec;
+  r.create_pool(pname, std::nullopt, y[ec]);
+  if (ec)
+    throw bs::system_error(ec, fmt::format("when creating pool '{}'", pname));
+}
+
+void rmpool(R::RADOS& r, const std::vector<std::string>& p,
+           ba::yield_context y) {
+  const auto& pname = p[0];
+  bs::error_code ec;
+  r.delete_pool(pname, y[ec]);
+  if (ec)
+    throw bs::system_error(ec, fmt::format("when removing pool '{}'", pname));
+}
+
+void create(R::RADOS& r, const std::vector<std::string>& p,
+           ba::yield_context y) {
+  const auto& pname = p[0];
+  const R::Object obj = p[1];
+  const auto pool = lookup_pool(r, pname, y);
+
+  bs::error_code ec;
+  R::WriteOp op;
+  op.create(true);
+  r.execute(obj, pool, std::move(op), y[ec]);
+  if (ec)
+    throw bs::system_error(ec,
+                          fmt::format(
+                            "when creating object '{}' in pool '{}'",
+                            obj, pname));
+}
+
+inline constexpr std::size_t io_size = 4 << 20;
+
+void write(R::RADOS& r, const std::vector<std::string>& p, ba::yield_context y) {
+  const auto& pname = p[0];
+  const R::Object obj(p[1]);
+  const auto pool = lookup_pool(r, pname, y);
+
+  bs::error_code ec;
+  std::unique_ptr<char[]> buf = std::make_unique<char[]>(io_size);
+  std::size_t off = 0;
+  boost::io::ios_exception_saver ies(std::cin);
+
+  cin.exceptions(std::istream::badbit);
+  std::cin.clear();
+
+  while (!std::cin.eof()) {
+    auto curoff = off;
+    std::cin.read(buf.get(), io_size);
+    auto len = std::cin.gcount();
+    off += len;
+    if (len == 0)
+      break; // Nothin' to do.
+
+    ceph::buffer::list bl;
+    bl.append(buffer::create_static(len, buf.get()));
+    R::WriteOp op;
+    op.write(curoff, std::move(bl));
+    r.execute(obj, pool, std::move(op), y[ec]);
+
+    if (ec)
+      throw bs::system_error(ec, fmt::format(
+                              "when writing object '{}' in pool '{}'",
+                              obj, pname));
+  }
+}
+
+void read(R::RADOS& r, const std::vector<std::string>& p, ba::yield_context y) {
+  const auto& pname = p[0];
+  const R::Object obj(p[1]);
+  const auto pool = lookup_pool(r, pname, y);
+
+  bs::error_code ec;
+  std::uint64_t len;
+  {
+    R::ReadOp op;
+    op.stat(&len, nullptr);
+    r.execute(obj, pool, std::move(op),
+             nullptr, y[ec]);
+    if (ec)
+      throw bs::system_error(
+       ec,
+       fmt::format("when getting length of object '{}' in pool '{}'",
+                   obj, pname));
+  }
+
+  std::size_t off = 0;
+  ceph::buffer::list bl;
+  while (auto toread = std::max(len - off, io_size)) {
+    R::ReadOp op;
+    op.read(off, toread, &bl);
+    r.execute(obj, pool, std::move(op), nullptr, y[ec]);
+    if (ec)
+      throw bs::system_error(
+       ec,
+       fmt::format("when reading from object '{}' in pool '{}'",
+                   obj, pool));
+
+    off += bl.length();
+    bl.write_stream(std::cout);
+    bl.clear();
+  }
+}
+
+void rm(R::RADOS& r, const std::vector<std::string>& p, ba::yield_context y) {
+  const auto& pname = p[0];
+  const R::Object obj = p[1];
+  const auto pool = lookup_pool(r, pname, y);
+
+  bs::error_code ec;
+  R::WriteOp op;
+  op.remove();
+  r.execute(obj, pool, std::move(op), y[ec]);
+  if (ec)
+    throw bs::system_error(ec, fmt::format(
+                            "when removing object '{}' in pool '{}'",
+                            obj, pname));
+}
+
+static constexpr auto version = std::make_tuple(0ul, 0ul, 1ul);
+
+using cmdfunc = void (*)(R::RADOS& r, const std::vector<std::string>& p,
+                        ba::yield_context);
+
+struct cmdesc {
+  std::string_view name;
+  std::size_t arity;
+  cmdfunc f;
+  std::string_view usage;
+  std::string_view desc;
+};
+
+const std::array commands = {
+  // Pools operations ;)
+
+  cmdesc{ "lspools"sv,
+         0, &lspools,
+         ""sv,
+         "List all pools"sv },
+
+  // Pool operations
+
+  cmdesc{ "ls"sv,
+         1, &ls,
+         "POOL"sv,
+         "list all objects in POOL"sv },
+  cmdesc{ "mkpool"sv,
+         1, &mkpool,
+         "POOL"sv,
+         "create POOL"sv },
+  cmdesc{ "rmpool"sv,
+         1, &rmpool,
+         "POOL"sv,
+         "remove POOL"sv },
+
+  // Object operations
+
+  cmdesc{ "create"sv,
+         2, &create,
+         "POOL OBJECT"sv,
+         "exclusively create OBJECT in POOL"sv },
+  cmdesc{ "write"sv,
+         2, &write,
+         "POOL OBJECT"sv,
+         "write to OBJECT in POOL from standard input"sv },
+  cmdesc{ "read"sv,
+         2, &read,
+         "POOL OBJECT"sv,
+         "read contents of OBJECT in POOL to standard out"sv },
+  cmdesc{ "rm"sv,
+         2, &rm,
+         "POOL OBJECT"sv,
+         "remove OBJECT in POOL"sv }
+};
+
+int main(int argc, char* argv[])
+{
+  const std::string_view prog(argv[0]);
+  std::string command;
+  namespace po = boost::program_options;
+  try {
+    std::vector<std::string> parameters;
+
+    po::options_description desc(fmt::format("{} options", prog));
+    desc.add_options()
+      ("help", "show help")
+      ("version", "show version")
+      ("command", po::value<std::string>(&command), "the operation to perform")
+      ("parameters", po::value<std::vector<std::string>>(&parameters),
+       "parameters to the command");
+
+    po::positional_options_description p;
+    p.add("command", 1);
+    p.add("parameters", -1);
+
+    po::variables_map vm;
+
+    po::store(po::command_line_parser(argc, argv).
+             options(desc).positional(p).run(), vm);
+
+    po::notify(vm);
+
+    if (vm.count("help")) {
+      fmt::print("{}", desc);
+      fmt::print("\nCommands:\n");
+      for (const auto& cmd : commands) {
+       fmt::print("\t{} {}\n\t\t{}\n",
+                  cmd.name, cmd.usage, cmd.desc);
+      }
+      return 0;
+    }
+
+    if (vm.count("version")) {
+      fmt::print(
+       "{}: RADOS command exerciser, {},\n"
+       "RADOS library version {}\n"
+       "Copyright (C) 2019 Red Hat <contact@redhat.com>\n"
+       "This is free software; you can redistribute it and/or\n"
+       "modify it under the terms of the GNU Lesser General Public\n"
+       "License version 2.1, as published by the Free Software\n"
+       "Foundation.  See file COPYING.\n", prog,
+       verstr(version), verstr(R::RADOS::version()));
+      return 0;
+    }
+
+    if (vm.find("command") == vm.end()) {
+      fmt::print(std::cerr, "{}: A command is required\n", prog);
+      return 1;
+    }
+
+    ba::io_context c;
+
+    if (auto ci = std::find_if(commands.begin(), commands.end(),
+                              [&command](const cmdesc& c) {
+                                return c.name == command;
+                              }); ci != commands.end()) {
+      if (parameters.size() < ci->arity) {
+       fmt::print(std::cerr, "{}: {}: too few arguments\n\t{} {}\n",
+                  prog, command, ci->name, ci->usage);
+       return 1;
+      }
+      if (parameters.size() > ci->arity) {
+       fmt::print(std::cerr, "{}: {}: too many arguments\n\t{} {}\n",
+                  prog, command, ci->name, ci->usage);
+       return 1;
+      }
+      ba::spawn(c, [&](ba::yield_context y) {
+                    auto r = R::RADOS::Builder{}.build(c, y);
+                    ci->f(r, parameters, y);
+                  });
+    } else {
+      fmt::print(std::cerr, "{}: {}: unknown command\n", prog, command);
+      return 1;
+    }
+    c.run();
+  } catch (const std::exception& e) {
+    fmt::print(std::cerr, "{}: {}: {}\n", prog, command, e.what());
+    return 1;
+  }
+
+  return 0;
+}