From: Brad Hubbard Date: Tue, 18 Apr 2017 00:13:33 +0000 (+1000) Subject: tests: Add integration tests for admin socket output X-Git-Tag: v12.1.0~199^2 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=c5536b1976100ff9cbf5838fc8170333aea2c2d5;p=ceph.git tests: Add integration tests for admin socket output Add a framework to test all admin socket commands as reported by "help" as well as custom commands. Signed-off-by: Brad Hubbard --- diff --git a/CMakeLists.txt b/CMakeLists.txt index a5efb6d1d5a52..f72e01eccd57c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 index 0000000000000..969c40902fb91 --- /dev/null +++ b/qa/suites/rados/singleton-nomsgr/all/admin_socket_output.yaml @@ -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 diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 9b62b29ce2ffd..db42747bd6e77 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -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 index 0000000000000..50fa96681e5c5 --- /dev/null +++ b/src/test/admin_socket_output.cc @@ -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 +#include // For extension +#include // 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 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> 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 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 +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 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(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 index 0000000000000..28f65767f91ab --- /dev/null +++ b/src/test/admin_socket_output.h @@ -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 +#include +#include +#include + +#include // For path +#include // For exists, is_directory + +namespace bfs = boost::filesystem; + +using socket_results = std::map; +using test_functions = + std::vector>; + +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 + run_command(AdminSocketClient &client, const std::string raw_command, + bool send_untouched = false); + + bool run_tests() const; + + std::set targets; + std::map sockets; + std::map results; + std::map> custom_commands; + std::map> postponed_commands; + std::map 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 index 0000000000000..72a573b780927 --- /dev/null +++ b/src/test/admin_socket_output_tests.cc @@ -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 +#include + +#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 index 0000000000000..ef13b55dc461b --- /dev/null +++ b/src/test/admin_socket_output_tests.h @@ -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 index 0000000000000..7e1b35d0aacc2 --- /dev/null +++ b/src/test/test_admin_socket_output.cc @@ -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 // for move +#include // for ostream +#include // for unique_ptr +#include // for operator<< +#include // for vector +#include // for program_opt... +#include // for options_des... +#include // for basic_comma... +#include // for variables_map +#include // 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&& 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 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; +}