From b837e24995ba0c66a0c2e1af0a5525cc3046bc07 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 21 May 2019 10:33:27 +0300 Subject: [PATCH] MXS-2481 Add MaxCtrl class to test framework The purpose of this class is to make it easy from a test program to access the output of the REST-API. Currently it provides only the equivalent of "maxctrl list servers", but it can be extended if and when additional needs arise. Right now it's implemented so that curl is executed on the 0th MaxScale instance, using the ssh mechanism of the test framework. --- maxscale-system-test/CMakeLists.txt | 2 +- maxscale-system-test/maxctrl.cc | 132 +++++++++++++++++++++ maxscale-system-test/maxctrl.hh | 176 ++++++++++++++++++++++++++++ 3 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 maxscale-system-test/maxctrl.cc create mode 100644 maxscale-system-test/maxctrl.hh diff --git a/maxscale-system-test/CMakeLists.txt b/maxscale-system-test/CMakeLists.txt index 7d2aabc29..69007acaa 100644 --- a/maxscale-system-test/CMakeLists.txt +++ b/maxscale-system-test/CMakeLists.txt @@ -28,7 +28,7 @@ add_library(testcore SHARED testconnections.cpp nodes.cpp mariadb_nodes.cpp maxs sql_t1.cpp test_binlog_fnc.cpp get_my_ip.cpp big_load.cpp get_com_select_insert.cpp different_size.cpp fw_copy_rules maxinfo_func.cpp config_operations.cpp rds_vpc.cpp execute_cmd.cpp blob_test.cpp keepalived_func.cpp tcp_connection.cpp base/stopwatch.cpp fw_copy_rules.cpp - labels_table.cpp envv.cpp clustrix_nodes.cpp + labels_table.cpp envv.cpp clustrix_nodes.cpp maxctrl.cc # Include the CDC connector in the core library ${CMAKE_SOURCE_DIR}/connectors/cdc-connector/cdc_connector.cpp) target_link_libraries(testcore ${MARIADB_CONNECTOR_LIBRARIES} ${JANSSON_LIBRARIES} z m pthread ssl dl rt crypto crypt maxbase) diff --git a/maxscale-system-test/maxctrl.cc b/maxscale-system-test/maxctrl.cc new file mode 100644 index 000000000..20abd729b --- /dev/null +++ b/maxscale-system-test/maxctrl.cc @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 2022-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include "maxctrl.hh" + +using namespace std; + +MaxCtrl::Server::Server(const MaxCtrl& maxctrl, json_t* pObject) + : name (maxctrl.get (pObject, "id", Presence::MANDATORY)) + , address (maxctrl.get (pObject, "attributes/parameters/address")) + , port (maxctrl.get(pObject, "attributes/parameters/port")) + , connections(maxctrl.get(pObject, "attributes/statistics/connections")) + , state (maxctrl.get (pObject, "attributes/state")) +{ +} + +MaxCtrl::MaxCtrl(TestConnections* pTest) + : m_test(*pTest) +{ +} + +unique_ptr MaxCtrl::servers() const +{ + return curl("servers"); +} + +vector MaxCtrl::list_servers() const +{ + return get_array(servers().get(), "data", Presence::MANDATORY); +} + +json_t* MaxCtrl::get_object(json_t* pObject, const string& key, Presence presence) const +{ + json_t* pValue = json_object_get(pObject, key.c_str()); + + if (!pValue && (presence == Presence::MANDATORY)) + { + raise("Mandatory key '" + key + "' not present."); + } + + return pValue; +} + +json_t* MaxCtrl::get_leaf_object(json_t* pObject, const string& key, Presence presence) const +{ + auto i = key.find("/"); + + if (i == string::npos) + { + pObject = get_object(pObject, key, presence); + } + else + { + string head = key.substr(0, i); + string tail = key.substr(i + 1); + + pObject = get_object(pObject, head, Presence::MANDATORY); + pObject = get_leaf_object(pObject, tail, presence); + } + + return pObject; +} + +unique_ptr MaxCtrl::parse(const string& json) const +{ + json_error_t error; + unique_ptr sRoot(json_loads(json.c_str(), 0, &error)); + + if (!sRoot) + { + raise("JSON parsing failed: " + string(error.text)); + } + + return sRoot; +} + +unique_ptr MaxCtrl::curl(const string& path) const +{ + string url = "http://127.0.0.1:8989/v1/" + path; + string command = "curl -u admin:mariadb " + url; + + auto result = m_test.maxscales->ssh_output(command.c_str(), 0, false); + + if (result.first != 0) + { + raise("Invocation of curl failed: " + to_string(result.first)); + } + + return parse(result.second); +} + +void MaxCtrl::raise(const std::string& message) const +{ + ++m_test.global_result; + throw runtime_error(message); +} + +template<> +string MaxCtrl::get(json_t* pObject, const string& key, Presence presence) const +{ + json_t* pValue = get_leaf_object(pObject, key, presence); + + if (pValue && !json_is_string(pValue)) + { + raise("Key '" + key + "' is present, but value is not a string."); + } + + return pValue ? json_string_value(pValue) : ""; +} + +template<> +int64_t MaxCtrl::get(json_t* pObject, const string& key, Presence presence) const +{ + json_t* pValue = get_leaf_object(pObject, key, presence); + + if (pValue && !json_is_integer(pValue)) + { + raise("Key '" + key + "' is present, but value is not an integer."); + } + + return pValue ? json_integer_value(pValue) : 0; +} diff --git a/maxscale-system-test/maxctrl.hh b/maxscale-system-test/maxctrl.hh new file mode 100644 index 000000000..d518fc378 --- /dev/null +++ b/maxscale-system-test/maxctrl.hh @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 2022-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ +#pragma once + +#include "testconnections.h" +#include + +class MaxCtrl +{ +public: + MaxCtrl(const MaxCtrl&) = delete; + MaxCtrl& operator=(const MaxCtrl&) = delete; + + /** + * A class corresponding to a row in the output of 'maxctrl list servers' + */ + struct Server + { + Server(const MaxCtrl& maxctrl, json_t* pObject); + + std::string name; + std::string address; + int64_t port; + int64_t connections; + std::string state; + }; + + /** + * Constructor + * + * @param pTest The TestConnections instance. Must exist for the lifetime + * of the MaxCtrl instance. + */ + MaxCtrl(TestConnections* pTest); + + /** + * @return The TestConnections instance used by this instance. + */ + TestConnections& test() const + { + return m_test; + } + + /** + * @return The JSON object corresponding to /v1/servers. + */ + std::unique_ptr servers() const; + + /** + * The equivalent of 'maxctrl list servers' + * + * @return The JSON resrouce /v1/servers as a vector of Server objects. + */ + std::vector list_servers() const; + + enum class Presence + { + OPTIONAL, + MANDATORY + }; + + /** + * Turns a JSON array at a specific path into a vector of desired type. + * + * @param pObject The JSON object containing the JSON array. + * @param path The path of the resource, e.g. "a/b/c" + * @param presence Whether the path must exist or not. Note that if it is + * a true path "a/b/c", onlt the leaf may be optional, the + * components leading to the leaf must be present. + * + * @return Vector of object of desired type. + */ + template + std::vector get_array(json_t* pObject, const std::string& path, Presence presence) const + { + std::vector rv; + pObject = get_leaf_object(pObject, path, presence); + + if (pObject) + { + if (!json_is_array(pObject)) + { + raise("'" + path + "' exists, but is not an array."); + } + + size_t size = json_array_size(pObject); + + for (size_t i = 0; i < size; ++i) + { + json_t* pElement = json_array_get(pObject, i); + + rv.push_back(T(*this, pElement)); + } + } + + return rv; + } + + /** + * Get JSON object at specific key. + * + * @param pObject The object containing the path. + * @param key The key of the resource. May *not* be a path. + * @param presence Whether the key must exist or not. + * + * @return The desired object, or NULL if it does not exist and @c presence is OPTIONAL. + */ + json_t* get_object(json_t* pObject, const std::string& path, Presence presence) const; + + /** + * Get JSON object at specific path. + * + * @param pObject The object containing the path. + * @param path The path of the resource, e.g. "a/b/c" + * @param presence Whether the leaf must exist or not. Note that if it is + * a true path "a/b/c", only the leaf may be optional, the + * components leading to the leaf must be present. + * + * @return The desired object, or NULL if it does not exist and @c presence is OPTIONAL. + */ + json_t* get_leaf_object(json_t* pObject, const std::string& path, Presence presence) const; + + /** + * Get a JSON value as a particular C++ type. + * + * @param pObject The object containing the path. + * @param path The path of the resource, e.g. "a/b/c" + * @param presence Whether the leaf must exist or not. Note that if it is + * a true path "a/b/c", onlt the leaf may be optional, the + * components leading to the leaf must be present. + * + * @return The desired object, or NULL if it does not exist and @c presence is OPTIONAL. + */ + template + T get(json_t* pObject, const std::string& path, Presence presence = Presence::OPTIONAL) const; + + /** + * Parse a JSON object in a string. + * + * @return The corresponding json_t object. + */ + std::unique_ptr parse(const std::string& json) const; + + /** + * Issue a curl request to the REST-API endpoint of the MaxScale running on + * the maxscale 0 VM instance. + * + * The path will be appended to "http://127.0.0.1:8989/v1/". + * + * @param path The path of the resource. + * + * @return The corresponding json_t object. + */ + std::unique_ptr curl(const std::string& path) const; + + void raise(const std::string& message) const; + +private: + TestConnections& m_test; +}; + +template<> +std::string MaxCtrl::get(json_t* pObject, const std::string& path, Presence presence) const; + +template<> +int64_t MaxCtrl::get(json_t* pObject, const std::string& path, Presence presence) const;