]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
tests: Add integration tests for admin socket output 15223/head
authorBrad Hubbard <bhubbard@redhat.com>
Tue, 18 Apr 2017 00:13:33 +0000 (10:13 +1000)
committerBrad Hubbard <bhubbard@redhat.com>
Wed, 7 Jun 2017 06:48:02 +0000 (16:48 +1000)
Add a framework to test all admin socket commands as reported by "help"
as well as custom commands.

Signed-off-by: Brad Hubbard <bhubbard@redhat.com>
CMakeLists.txt
qa/suites/rados/singleton-nomsgr/all/admin_socket_output.yaml [new file with mode: 0644]
src/test/CMakeLists.txt
src/test/admin_socket_output.cc [new file with mode: 0644]
src/test/admin_socket_output.h [new file with mode: 0644]
src/test/admin_socket_output_tests.cc [new file with mode: 0644]
src/test/admin_socket_output_tests.h [new file with mode: 0644]
src/test/test_admin_socket_output.cc [new file with mode: 0644]

index a5efb6d1d5a52a13790c7eae67b464f75fa56806..f72e01eccd57c3314492296482d586b71cb1d1d6 100644 (file)
@@ -516,7 +516,8 @@ option(WITH_SYSTEM_BOOST "require and build with system Boost" OFF)
 
 # Boost::thread depends on Boost::atomic, so list it explicitly.
 set(BOOST_COMPONENTS
-  atomic thread system regex random program_options date_time iostreams)
+  atomic thread system regex random program_options date_time iostreams
+  filesystem)
 set(BOOST_HEADER_COMPONENTS container)
 
 if(WITH_MGR)
diff --git a/qa/suites/rados/singleton-nomsgr/all/admin_socket_output.yaml b/qa/suites/rados/singleton-nomsgr/all/admin_socket_output.yaml
new file mode 100644 (file)
index 0000000..969c409
--- /dev/null
@@ -0,0 +1,15 @@
+roles:
+- [mon.a, mds.a, mgr.x, osd.0, osd.1, client.0]
+overrides:
+  ceph:
+    log-whitelist:
+      - MDS in read-only mode
+      - force file system read-only
+tasks:
+- install:
+- ceph:
+- rgw:
+  - client.0
+- exec:
+    client.0:
+      - ceph_test_admin_socket_output --all
index 9b62b29ce2ffd47221a3f745f07ae62273d133fc..db42747bd6e7753d6fd53f0dbf6f8007f6b061e5 100644 (file)
@@ -504,6 +504,21 @@ if(HAVE_BLKID)
     )
 endif(HAVE_BLKID)
 
+# ceph_test_admin_socket_output
+
+add_executable(ceph_test_admin_socket_output
+  test_admin_socket_output.cc
+  admin_socket_output.cc
+  admin_socket_output_tests.cc
+  )
+target_link_libraries(ceph_test_admin_socket_output
+  ceph-common
+  ${Boost_FILESYSTEM_LIBRARY}
+  )
+install(TARGETS
+  ceph_test_admin_socket_output
+  DESTINATION ${CMAKE_INSTALL_BINDIR})
+
 #make check starts here
 
 #following dependencies are run inside make check unit tests
