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;