polardbxengine/router/tests/component/test_rest_metadata_cache.cc

955 lines
35 KiB
C++

/*
Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <array>
#include <thread>
#ifdef RAPIDJSON_NO_SIZETYPEDEFINE
// if we build within the server, it will set RAPIDJSON_NO_SIZETYPEDEFINE
// globally and require to include my_rapidjson_size_t.h
#include "my_rapidjson_size_t.h"
#endif
#include <gmock/gmock.h>
#include <rapidjson/document.h>
#include <rapidjson/pointer.h>
#include <rapidjson/schema.h>
#include <rapidjson/stringbuffer.h>
#include "config_builder.h"
#include "dim.h"
#include "mysql/harness/logging/registry.h"
#include "mysql/harness/utility/string.h" // ::join
#include "mysql_session.h"
#include "rest_api_testutils.h"
#include "router_component_test.h"
#include "tcp_port_pool.h"
#include "temp_dir.h"
#include "mysqlrouter/rest_client.h"
using namespace std::chrono_literals;
static const std::string http_auth_realm_name("somerealm");
static const std::string http_auth_backend_name("somebackend");
// init_keyring() creates it
static const std::string keyring_username("mysql_router1_user");
static const std::string metadata_cache_section_name("gr_shard_1");
class RestApiTestBase : public RestApiComponentTest {
protected:
void SetUp() override {
RouterComponentTest::SetUp();
default_section_ = get_DEFAULT_defaults();
init_keyring(default_section_, conf_dir_.name());
}
std::string passwd_filename_;
std::map<std::string, std::string> default_section_;
};
static const std::vector<SwaggerPath> kMetadataSwaggerPaths{
{"/metadata/{metadataName}/config",
"Get config of the metadata cache of a replicaset of a cluster",
"config of metadata cache", "cache not found"},
{"/metadata/{metadataName}/status",
"Get status of the metadata cache of a replicaset of a cluster",
"status of metadata cache", "cache not found"},
{"/metadata", "Get list of the metadata cache instances",
"list of the metadata cache instances", ""},
};
// global variables used by the test
size_t g_refresh_failed = 0, g_refresh_succeeded = 0;
std::string g_time_last_refresh_succeeded, g_time_last_refresh_failed;
#if 0
// precondition to these tests is that we can start a router agianst a metadata-cluster
// which has no nodes. But with Bug#28352482 (no empty bootstrap_server_addresses) fixed
// we can't bring the metadata into that state anymore. We just won't start.
//
// An empty dynamic_config file will also not allow to start.
//
// In case that functionally ever comes back, we'll leave this code around, but disabled.
class RestMetadataCacheApiWithoutClusterTest
: public RestApiTestBase,
public ::testing::WithParamInterface<RestApiTestParams> {};
TEST_P(RestMetadataCacheApiWithoutClusterTest, ensure_openapi) {
const std::string http_hostname = "127.0.0.1";
const std::string userfile = create_password_file();
const std::string http_uri = GetParam().uri;
auto config_sections = get_restapi_config("rest_routing", userfile,
GetParam().request_authentication);
config_sections.push_back(ConfigBuilder::build_section("rest_api", {}));
config_sections.push_back(ConfigBuilder::build_section(
"rest_metadata_cache", {
{"require_realm", http_auth_realm_name},
}));
config_sections.push_back(ConfigBuilder::build_section(
"metadata_cache:" + metadata_cache_section_name,
{
{"user", keyring_username},
{"ttl", "0.2"},
}));
std::string conf_file{create_config_file(
conf_dir_.name(), mysql_harness::join(config_sections, "\n"),
&default_section_)};
ProcessWrapper &http_server{launch_router({"-c", conf_file})};
g_refresh_failed = 0;
g_time_last_refresh_failed = "";
fetch_and_validate_schema_and_resource(GetParam(), http_server);
// this part is relevant only for Get OK, otherwise let's avoid useless sleep
if (GetParam().status_code == HttpMethod::Get &&
GetParam().methods == HttpStatusCode::Ok) {
// sleep a while to make the counters and timestamps change
std::this_thread::sleep_for(std::chrono::seconds(1));
// check the resources again, we want to compare them against the previous
// ones
fetch_and_validate_schema_and_resource(GetParam(), http_server);
}
}
static const RestApiTestParams rest_api_params_without_cluster[]{
// The socpe of WL#12441 was limited and does not include those
// {"cluster_list_no_cluster",
// std::string(rest_api_basepath) + "/clusters/",
// "/clusters",
// HttpMethod::Get,
// HttpStatusCode::Ok,
// kContentTypeJson,
// kRestApiUsername,
// kRestApiPassword,
// /*request_authentication =*/true,
// {
// {"/items",
// [](const JsonValue *value) {
// ASSERT_NE(value, nullptr);
// ASSERT_TRUE(value->IsArray());
// ASSERT_EQ(value->GetArray().Size(), 0);
// }},
// }},
// {"cluster_nodes_no_cluster",
// std::string(rest_api_basepath) + "/clusters/71286871562387612/nodes",
// "/clusters/{clusterName}/nodes",
// HttpMethod::Get,
// HttpStatusCode::Ok,
// kContentTypeJson,
// kRestApiUsername,
// kRestApiPassword,
// /*request_authentication =*/true,
// {
// {"/items",
// [](const JsonValue *value) {
// ASSERT_NE(value, nullptr);
// ASSERT_TRUE(value->IsArray());
// ASSERT_EQ(value->GetArray().Size(), 0);
// }},
// }},
{"metadata_list_no_cluster",
std::string(rest_api_basepath) + "/metadata/",
"/metadata",
HttpMethod::Get,
HttpStatusCode::Ok,
kContentTypeJson,
kRestApiUsername,
kRestApiPassword,
/*request_authentication =*/true,
{
{"/items",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(value->GetArray().Size(), 1);
}},
{"/items/0/name",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsString());
ASSERT_EQ(value->GetString(), metadata_cache_section_name);
}},
},
kMetadataSwaggerPaths},
{"metadata_status_no_cluster",
std::string(rest_api_basepath) + "/metadata/" +
metadata_cache_section_name + "/status",
"/metadata/{metadataName}/status",
HttpMethod::Get,
HttpStatusCode::Ok,
kContentTypeJson,
kRestApiUsername,
kRestApiPassword,
/*request_authentication =*/true,
{
{"/refreshFailed",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsInt());
ASSERT_GT(value->GetInt(), 0);
// check if it is more than last time we checked
ASSERT_GT(value->GetInt(), g_refresh_failed);
g_refresh_failed = static_cast<size_t>(value->GetInt());
}},
{"/refreshSucceeded",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsInt());
ASSERT_EQ(value->GetInt(), 0);
}},
{"/timeLastRefreshFailed",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsString());
ASSERT_TRUE(pattern_found(value->GetString(), kTimestampPattern))
<< value->GetString();
// check if it is later than last time we checked
// timestamp format is YY-MM-DDThh:mm:ss.milisecZ (which we check
// above) so lexical string comparison should be fine
const std::string currentLastRefreshFailed = value->GetString();
ASSERT_TRUE(currentLastRefreshFailed > g_time_last_refresh_failed);
// save the current value
g_time_last_refresh_failed = currentLastRefreshFailed;
}},
{"/timeLastRefreshSucceeded",
[](const JsonValue *value) -> void { ASSERT_EQ(value, nullptr); }},
},
kMetadataSwaggerPaths},
{"metadata_config_no_cluster",
std::string(rest_api_basepath) + "/metadata/" +
metadata_cache_section_name + "/config",
"/metadata/{metadataName}/config",
HttpMethod::Get,
HttpStatusCode::Ok,
kContentTypeJson,
kRestApiUsername,
kRestApiPassword,
/*request_authentication =*/true,
{
{"/clusterName",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsString());
ASSERT_STREQ(value->GetString(), "");
}},
{"/groupReplicationId",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsString());
ASSERT_STREQ(value->GetString(), "");
}},
{"/timeRefreshInMs",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsInt());
ASSERT_EQ(value->GetInt(), 200);
}},
},
kMetadataSwaggerPaths},
};
INSTANTIATE_TEST_CASE_P(
Spec, RestMetadataCacheApiWithoutClusterTest,
::testing::ValuesIn(rest_api_params_without_cluster),
[](const ::testing::TestParamInfo<RestApiTestParams> &info) {
return info.param.test_name;
});
#endif
/**
* with cluster.
*/
class RestMetadataCacheApiTest
: public RestApiTestBase,
public ::testing::WithParamInterface<RestApiTestParams> {
protected:
const uint16_t metadata_server_port_{port_pool_.get_next_available()};
};
TEST_P(RestMetadataCacheApiTest, ensure_openapi) {
const std::string http_hostname = "127.0.0.1";
const std::string http_uri = GetParam().uri;
auto &md_server = ProcessManager::launch_mysql_server_mock(
get_data_dir().join("metadata_1_node_repeat.js").str(),
metadata_server_port_, EXIT_SUCCESS, false);
const std::string userfile = create_password_file();
auto config_sections = get_restapi_config("rest_routing", userfile,
GetParam().request_authentication);
config_sections.push_back(ConfigBuilder::build_section("rest_api", {}));
config_sections.push_back(ConfigBuilder::build_section(
"rest_metadata_cache", {
{"require_realm", http_auth_realm_name},
}));
config_sections.push_back(ConfigBuilder::build_section(
"metadata_cache:" + metadata_cache_section_name,
{
{"user", keyring_username},
// name of the cluster in the mock's metadata
{"metadata_cluster", "test"},
{"ttl", "0.2"},
{"bootstrap_server_addresses",
"mysql://127.0.0.1:" + std::to_string(metadata_server_port_)},
}));
std::string conf_file{create_config_file(
conf_dir_.name(), mysql_harness::join(config_sections, "\n"),
&default_section_)};
// delay the wait until we really need it.
ASSERT_NO_FATAL_FAILURE(
check_port_ready(md_server, metadata_server_port_, 5000ms));
auto &router_proc{launch_router({"-c", conf_file})};
g_refresh_succeeded = 0;
g_time_last_refresh_failed = "";
EXPECT_NO_FATAL_FAILURE(
fetch_and_validate_schema_and_resource(GetParam(), router_proc))
<< router_proc.get_full_output();
// this part is relevant only for Get OK, otherwise let's avoid useless sleep
if (GetParam().status_code == HttpMethod::Get &&
GetParam().methods == HttpStatusCode::Ok) {
// sleep a while to make the counters and timestamps change
std::this_thread::sleep_for(std::chrono::seconds(1));
// check the resources again, we want to compare them against the previous
// ones
fetch_and_validate_schema_and_resource(GetParam(), router_proc);
}
}
// ****************************************************************************
// Request the resource(s) using supported methods with authentication enabled
// and valid credentials
// ****************************************************************************
static const RestApiTestParams rest_api_valid_methods[]{
// The socpe of WL#12441 was limited and does not include those
// {"cluster_list",
// std::string(rest_api_basepath) + "/clusters/",
// "/clusters",
// HttpMethod::Get,
// HttpStatusCode::Ok,
// kContentTypeJson,
// kRestApiUsername,
// kRestApiPassword,
// /*request_authentication =*/true,
// {
// {"/items",
// [](const JsonValue *value) {
// ASSERT_NE(value, nullptr);
// ASSERT_TRUE(value->IsArray());
// ASSERT_EQ(value->GetArray().Size(), 0);
// }},
// }},
// {"cluster_nodes",
// std::string(rest_api_basepath) + "/clusters/71286871562387612/nodes",
// "/clusters/{clusterName}/nodes",
// HttpMethod::Get,
// HttpStatusCode::Ok,
// kContentTypeJson,
// kRestApiUsername,
// kRestApiPassword,
// /*request_authentication =*/true,
// {
// {"/items",
// [](const JsonValue *value) {
// ASSERT_NE(value, nullptr);
// ASSERT_TRUE(value->IsArray());
// ASSERT_EQ(value->GetArray().Size(), 0);
// }},
// }},
{"metadata_list",
std::string(rest_api_basepath) + "/metadata/",
"/metadata",
HttpMethod::Get,
HttpStatusCode::Ok,
kContentTypeJson,
kRestApiUsername,
kRestApiPassword,
/*request_authentication =*/true,
{
{"/items",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(value->GetArray().Size(), 1);
}},
{"/items/0/name",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsString());
ASSERT_EQ(value->GetString(), metadata_cache_section_name);
}},
},
kMetadataSwaggerPaths},
{"metadata_status",
std::string(rest_api_basepath) + "/metadata/" +
metadata_cache_section_name + "/status",
"/metadata/{metadataName}/status",
HttpMethod::Get,
HttpStatusCode::Ok,
kContentTypeJson,
kRestApiUsername,
kRestApiPassword,
/*request_authentication =*/true,
{
{"/refreshFailed",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsInt());
ASSERT_EQ(value->GetInt(), 0);
}},
{"/refreshSucceeded",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsInt());
// check if it is more than last time we checked
ASSERT_GT(value->GetInt(), g_refresh_succeeded);
g_refresh_succeeded = static_cast<size_t>(value->GetInt());
}},
{"/timeLastRefreshSucceeded",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsString());
ASSERT_TRUE(pattern_found(value->GetString(), kTimestampPattern))
<< value->GetString();
// check if it is later than last time we checked
// timestamp format is YY-MM-DDThh:mm:ss.milisecZ (which we check
// above) so lexical string comparison should be fine
const std::string currentLastRefreshSucceeded = value->GetString();
ASSERT_TRUE(currentLastRefreshSucceeded >
g_time_last_refresh_succeeded);
// save the current value
g_time_last_refresh_succeeded = currentLastRefreshSucceeded;
}},
{"/timeLastRefreshFailed",
[](const JsonValue *value) -> void { ASSERT_EQ(value, nullptr); }},
{"/lastRefreshHostname",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsString());
ASSERT_STRNE(value->GetString(), "");
}},
{"/lastRefreshPort",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsInt());
ASSERT_GT(value->GetInt(), 0);
}},
},
kMetadataSwaggerPaths},
{"metadata_config",
std::string(rest_api_basepath) + "/metadata/" +
metadata_cache_section_name + "/config",
"/metadata/{metadataName}/config",
HttpMethod::Get,
HttpStatusCode::Ok,
kContentTypeJson,
kRestApiUsername,
kRestApiPassword,
/*request_authentication =*/true,
{
{"/clusterName",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsString());
ASSERT_STREQ(value->GetString(), "test");
}},
{"/groupReplicationId",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsString());
ASSERT_STREQ(value->GetString(), "");
}},
{"/timeRefreshInMs",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsUint64());
ASSERT_EQ(value->GetUint64(), 200);
}},
{"/nodes",
[](const JsonValue *value) -> void {
ASSERT_NE(value, nullptr);
ASSERT_TRUE(value->IsArray());
auto nodes = value->GetArray();
ASSERT_GT(nodes.Size(), 0);
for (unsigned i = 0; i < nodes.Size(); ++i) {
ASSERT_TRUE(nodes[i].IsObject());
const auto &node = nodes[i].GetObject();
ASSERT_TRUE(node.HasMember("hostname"));
ASSERT_TRUE(node["hostname"].IsString());
ASSERT_STRNE(node["hostname"].GetString(), "");
ASSERT_TRUE(node.HasMember("port"));
ASSERT_TRUE(node["port"].IsInt());
ASSERT_GT(node["port"].GetInt(), 0);
}
}},
},
kMetadataSwaggerPaths},
};
INSTANTIATE_TEST_CASE_P(
ValidMethods, RestMetadataCacheApiTest,
::testing::ValuesIn(rest_api_valid_methods),
[](const ::testing::TestParamInfo<RestApiTestParams> &info) {
return info.param.test_name;
});
// ****************************************************************************
// Request non-existing resource(s) using supported methods with authentication
// enabled and valid credentials
// ****************************************************************************
static const RestApiTestParams rest_api_non_existig_resouces[]{
{"metadata_status_non_existing",
std::string(rest_api_basepath) + "/metadata/NON_EXISTING/status",
"/metadata/{metadataName}/status",
HttpMethod::Get,
HttpStatusCode::NotFound,
kContentTypeJson,
kRestApiUsername,
kRestApiPassword,
/*request_authentication =*/true,
{},
kMetadataSwaggerPaths},
{"metadata_config_non_existing",
std::string(rest_api_basepath) + "/metadata/NON_EXISTING/config",
"/metadata/{metadataName}/config",
HttpMethod::Get,
HttpStatusCode::NotFound,
kContentTypeJson,
kRestApiUsername,
kRestApiPassword,
/*request_authentication =*/true,
{},
kMetadataSwaggerPaths},
{"metadata_unsupported_param",
std::string(rest_api_basepath) + "/metadata/?limit=10",
"/metadata",
HttpMethod::Get,
HttpStatusCode::BadRequest,
kContentTypeJsonProblem,
kRestApiUsername,
kRestApiPassword,
/*request_authentication =*/true,
{},
kMetadataSwaggerPaths},
{"metadata_status_unsupported_param",
std::string(rest_api_basepath) + "/metadata/" +
metadata_cache_section_name +
"/status?refreshFailed=0&refreshSucceeded=1",
"/metadata/{metadataName}/status",
HttpMethod::Get,
HttpStatusCode::BadRequest,
kContentTypeJsonProblem,
kRestApiUsername,
kRestApiPassword,
/*request_authentication =*/true,
{},
kMetadataSwaggerPaths},
{"metadata_config_unsupported_param",
std::string(rest_api_basepath) + "/metadata/" +
metadata_cache_section_name +
"/config?refreshFailed=0&refreshSucceeded=1",
"/metadata/{metadataName}/config",
HttpMethod::Get,
HttpStatusCode::BadRequest,
kContentTypeJsonProblem,
kRestApiUsername,
kRestApiPassword,
/*request_authentication =*/true,
{},
kMetadataSwaggerPaths},
};
INSTANTIATE_TEST_CASE_P(
NoNexistingResources, RestMetadataCacheApiTest,
::testing::ValuesIn(rest_api_non_existig_resouces),
[](const ::testing::TestParamInfo<RestApiTestParams> &info) {
return info.param.test_name;
});
// ****************************************************************************
// Request the resource(s) using supported methods with authentication enabled
// and invalid credentials
// ****************************************************************************
static const RestApiTestParams rest_api_valid_methods_invalid_auth_params[]{
// The socpe of WL#12441 was limited and does not include those
// {"cluster_list_invalid_auth",
// std::string(rest_api_basepath) + "/clusters/",
// "/clusters",
// HttpMethod::Get,
// HttpStatusCode::Unauthorized,
// kContentTypeHtmlCharset,
// kRestApiUsername,
// "invalid password",
// /*request_authentication =*/true,
// {}},
// {"cluster_nodes_invalid_auth",
// std::string(rest_api_basepath) + "/clusters/71286871562387612/nodes",
// "/clusters/{clusterName}/nodes",
// HttpMethod::Get,
// HttpStatusCode::Unauthorized,
// kContentTypeHtmlCharset,
// kRestApiUsername,
// "invalid password",
// /*request_authentication =*/true,
// {}},
{"metadata_list_invalid_auth",
std::string(rest_api_basepath) + "/metadata/",
"/metadata",
HttpMethod::Get,
HttpStatusCode::Unauthorized,
kContentTypeHtmlCharset,
kRestApiUsername,
"invalid password",
/*request_authentication =*/true,
{},
kMetadataSwaggerPaths},
{"metadata_status_invalid_auth",
std::string(rest_api_basepath) + "/metadata/" +
metadata_cache_section_name + "/status",
"/metadata/{metadataName}/status",
HttpMethod::Get,
HttpStatusCode::Unauthorized,
kContentTypeHtmlCharset,
kRestApiUsername,
"invalid password",
/*request_authentication =*/true,
{},
kMetadataSwaggerPaths},
{"metadata_config_invalid_auth",
std::string(rest_api_basepath) + "/metadata/" +
metadata_cache_section_name + "/config",
"/metadata/{metadataName}/config",
HttpMethod::Get,
HttpStatusCode::Unauthorized,
kContentTypeHtmlCharset,
kRestApiUsername,
"invalid password",
/*request_authentication =*/true,
{},
kMetadataSwaggerPaths},
};
INSTANTIATE_TEST_CASE_P(
ValidMethodsInvalidAuth, RestMetadataCacheApiTest,
::testing::ValuesIn(rest_api_valid_methods_invalid_auth_params),
[](const ::testing::TestParamInfo<RestApiTestParams> &info) {
return info.param.test_name;
});
// ****************************************************************************
// Request the resource(s) using unsupported methods with authentication enabled
// and valid credentials
// ****************************************************************************
static const RestApiTestParams rest_api_invalid_methods_params[]{
// The socpe of WL#12441 was limited and does not include those
// {"cluster_list_invalid_methods",
// std::string(rest_api_basepath) + "/clusters/",
// "/clusters",
// HttpMethod::Post | HttpMethod::Delete | HttpMethod::Patch,
// HttpStatusCode::MethodNotAllowed,
// kContentTypeJsonProblem,
// kRestApiUsername,
// kRestApiPassword,
// /*request_authentication =*/true,
// {
// {"/status",
// [](const JsonValue *value) -> void {
// ASSERT_NE(value, nullptr);
// ASSERT_TRUE(value->IsInt());
// ASSERT_EQ(value->GetInt(), HttpStatusCode::MethodNotAllowed);
// }},
// }},
// {"cluster_nodes_invalid_methods",
// std::string(rest_api_basepath) + "/clusters/71286871562387612/nodes",
// "/clusters/{clusterName}/nodes",
// HttpMethod::Post | HttpMethod::Delete | HttpMethod::Patch,
// HttpStatusCode::MethodNotAllowed,
// kContentTypeJsonProblem,
// kRestApiUsername,
// kRestApiPassword,
// /*request_authentication =*/true,
// {
// {"/status",
// [](const JsonValue *value) -> void {
// ASSERT_NE(value, nullptr);
// ASSERT_TRUE(value->IsInt());
// ASSERT_EQ(value->GetInt(), HttpStatusCode::MethodNotAllowed);
// }},
// }},
{"metadata_list_invalid_methods",
std::string(rest_api_basepath) + "/metadata/", "/metadata",
HttpMethod::Post | HttpMethod::Delete | HttpMethod::Patch |
HttpMethod::Head | HttpMethod::Trace | HttpMethod::Options |
HttpMethod::Connect,
HttpStatusCode::MethodNotAllowed, kContentTypeJsonProblem,
kRestApiUsername, kRestApiPassword,
/*request_authentication =*/true,
RestApiComponentTest::kProblemJsonMethodNotAllowed, kMetadataSwaggerPaths},
{"metadata_status_invalid_methods",
std::string(rest_api_basepath) + "/metadata/" +
metadata_cache_section_name + "/status",
"/metadata/{metadataName}/status",
HttpMethod::Post | HttpMethod::Delete | HttpMethod::Patch |
HttpMethod::Head | HttpMethod::Trace | HttpMethod::Options |
HttpMethod::Connect,
HttpStatusCode::MethodNotAllowed, kContentTypeJsonProblem,
kRestApiUsername, kRestApiPassword,
/*request_authentication =*/true,
RestApiComponentTest::kProblemJsonMethodNotAllowed, kMetadataSwaggerPaths},
{"metadata_config_invalid_methods",
std::string(rest_api_basepath) + "/metadata/" +
metadata_cache_section_name + "/config",
"/metadata/{metadataName}/config",
HttpMethod::Post | HttpMethod::Delete | HttpMethod::Patch |
HttpMethod::Head | HttpMethod::Trace | HttpMethod::Options |
HttpMethod::Connect,
HttpStatusCode::MethodNotAllowed, kContentTypeJsonProblem,
kRestApiUsername, kRestApiPassword,
/*request_authentication =*/true,
RestApiComponentTest::kProblemJsonMethodNotAllowed, kMetadataSwaggerPaths},
};
INSTANTIATE_TEST_CASE_P(
InvalidMethods, RestMetadataCacheApiTest,
::testing::ValuesIn(rest_api_invalid_methods_params),
[](const ::testing::TestParamInfo<RestApiTestParams> &info) {
return info.param.test_name;
});
// ****************************************************************************
// Configuration errors scenarios
// ****************************************************************************
/**
* @test Try to disable authentication although a REST API endpoint/plugin
* defines authentication as a MUST.
*
*/
TEST_F(RestMetadataCacheApiTest, metadata_cache_api_no_auth) {
const std::string userfile = create_password_file();
auto config_sections = get_restapi_config("rest_metadata_cache", userfile,
/*request_authentication=*/false);
// [rest_api] is always required
config_sections.push_back(ConfigBuilder::build_section("rest_api", {}));
const std::string conf_file{create_config_file(
conf_dir_.name(), mysql_harness::join(config_sections, "\n"))};
auto &router = launch_router({"-c", conf_file}, EXIT_FAILURE);
const auto wait_for_process_exit_timeout{10000ms};
check_exit_code(router, EXIT_FAILURE, wait_for_process_exit_timeout);
const std::string router_output = router.get_full_logfile();
EXPECT_NE(
router_output.find("plugin 'rest_metadata_cache' init failed: option "
"require_realm in [rest_metadata_cache] is required"),
router_output.npos)
<< router_output;
}
/**
* @test Enable authentication for the plugin in question. Reference a realm
* that does not exist in the configuration file.
*/
TEST_F(RestMetadataCacheApiTest, invalid_realm) {
const std::string userfile = create_password_file();
auto config_sections =
get_restapi_config("rest_metadata_cache", userfile,
/*request_authentication=*/true, "invalidrealm");
// [rest_api] is always required
config_sections.push_back(ConfigBuilder::build_section("rest_api", {}));
const std::string conf_file{create_config_file(
conf_dir_.name(), mysql_harness::join(config_sections, "\n"))};
auto &router = launch_router({"-c", conf_file}, EXIT_FAILURE);
const auto wait_for_process_exit_timeout{10000ms};
check_exit_code(router, EXIT_FAILURE, wait_for_process_exit_timeout);
const std::string router_output = router.get_full_logfile();
EXPECT_NE(router_output.find(
"Configuration error: unknown authentication "
"realm for [rest_metadata_cache] '': invalidrealm, known "
"realm(s): somerealm"),
router_output.npos)
<< router_output;
}
/**
* @test Start router with the REST routing API plugin [rest_metadata_cache],
* [http_plugin] and [metadata_cache] enabled but not the [rest_api] plugin.
*
*/
TEST_F(RestMetadataCacheApiTest, metadata_cache_api_no_rest_api) {
const std::string userfile = create_password_file();
auto config_sections = get_restapi_config("rest_metadata_cache", userfile,
/*request_authentication=*/false);
const std::string conf_file{create_config_file(
conf_dir_.name(), mysql_harness::join(config_sections, "\n"))};
auto &router = launch_router({"-c", conf_file}, EXIT_FAILURE);
const auto wait_for_process_exit_timeout{10000ms};
check_exit_code(router, EXIT_FAILURE, wait_for_process_exit_timeout);
const std::string router_output = router.get_full_output();
EXPECT_NE(router_output.find("Plugin 'rest_metadata_cache' needs plugin "
"'rest_api' which is missing in the "
"configuration"),
router_output.npos)
<< router_output;
}
/**
* @test Start router with the REST routing API plugin [rest_metadata_cache],
* [http_plugin] and [rest_api] enabled but not the [metadata_cache] plugin.
*
*/
// TEST_F(RestMetadataCacheApiTest, metadata_cache_api_no_mdc_secion) {
// const std::string userfile = create_password_file();
// auto config_sections = get_restapi_config("rest_metadata_cache", userfile,
// /*request_authentication=*/true);
// // [rest_api] is always required
// config_sections.push_back(ConfigBuilder::build_section("rest_api", {}));
// const std::string conf_file{create_config_file(
// conf_dir_.name(), mysql_harness::join(config_sections, "\n"))};
// auto router = launch_router({"-c", conf_file});
// const unsigned wait_for_process_exit_timeout{10000};
// check_exit_code(router, EXIT_FAILURE, wait_for_process_exit_timeout);
// const std::string router_output = router.get_full_output();
// EXPECT_NE(router_output.find("Plugin 'rest_metadata_cache' needs plugin "
// "'metadata_cache' which is missing in the "
// "configuration"),
// router_output.npos)
// << router_output;
//}
/**
* @test Add [rest_metadata_cache] twice to the configuration file. Start
* router. Expect router to fail providing an error about the duplicate section.
*
*/
TEST_F(RestMetadataCacheApiTest, rest_metadata_cache_section_twice) {
const std::string userfile = create_password_file();
auto config_sections = get_restapi_config("rest_metadata_cache", userfile,
/*request_authentication=*/true);
// [rest_api] is always required
config_sections.push_back(ConfigBuilder::build_section("rest_api", {}));
// force [rest_metadata_cache] twice in the config
config_sections.push_back(
ConfigBuilder::build_section("rest_metadata_cache", {}));
const std::string conf_file{create_config_file(
conf_dir_.name(), mysql_harness::join(config_sections, "\n"))};
auto &router = launch_router({"-c", conf_file}, EXIT_FAILURE);
const auto wait_for_process_exit_timeout{10000ms};
check_exit_code(router, EXIT_FAILURE, wait_for_process_exit_timeout);
const std::string router_output = router.get_full_output();
EXPECT_NE(
router_output.find(
"Configuration error: Section 'rest_metadata_cache' already exists"),
router_output.npos)
<< router_output;
}
/**
* @test Enable [rest_metadata_cache] using a section key such as
* [rest_metadata_cache:A]. Start router. Expect router to fail providing an
* error about the use of an unsupported section key.
*
*/
TEST_F(RestMetadataCacheApiTest, rest_metadata_cache_section_has_key) {
const std::string userfile = create_password_file();
auto config_sections = get_restapi_config("rest_metadata_cache:A", userfile,
/*request_authentication=*/true);
// [rest_api] is always required
config_sections.push_back(ConfigBuilder::build_section("rest_api", {}));
const std::string conf_file{create_config_file(
conf_dir_.name(), mysql_harness::join(config_sections, "\n"))};
auto &router = launch_router({"-c", conf_file}, EXIT_FAILURE);
const auto wait_for_process_exit_timeout{10000ms};
check_exit_code(router, EXIT_FAILURE, wait_for_process_exit_timeout);
const std::string router_output = router.get_full_logfile();
EXPECT_NE(
router_output.find(
"plugin 'rest_metadata_cache' init failed: [rest_metadata_cache] "
"section does not expect a key, found 'A'"),
router_output.npos)
<< router_output;
}
int main(int argc, char *argv[]) {
init_windows_sockets();
ProcessManager::set_origin(Path(argv[0]).dirname());
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}