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.
This commit is contained in:
@ -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)
|
||||
|
||||
132
maxscale-system-test/maxctrl.cc
Normal file
132
maxscale-system-test/maxctrl.cc
Normal file
@ -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<string> (pObject, "id", Presence::MANDATORY))
|
||||
, address (maxctrl.get<string> (pObject, "attributes/parameters/address"))
|
||||
, port (maxctrl.get<int64_t>(pObject, "attributes/parameters/port"))
|
||||
, connections(maxctrl.get<int64_t>(pObject, "attributes/statistics/connections"))
|
||||
, state (maxctrl.get<string> (pObject, "attributes/state"))
|
||||
{
|
||||
}
|
||||
|
||||
MaxCtrl::MaxCtrl(TestConnections* pTest)
|
||||
: m_test(*pTest)
|
||||
{
|
||||
}
|
||||
|
||||
unique_ptr<json_t> MaxCtrl::servers() const
|
||||
{
|
||||
return curl("servers");
|
||||
}
|
||||
|
||||
vector<MaxCtrl::Server> MaxCtrl::list_servers() const
|
||||
{
|
||||
return get_array<Server>(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<json_t> MaxCtrl::parse(const string& json) const
|
||||
{
|
||||
json_error_t error;
|
||||
unique_ptr<json_t> sRoot(json_loads(json.c_str(), 0, &error));
|
||||
|
||||
if (!sRoot)
|
||||
{
|
||||
raise("JSON parsing failed: " + string(error.text));
|
||||
}
|
||||
|
||||
return sRoot;
|
||||
}
|
||||
|
||||
unique_ptr<json_t> 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<string>(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<int64_t>(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;
|
||||
}
|
||||
176
maxscale-system-test/maxctrl.hh
Normal file
176
maxscale-system-test/maxctrl.hh
Normal file
@ -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 <maxscale/jansson.hh>
|
||||
|
||||
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<json_t> servers() const;
|
||||
|
||||
/**
|
||||
* The equivalent of 'maxctrl list servers'
|
||||
*
|
||||
* @return The JSON resrouce /v1/servers as a vector of Server objects.
|
||||
*/
|
||||
std::vector<Server> 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<class T>
|
||||
std::vector<T> get_array(json_t* pObject, const std::string& path, Presence presence) const
|
||||
{
|
||||
std::vector<T> 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<class T>
|
||||
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<json_t> 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<json_t> curl(const std::string& path) const;
|
||||
|
||||
void raise(const std::string& message) const;
|
||||
|
||||
private:
|
||||
TestConnections& m_test;
|
||||
};
|
||||
|
||||
template<>
|
||||
std::string MaxCtrl::get<std::string>(json_t* pObject, const std::string& path, Presence presence) const;
|
||||
|
||||
template<>
|
||||
int64_t MaxCtrl::get<int64_t>(json_t* pObject, const std::string& path, Presence presence) const;
|
||||
Reference in New Issue
Block a user