diff --git a/src/test/admin_socket_output.cc b/src/test/admin_socket_output.cc
new file mode 100644 (file)
index 0000000..50fa966
--- /dev/null
@@ -0,0 +1,227 @@
+// -*- 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) 2017 Red Hat
+ *
+ * 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.
+ *
+ */
+
+#include <iostream>
+#include <boost/filesystem/convenience.hpp> // For extension
+#include <boost/regex.hpp>                 // For regex, regex_search
+
+#include "common/admin_socket_client.h"     // For AdminSocketClient
+#include "common/ceph_json.h"               // For JSONParser, JSONObjIter
+#include "include/buffer.h"                 // For bufferlist
+
+#include "admin_socket_output.h"
+
+void AdminSocketOutput::add_target(const std::string& target) {
+  if (target == "all") {
+    add_target("osd");
+    add_target("mon");
+    add_target("mgr");
+    add_target("mds");
+    add_target("client");
+    return;
+  }
+  targets.insert(target);
+}
+
+void AdminSocketOutput::add_command(const std::string& target,
+                                    const std::string& command) {
+  auto seek = custom_commands.find(target);
+  if (seek != custom_commands.end()) {
+    seek->second.push_back(command);
+  } else {
+    std::vector<std::string> vec;
+    vec.push_back(command);
+    custom_commands.insert(std::make_pair(target, vec));
+  }
+
+}
+
+void AdminSocketOutput::add_test(const std::string &target,
+                                 const std::string &command,
+                                 bool (*test)(std::string &)) {
+  auto seek = tests.find(target);
+  if (seek != tests.end()) {
+    seek->second.push_back(std::make_pair(command, test));
+  } else {
+    std::vector<std::pair<std::string, bool (*)(std::string &)>> vec;
+    vec.push_back(std::make_pair(command, test));
+    tests.insert(std::make_pair(target, vec));
+  }
+}
+
+void AdminSocketOutput::postpone(const std::string &target,
+                                 const std::string& command) {
+  auto seek = postponed_commands.find(target);
+  if (seek != postponed_commands.end()) {
+    seek->second.push_back(command);
+  } else {
+    std::vector<string> vec;
+    vec.push_back(command);
+    postponed_commands.insert(std::make_pair(target, vec));
+  }
+}
+
+bool AdminSocketOutput::init_sockets() {
+  std::cout << "Initialising sockets" << std::endl;
+  for (const auto &x : bfs::directory_iterator(socketdir)) {
+    std::cout << x.path() << std::endl;
+    if (bfs::extension(x.path()) == ".asok") {
+      for (auto &target : targets) {
+        if (boost::regex_search(x.path().filename().string(),
+            boost::regex(prefix + target + R"(\..*\.asok)"))) {
+          std::cout << "Found " << target << " socket " << x.path()
+                    << std::endl;
+          sockets.insert(std::make_pair(target, x.path().string()));
+          targets.erase(target);
+        }
+      }
+      if (targets.empty()) {
+        std::cout << "Found all required sockets" << std::endl;
+        break;
+      }
+    }
+  }
+
+  return !sockets.empty() && targets.empty();
+}
+
+std::pair<std::string, std::string>
+AdminSocketOutput::run_command(AdminSocketClient &client,
+                               const std::string raw_command,
+                               bool send_untouched) {
+  std::cout << "Sending command \"" << raw_command << "\"" << std::endl;
+  std::string command;
+  std::string output;
+  if (send_untouched) {
+    command = raw_command;
+  } else {
+    command = "{\"prefix\":\"" + raw_command + "\"}";
+  }
+  client.do_request(command, &output);
+  return std::make_pair(command, output);
+}
+
+bool AdminSocketOutput::gather_socket_output() {
+
+  std::cout << "Gathering socket output" << std::endl;
+  for (const auto& socket : sockets) {
+    std::string response;
+    AdminSocketClient client(socket.second);
+    std::cout << std::endl
+              << "Sending request to " << socket << std::endl
+              << std::endl;
+    client.do_request("{\"prefix\":\"help\"}", &response);
+    std::cout << response << '\n';
+
+    JSONParser parser;
+    bool ret = parser.parse(response.c_str(), response.size());
+    if (!ret) {
+      cerr << "parse error" << std::endl;
+      return false;
+    }
+
+    socket_results sresults;
+    JSONObjIter iter = parser.find_first();
+    const auto postponed_iter = postponed_commands.find(socket.first);
+    std::vector<std::string> postponed;
+    if (postponed_iter != postponed_commands.end()) {
+      postponed = postponed_iter->second;
+    }
+    std::cout << "Sending commands to " << socket.first << " socket"
+              << std::endl;
+    for (; !iter.end(); ++iter) {
+      if (std::find(postponed.begin(), postponed.end(), (*iter)->get_name())
+          != std::end(postponed)) {
+        std::cout << "Command \"" << (*iter)->get_name() << "\" postponed"
+                  << std::endl;
+        continue;
+      }
+      sresults.insert(run_command(client, (*iter)->get_name()));
+    }
+
+    if (sresults.empty()) {
+      return false;
+    }
+
+    // Custom commands
+    const auto seek = custom_commands.find(socket.first);
+    if (seek != custom_commands.end()) {
+      std::cout << std::endl << "Sending custom commands:" << std::endl;
+      for (const auto& raw_command : seek->second) {
+        sresults.insert(run_command(client, raw_command, true));
+      }
+    }
+
+    // Postponed commands
+    if (!postponed.empty())
+      std::cout << std::endl << "Sending postponed commands" << std::endl;
+    for (const auto& command : postponed) {
+      sresults.insert(run_command(client, command));
+    }
+
+    results.insert(
+        std::pair<std::string, socket_results>(socket.first, sresults));
+
+  }
+
+  return true;
+}
+
+std::string AdminSocketOutput::get_result(const std::string target,
+                                          const std::string command) const {
+  const auto& target_results = results.find(target);
+  if (target_results == results.end())
+    return std::string("");
+  else {
+    const auto& result = target_results->second.find(command);
+    if (result == target_results->second.end())
+      return std::string("");
+    else
+      return result->second;
+  }
+}
+
+bool AdminSocketOutput::run_tests() const {
+  for (const auto& socket : sockets) {
+    const auto& seek = tests.find(socket.first);
+    if (seek != tests.end()) {
+      std::cout << std::endl;
+      std::cout << "Running tests for " << socket.first << " socket" << std::endl;
+      for (const auto& test : seek->second) {
+          auto result = get_result(socket.first, test.first);
+          if(result.empty()) {
+            std::cout << "Failed to find result for command: " << test.first << std::endl;
+            return false;
+          } else {
+            std::cout << "Running test for command: " << test.first << std::endl;
+            const auto& test_func = test.second;
+            bool res = test_func(result);
+            if (res == false)
+              return false;
+            else
+              std::cout << "Test passed" << std::endl;
+          }
+        }
+      }
+    }
+
+  return true;
+}
+
+void AdminSocketOutput::exec() {
+  ceph_assert(init_directories());
+  ceph_assert(init_sockets());
+  ceph_assert(gather_socket_output());
+  ceph_assert(run_tests());
+}
diff --git a/src/test/admin_socket_output.h b/src/test/admin_socket_output.h
new file mode 100644 (file)
index 0000000..28f6576
--- /dev/null
@@ -0,0 +1,78 @@
+// -*- 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) 2017 Red Hat
+ *
+ * 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.
+ *
+ */
+
+#ifndef CEPH_ADMIN_SOCKET_OUTPUT_H
+#define CEPH_ADMIN_SOCKET_OUTPUT_H
+
+#include <string>
+#include <map>
+#include <set>
+#include <vector>
+
+#include <boost/filesystem/path.hpp>       // For path
+#include <boost/filesystem/operations.hpp> // For exists, is_directory
+
+namespace bfs = boost::filesystem;
+
+using socket_results = std::map<std::string, std::string>;
+using test_functions =
+    std::vector<std::pair<std::string, bool (*)(std::string &)>>;
+
+class AdminSocketClient;
+
+class AdminSocketOutput {
+public:
+  AdminSocketOutput() {}
+
+  void add_target(const std::string &target);
+  void add_command(const std::string &target, const std::string &command);
+  void add_test(const std::string &target, const std::string &command,
+                bool (*test)(std::string &));
+  void postpone(const std::string &target, const std::string &command);
+
+  void exec();
+
+  void mod_for_vstart() {
+    socketdir = "./out";
+    prefix = "";
+  }
+
+private:
+  bool init_directories() const {
+    std::cout << "Checking " << socketdir << std::endl;
+    return exists(socketdir) && is_directory(socketdir);
+  }
+
+  bool init_sockets();
+  bool gather_socket_output();
+  std::string get_result(const std::string target, const std::string command) const;
+
+  std::pair<std::string, std::string>
+  run_command(AdminSocketClient &client, const std::string raw_command,
+              bool send_untouched = false);
+
+  bool run_tests() const;
+
+  std::set<std::string> targets;
+  std::map<std::string, std::string> sockets;
+  std::map<std::string, socket_results> results;
+  std::map<std::string, std::vector<std::string>> custom_commands;
+  std::map<std::string, std::vector<std::string>> postponed_commands;
+  std::map<std::string, test_functions> tests;
+
+  std::string prefix = "ceph-";
+  bfs::path socketdir = "/var/run/ceph";
+};
+
+#endif // CEPH_ADMIN_SOCKET_OUTPUT_H
diff --git a/src/test/admin_socket_output_tests.cc b/src/test/admin_socket_output_tests.cc
new file mode 100644 (file)
index 0000000..72a573b
--- /dev/null
@@ -0,0 +1,66 @@
+// -*- 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) 2017 Red Hat
+ *
+ * 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.
+ *
+ */
+
+#include <string>
+#include <iostream>
+
+#include "common/ceph_json.h"
+
+// Test functions
+
+// Example test function
+/*
+bool test_config_get_admin_socket(std::string& output) {
+  return std::string::npos != output.find("admin_socket") &&
+         std::string::npos != output.rfind(".asok");
+}
+*/
+
+bool test_dump_pgstate_history(std::string &output) {
+  JSONParser parser;
+  bool ret = parser.parse(output.c_str(), output.size());
+  if (!ret) {
+    std::cerr << "test_dump_pgstate_history: parse error" << std::endl;
+    return false;
+  }
+
+  JSONObjIter iter = parser.find_first();
+  for (; !iter.end(); ++iter) {
+    if ((*iter)->get_name() == "pg") {
+      ret = !(*iter)->get_data().empty();
+      if (ret == false) {
+        std::cerr << "test_dump_pgstate_history: pg value empty, failing"
+                  << std::endl;
+        std::cerr << "Dumping full output: " << std::endl;
+        std::cerr << output << std::endl;
+        break;
+      }
+    } else if ((*iter)->get_name() == "history") {
+      ret = std::string::npos != (*iter)->get_data().find("epoch") &&
+            std::string::npos != (*iter)->get_data().find("state") &&
+            std::string::npos != (*iter)->get_data().find("Initial") &&
+            std::string::npos != (*iter)->get_data().find("enter") &&
+            std::string::npos != (*iter)->get_data().find("exit");
+      if (ret == false) {
+        std::cerr << "test_dump_pgstate_history: Can't find expected values in "
+                     "history object, failing"
+                  << std::endl;
+        std::cerr << "Problem output was:" << std::endl;
+        std::cerr << (*iter)->get_data() << std::endl;
+        break;
+      }
+    }
+  }
+  return ret;
+}
diff --git a/src/test/admin_socket_output_tests.h b/src/test/admin_socket_output_tests.h
new file mode 100644 (file)
index 0000000..ef13b55
--- /dev/null
@@ -0,0 +1,28 @@
+// -*- 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) 2017 Red Hat
+ *
+ * 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.
+ *
+ */
+
+#ifndef CEPH_ADMIN_SOCKET_OUTPUT_TESTS_H
+#define CEPH_ADMIN_SOCKET_OUTPUT_TESTS_H
+
+// Test function declarations, definitions in admin_socket_output_tests.cc
+
+// Example test function
+
+/*
+bool test_config_get_admin_socket(std::string& output);
+*/
+
+bool test_dump_pgstate_history(std::string& output);
+
+#endif // CEPH_ADMIN_SOCKET_OUTPUT_TESTS_H
diff --git a/src/test/test_admin_socket_output.cc b/src/test/test_admin_socket_output.cc
new file mode 100644 (file)
index 0000000..7e1b35d
--- /dev/null
@@ -0,0 +1,128 @@
+// -*- 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) 2017 Red Hat
+ *
+ * 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.
+ *
+ */
+
+#include <algorithm>                                      // for move
+#include <iostream>                                       // for ostream
+#include <memory>                                         // for unique_ptr
+#include <string>                                         // for operator<<
+#include <vector>                                         // for vector
+#include <boost/program_options/option.hpp>               // for program_opt...
+#include <boost/program_options/options_description.hpp>  // for options_des...
+#include <boost/program_options/parsers.hpp>              // for basic_comma...
+#include <boost/program_options/variables_map.hpp>        // for variables_map
+#include <boost/program_options/parsers.hpp>              // for basic_comma...
+
+#include "admin_socket_output.h"
+#include "admin_socket_output_tests.h"
+
+namespace po = boost::program_options;
+
+void usage(po::options_description desc) {
+  std::cout << desc << std::endl;
+}
+
+void handle_unrecognised(std::vector<std::string>&& unrecognised) {
+  for (auto& un : unrecognised) {
+    std::cout << "Unrecognized Parameter: " << un << std::endl;
+  }
+}
+
+// Test functions:
+// See admin_socket_output_tests.h
+
+int main(int argc, char** argv) {
+
+  po::options_description desc("Allowed options");
+  desc.add_options()
+    ("help,h", "produce help message")
+    ("all", "implies"
+     " --osd"
+     " --mon"
+     " --mgr"
+     " --mds"
+     " --client"
+     " --vstart")
+    ("osd", "Test osd admin socket output")
+    ("mon", "Test mon admin socket output")
+    ("mgr", "Test mgr admin socket output")
+    ("mds", "Test mds admin socket output")
+    ("client", "Test client (includes rgw) admin socket output")
+    ("vstart", "Modify to run in vstart environment")
+    ;
+  auto parsed =
+    po::command_line_parser(argc, argv).options(desc).allow_unregistered().run();
+  po::variables_map vm;
+  po::store(parsed, vm);
+  po::notify(vm);
+
+  auto unrecognised = collect_unrecognized(parsed.options, po::include_positional);
+  if(!unrecognised.empty()) {
+    handle_unrecognised(std::move(unrecognised));
+    usage(desc);
+    return 1;
+  }
+  if (vm.count("help") || vm.empty()) {
+    usage(desc);
+    return 2;
+  }
+
+  std::unique_ptr<AdminSocketOutput> asockout(new AdminSocketOutput);
+
+  if (vm.count("vstart")) {
+    asockout->mod_for_vstart();
+  }
+
+  if(vm.count("all")) {
+    asockout->add_target("all");
+  } else {
+    if (vm.count("osd")) {
+      asockout->add_target("osd");
+    }
+    if (vm.count("mon")) {
+      asockout->add_target("mon");
+    }
+    if (vm.count("mgr")) {
+      asockout->add_target("mgr");
+    }
+    if (vm.count("mds")) {
+      asockout->add_target("mds");
+    }
+    if (vm.count("client")) {
+      asockout->add_target("client");
+    }
+  }
+
+  // Postpone commands that may affect later commands
+
+  asockout->postpone("mds", "force_readonly");
+
+  // Custom commands
+
+  //Example:
+  //asockout->add_command("osd", R"({"prefix":"config get", "var":"admin_socket"})");
+
+  // End custom commands
+
+  // Tests
+  //Example:
+  //asockout->add_test("osd", R"({"prefix":"config get", "var":"admin_socket"})", test_config_get_admin_socket);
+
+  asockout->add_test("osd", R"({"prefix":"dump_pgstate_history"})", test_dump_pgstate_history);
+
+  // End tests
+
+  asockout->exec();
+
+  return 0;
+}