1
0
mirror of https://github.com/codership/wsrep-lib.git synced 2025-07-30 07:23:07 +03:00

Initial implementation of the status interface reporter object.

This commit is contained in:
Alexey Yurchenko
2021-08-06 15:34:06 +03:00
parent 0151e98802
commit 4f1c201c9d
13 changed files with 1442 additions and 12 deletions

View File

@ -1,5 +1,5 @@
# #
# Copyright (C) 2018 Codership Oy <info@codership.com> # Copyright (C) 2021 Codership Oy <info@codership.com>
# #
cmake_minimum_required (VERSION 2.8) cmake_minimum_required (VERSION 2.8)
@ -25,7 +25,15 @@ else()
project(wsrep-lib) project(wsrep-lib)
endif() endif()
include(CheckIncludeFile) if (POLICY CMP0057)
cmake_policy(SET CMP0057 NEW)
endif()
if (POLICY CMP0077)
cmake_policy(SET CMP0077 NEW)
endif()
include(CheckIncludeFileCXX)
include(CheckLibraryExists) include(CheckLibraryExists)
include(CheckCXXCompilerFlag) include(CheckCXXCompilerFlag)
@ -36,6 +44,7 @@ option(WSREP_LIB_WITH_UNIT_TESTS "Compile unit tests" ON)
if (WSREP_LIB_WITH_UNIT_TESTS) if (WSREP_LIB_WITH_UNIT_TESTS)
# Run tests automatically by default if compiled # Run tests automatically by default if compiled
option(WSREP_LIB_WITH_AUTO_TEST "Run unit tests automatically after build" OFF) option(WSREP_LIB_WITH_AUTO_TEST "Run unit tests automatically after build" OFF)
option(WSREP_LIB_WITH_UNIT_TESTS_EXTRA "Compile unit tests that may require additional software" OFF)
endif() endif()
# Build a sample program # Build a sample program
@ -107,14 +116,47 @@ else()
set(WSREP_LIB_LIBDL "") set(WSREP_LIB_LIBDL "")
endif() endif()
set(MIN_BOOST_VERSION "1.54.0")
if (WSREP_LIB_WITH_UNIT_TESTS) if (WSREP_LIB_WITH_UNIT_TESTS)
find_package(Boost 1.54.0 REQUIRED if (WSREP_LIB_WITH_UNIT_TESTS_EXTRA)
set(json_HEADER "boost/json/src.hpp")
# Extra tests may require packages from very recent boost which may be
# unavailable on the system. In such case download private boost distro.
check_include_file_cxx(${json_HEADER} system_json_FOUND)
if (NOT system_json_FOUND)
if (NOT WITH_BOOST)
set(WITH_BOOST "${CMAKE_SOURCE_DIR}/../boost")
endif()
set(DOWNLOAD_BOOST ON)
include (cmake/boost.cmake)
set(MIN_BOOST_VERSION "${BOOST_MAJOR}.${BOOST_MINOR}.${BOOST_PATCH}")
message(STATUS "Boost includes: ${BOOST_INCLUDE_DIR}, ver: ${MIN_BOOST_VERSION}")
find_package(Boost ${MIN_BOOST_VERSION} REQUIRED
COMPONENTS json headers
PATHS ${WITH_BOOST}/lib/cmake
NO_DEFAULT_PATH
)
# Boost 1.76.0 comes very sloppy (and not only in JSON department)
# - need to disable some checks.
set(ADDITIONAL_CXX_FLAGS "-Wno-effc++ -Wno-conversion -Wno-suggest-override -Wno-overloaded-virtual")
set(ADDITIONAL_CXX_INCLUDES ${BOOST_INCLUDE_DIR})
check_include_file_cxx(${json_HEADER} json_FOUND
"-I${ADDITIONAL_CXX_INCLUDES} ${ADDITIONAL_CXX_FLAGS}"
)
if (NOT json_FOUND)
message(FATAL_ERROR "Required header 'boost/json.hpp' not found: ${json_FOUND}")
else()
include_directories(SYSTEM ${ADDITIONAL_CXX_INCLUDES})
endif()
endif()
endif()
find_package(Boost ${MIN_BOOST_VERSION} REQUIRED
unit_test_framework unit_test_framework
) )
endif() endif()
if (WSREP_LIB_WITH_DBSIM) if (WSREP_LIB_WITH_DBSIM)
find_package(Boost 1.54.0 REQUIRED find_package(Boost ${MIN_BOOST_VERSION} REQUIRED
program_options program_options
filesystem filesystem
thread thread

335
cmake/boost.cmake Normal file
View File

@ -0,0 +1,335 @@
# Copyright (c) 2014, 2021, Oracle and/or its affiliates.
# Copyright (c) 2021, Codership OY.
#
# 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, version 2.0, 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
# We need boost verision >= 1.75 to test JSON-related code.
# Invoke with -DWITH_BOOST=<directory> or set WITH_BOOST in environment.
# If WITH_BOOST is *not* set, or is set to the special value "system",
# we assume that the correct version (see below)
# is installed on the compile host in the standard location.
FUNCTION(GET_FILE_SIZE FILE_NAME OUTPUT_SIZE)
IF(WIN32)
FILE(TO_NATIVE_PATH "${CMAKE_SOURCE_DIR}/cmake/filesize.bat" FILESIZE_BAT)
FILE(TO_NATIVE_PATH "${FILE_NAME}" NATIVE_FILE_NAME)
EXECUTE_PROCESS(
COMMAND "${FILESIZE_BAT}" "${NATIVE_FILE_NAME}"
RESULT_VARIABLE COMMAND_RESULT
OUTPUT_VARIABLE RESULT
OUTPUT_STRIP_TRAILING_WHITESPACE)
ELSEIF(APPLE OR FREEBSD)
EXEC_PROGRAM(stat ARGS -f '%z' ${FILE_NAME} OUTPUT_VARIABLE RESULT)
ELSE()
EXEC_PROGRAM(stat ARGS -c '%s' ${FILE_NAME} OUTPUT_VARIABLE RESULT)
ENDIF()
SET(${OUTPUT_SIZE} ${RESULT} PARENT_SCOPE)
ENDFUNCTION()
SET(BOOST_MAJOR "1")
SET(BOOST_MINOR "76")
SET(BOOST_PATCH "0")
SET(BOOST_PACKAGE_NAME "boost_${BOOST_MAJOR}_${BOOST_MINOR}_${BOOST_PATCH}")
SET(BOOST_TARBALL "${BOOST_PACKAGE_NAME}.tar.gz")
SET(BOOST_DOWNLOAD_URL
"https://boostorg.jfrog.io/artifactory/main/release/${BOOST_MAJOR}.${BOOST_MINOR}.${BOOST_PATCH}/source/${BOOST_TARBALL}"
)
MACRO(RESET_BOOST_VARIABLES)
UNSET(BOOST_INCLUDE_DIR)
UNSET(BOOST_INCLUDE_DIR CACHE)
UNSET(LOCAL_BOOST_DIR)
UNSET(LOCAL_BOOST_DIR CACHE)
UNSET(LOCAL_BOOST_ZIP)
UNSET(LOCAL_BOOST_ZIP CACHE)
ENDMACRO()
MACRO(ECHO_BOOST_VARIABLES)
MESSAGE(STATUS "BOOST_INCLUDE_DIR ${BOOST_INCLUDE_DIR}")
MESSAGE(STATUS "LOCAL_BOOST_DIR ${LOCAL_BOOST_DIR}")
MESSAGE(STATUS "LOCAL_BOOST_ZIP ${LOCAL_BOOST_ZIP}")
ENDMACRO()
MACRO(COULD_NOT_FIND_BOOST)
ECHO_BOOST_VARIABLES()
RESET_BOOST_VARIABLES()
MESSAGE(STATUS "Could not find (the correct version of) boost.")
MESSAGE(STATUS "wsrep-lib currently requires ${BOOST_PACKAGE_NAME}\n")
MESSAGE(FATAL_ERROR
"You can download it with -DDOWNLOAD_BOOST=1 -DWITH_BOOST=<directory>\n"
"This CMake script will look for boost in <directory>. "
"If it is not there, it will download and unpack it "
"(in that directory) for you.\n"
"You can also download boost manually, from ${BOOST_DOWNLOAD_URL}\n"
"If you are inside a firewall, you may need to use an https proxy:\n"
"export https_proxy=http://example.com:80\n"
)
ENDMACRO()
# Pick value from environment if not set on command line.
IF(DEFINED ENV{WITH_BOOST} AND NOT DEFINED WITH_BOOST)
SET(WITH_BOOST "$ENV{WITH_BOOST}")
ENDIF()
# Pick value from environment if not set on command line.
IF(DEFINED ENV{BOOST_ROOT} AND NOT DEFINED WITH_BOOST)
SET(WITH_BOOST "$ENV{BOOST_ROOT}")
ENDIF()
IF(WITH_BOOST AND WITH_BOOST STREQUAL "system")
UNSET(WITH_BOOST)
UNSET(WITH_BOOST CACHE)
ENDIF()
# Update the cache, to make it visible in cmake-gui.
SET(WITH_BOOST ${WITH_BOOST} CACHE PATH
"Path to boost sources: a directory, or a tarball to be unzipped.")
# If the value of WITH_BOOST changes, we must unset all dependent variables:
IF(OLD_WITH_BOOST)
IF(NOT "${OLD_WITH_BOOST}" STREQUAL "${WITH_BOOST}")
RESET_BOOST_VARIABLES()
ENDIF()
ENDIF()
SET(OLD_WITH_BOOST ${WITH_BOOST} CACHE INTERNAL
"Previous version of WITH_BOOST" FORCE)
IF (WITH_BOOST)
## Did we get a full path name, including file name?
IF (${WITH_BOOST} MATCHES ".*\\.tar.gz" OR ${WITH_BOOST} MATCHES ".*\\.zip")
GET_FILENAME_COMPONENT(BOOST_DIR ${WITH_BOOST} PATH)
GET_FILENAME_COMPONENT(BOOST_ZIP ${WITH_BOOST} NAME)
FIND_FILE(LOCAL_BOOST_ZIP
NAMES ${BOOST_ZIP}
PATHS ${BOOST_DIR}
NO_DEFAULT_PATH
)
ENDIF()
## Did we get a path name to the directory of the .tar.gz or .zip file?
FIND_FILE(LOCAL_BOOST_ZIP
NAMES "${BOOST_PACKAGE_NAME}.tar.gz" "${BOOST_PACKAGE_NAME}.zip"
PATHS "${WITH_BOOST}"
NO_DEFAULT_PATH
)
IF(LOCAL_BOOST_ZIP)
MESSAGE(STATUS "Local boost zip: ${LOCAL_BOOST_ZIP}")
GET_FILE_SIZE(${LOCAL_BOOST_ZIP} LOCAL_BOOST_ZIP_SIZE)
IF(LOCAL_BOOST_ZIP_SIZE EQUAL 0)
# A previous failed download has left an empty file, most likely the
# user pressed Ctrl-C to kill a hanging connection due to missing vpn
# proxy. Remove it!
MESSAGE("${LOCAL_BOOST_ZIP} is zero length. Deleting it.")
FILE(REMOVE ${WITH_BOOST}/${BOOST_TARBALL})
UNSET(LOCAL_BOOST_ZIP)
UNSET(LOCAL_BOOST_ZIP CACHE)
ENDIF()
UNSET(LOCAL_BOOST_ZIP_ZERO_LENGTH)
ENDIF()
## Did we get a path name to the directory of an unzipped version?
FIND_FILE(LOCAL_BOOST_SOURCE
NAMES "${BOOST_PACKAGE_NAME}"
PATHS "${WITH_BOOST}"
NO_DEFAULT_PATH
)
## Did we get a path name to an unzippped version?
FIND_PATH(LOCAL_BOOST_VER
NAMES "boost/version.hpp"
PATHS "${WITH_BOOST}"
NO_DEFAULT_PATH
)
IF(LOCAL_BOOST_VER AND NOT LOCAL_BOOST_SOURCE)
SET(LOCAL_BOOST_SOURCE ${WITH_BOOST})
UNSET(LOCAL_BOOST_VER)
UNSET(LOCAL_BOOST_VER CACHE)
ENDIF()
IF(LOCAL_BOOST_SOURCE)
MESSAGE(STATUS "Local boost source: ${LOCAL_BOOST_SOURCE}")
ENDIF()
## Did we get a path to compiled boost install?
FIND_PATH(LOCAL_BOOST_LIB
NAMES "lib"
PATHS "${WITH_BOOST}"
NO_DEFAULT_PATH
)
IF(LOCAL_BOOST_LIB)
SET(LOCAL_BOOST_BUILD ${WITH_BOOST})
UNSET(LOCAL_BOOST_LIB)
UNSET(LOCAL_BOOST_LIB CACHE)
ENDIF()
ENDIF()
# There is a similar option in unittest/gunit.
# But the boost tarball is much bigger, so we have a separate option.
OPTION(DOWNLOAD_BOOST "Download boost from sourceforge." OFF)
SET(DOWNLOAD_BOOST_TIMEOUT 600 CACHE STRING
"Timeout in seconds when downloading boost.")
# If we could not find it, then maybe download it.
IF(WITH_BOOST AND NOT LOCAL_BOOST_ZIP AND NOT LOCAL_BOOST_SOURCE AND NOT LOCAL_BOOST_BUILD)
IF(NOT DOWNLOAD_BOOST)
MESSAGE(STATUS "WITH_BOOST=${WITH_BOOST}")
COULD_NOT_FIND_BOOST()
ENDIF()
# Download the tarball
MESSAGE(STATUS "Downloading ${BOOST_TARBALL} to ${WITH_BOOST}")
FILE(DOWNLOAD ${BOOST_DOWNLOAD_URL}
${WITH_BOOST}/${BOOST_TARBALL}
TIMEOUT ${DOWNLOAD_BOOST_TIMEOUT}
STATUS ERR
SHOW_PROGRESS
)
IF(ERR EQUAL 0)
SET(LOCAL_BOOST_ZIP "${WITH_BOOST}/${BOOST_TARBALL}")
SET(LOCAL_BOOST_ZIP "${WITH_BOOST}/${BOOST_TARBALL}" CACHE INTERNAL "")
ELSE()
MESSAGE(STATUS "Download failed, error: ${ERR}")
# A failed DOWNLOAD leaves an empty file, remove it
FILE(REMOVE ${WITH_BOOST}/${BOOST_TARBALL})
# STATUS returns a list of length 2
LIST(GET ERR 0 NUMERIC_RETURN)
IF(NUMERIC_RETURN EQUAL 28)
MESSAGE(FATAL_ERROR
"You can try downloading ${BOOST_DOWNLOAD_URL} manually"
" using curl/wget or a similar tool,"
" or increase the value of DOWNLOAD_BOOST_TIMEOUT"
" (which is now ${DOWNLOAD_BOOST_TIMEOUT} seconds)"
)
ENDIF()
MESSAGE(FATAL_ERROR
"You can try downloading ${BOOST_DOWNLOAD_URL} manually"
" using curl/wget or a similar tool"
)
ENDIF()
ENDIF()
IF(LOCAL_BOOST_ZIP AND NOT LOCAL_BOOST_SOURCE AND NOT LOCAL_BOOST_BUILD)
GET_FILENAME_COMPONENT(LOCAL_BOOST_DIR ${LOCAL_BOOST_ZIP} PATH)
IF(NOT EXISTS "${LOCAL_BOOST_DIR}/${BOOST_PACKAGE_NAME}")
GET_FILENAME_COMPONENT(BOOST_TARBALL ${LOCAL_BOOST_ZIP} NAME)
MESSAGE(STATUS "cd ${LOCAL_BOOST_DIR}; tar xfz ${BOOST_TARBALL}")
EXECUTE_PROCESS(
COMMAND ${CMAKE_COMMAND} -E tar xfz "${BOOST_TARBALL}"
WORKING_DIRECTORY "${LOCAL_BOOST_DIR}"
RESULT_VARIABLE tar_result
)
IF (tar_result MATCHES 0)
SET(BOOST_FOUND 1 CACHE INTERNAL "")
SET(LOCAL_BOOST_SOURCE "${LOCAL_BOOST_DIR}/${BOOST_PACKAGE_NAME}")
ELSE()
MESSAGE(STATUS "WITH_BOOST ${WITH_BOOST}.")
MESSAGE(STATUS "Failed to extract files.\n"
" Please try downloading and extracting yourself.\n"
" The url is: ${BOOST_DOWNLOAD_URL}")
MESSAGE(FATAL_ERROR "Giving up.")
ENDIF()
ENDIF()
ENDIF()
IF (LOCAL_BOOST_SOURCE AND NOT LOCAL_BOOST_BUILD)
GET_FILENAME_COMPONENT(LOCAL_BOOST_BUILD ${LOCAL_BOOST_SOURCE} PATH)
SET(BOOST_TO_BUILD "thread,filesystem,program_options,test,json")
MESSAGE(STATUS "Executing Boost configure")
EXECUTE_PROCESS(
COMMAND ./bootstrap.sh --prefix=${LOCAL_BOOST_BUILD} --with-libraries=${BOOST_TO_BUILD}
WORKING_DIRECTORY "${LOCAL_BOOST_SOURCE}"
RESULT_VARIABLE RES
ERROR_VARIABLE ERR
OUTPUT_FILE "bootstrap.log"
ERROR_FILE "bootstrap.err"
COMMAND_ECHO STDOUT
)
IF (NOT RES MATCHES 0)
MESSAGE(FATAL_ERROR "Boost configure failed: ${ERR}. Logs at ${LOCAL_BOOST_SOURCE}/bootstrap.log, ${LOCAL_BOOST_SOURCE}/bootstrap.err")
ENDIF()
MESSAGE(STATUS "Executing Boost build")
EXECUTE_PROCESS(
COMMAND ./b2 install -q
WORKING_DIRECTORY "${LOCAL_BOOST_SOURCE}"
RESULT_VARIABLE RES
ERROR_VARIABLE ERR
OUTPUT_FILE "b2.log"
ERROR_FILE "b2.err"
COMMAND_ECHO STDOUT
)
IF (NOT RES MATCHES 0)
MESSAGE(FATAL_ERROR "Boost build failed: ${ERR}. Logs at ${LOCAL_BOOST_SOURCE}/b2.log, ${LOCAL_BOOST_SOURCE}/b2.err")
ENDIF()
# remove the source directory so save space
FILE(REMOVE_RECURSE ${LOCAL_BOOST_SOURCE})
ENDIF()
# Search for the version file, first in LOCAL_BOOST_DIR or WITH_BOOST
MESSAGE(STATUS
"Looking for boost/version.hpp in ${LOCAL_BOOST_BUILD}/include")
FIND_PATH(BOOST_INCLUDE_DIR
NAMES "boost/version.hpp"
NO_DEFAULT_PATH
PATHS ${LOCAL_BOOST_BUILD}/include
)
# Then search in standard places (if not found above).
FIND_PATH(BOOST_INCLUDE_DIR
NAMES "boost/version.hpp"
)
IF(NOT BOOST_INCLUDE_DIR)
MESSAGE(STATUS
"Looked for boost/version.hpp in ${LOCAL_BOOST_BUILD}/include and default locations")
COULD_NOT_FIND_BOOST()
ELSE()
MESSAGE(STATUS "Found ${BOOST_INCLUDE_DIR}/boost/version.hpp ")
ENDIF()
# Verify version number. Version information looks like:
# // BOOST_VERSION % 100 is the patch level
# // BOOST_VERSION / 100 % 1000 is the minor version
# // BOOST_VERSION / 100000 is the major version
# #define BOOST_VERSION 107300
FILE(STRINGS "${BOOST_INCLUDE_DIR}/boost/version.hpp"
BOOST_VERSION_NUMBER
REGEX "^#define[\t ]+BOOST_VERSION[\t ][0-9]+.*"
)
STRING(REGEX REPLACE
"^.*BOOST_VERSION[\t ]([0-9][0-9])([0-9][0-9])([0-9][0-9]).*$" "\\1"
BOOST_MAJOR_VERSION "${BOOST_VERSION_NUMBER}")
STRING(REGEX REPLACE
"^.*BOOST_VERSION[\t ]([0-9][0-9])([0-9][0-9])([0-9][0-9]).*$" "\\2"
BOOST_MINOR_VERSION "${BOOST_VERSION_NUMBER}")
MESSAGE(STATUS "BOOST_VERSION_NUMBER is ${BOOST_VERSION_NUMBER}")
MESSAGE(STATUS "BOOST_INCLUDE_DIR ${BOOST_INCLUDE_DIR}")
# Boost gets confused about language support with Clang 7 + MSVC 15.9
IF(WIN32_CLANG)
ADD_DEFINITIONS(-DBOOST_NO_CXX17_HDR_STRING_VIEW)
ENDIF()
IF(LOCAL_BOOST_BUILD OR LOCAL_BOOST_ZIP)
SET(USING_LOCAL_BOOST 1)
ELSE()
SET(USING_SYSTEM_BOOST 1)
ENDIF()

View File

@ -57,6 +57,9 @@ db::params db::parse_args(int argc, char** argv)
("wsrep-provider-options", ("wsrep-provider-options",
po::value<std::string>(&params.wsrep_provider_options), po::value<std::string>(&params.wsrep_provider_options),
"wsrep provider options") "wsrep provider options")
("status-file",
po::value<std::string>(&params.status_file),
"status output file")
("servers", po::value<size_t>(&params.n_servers)->required(), ("servers", po::value<size_t>(&params.n_servers)->required(),
"number of servers to start") "number of servers to start")
("topology", po::value<std::string>(&params.topology), ("topology", po::value<std::string>(&params.topology),

View File

@ -38,6 +38,7 @@ namespace db
std::string topology; std::string topology;
std::string wsrep_provider; std::string wsrep_provider;
std::string wsrep_provider_options; std::string wsrep_provider_options;
std::string status_file;
int debug_log_level; int debug_log_level;
int fast_exit; int fast_exit;
int thread_instrumentation; int thread_instrumentation;
@ -55,6 +56,7 @@ namespace db
, topology() , topology()
, wsrep_provider() , wsrep_provider()
, wsrep_provider_options() , wsrep_provider_options()
, status_file("status.json")
, debug_log_level(0) , debug_log_level(0)
, fast_exit(0) , fast_exit(0)
, thread_instrumentation() , thread_instrumentation()

View File

@ -24,10 +24,52 @@
#include "db_simulator.hpp" #include "db_simulator.hpp"
#include "wsrep/logger.hpp" #include "wsrep/logger.hpp"
#include "wsrep/reporter.hpp"
#include <ostream>
#include <cstdio>
static wsrep::default_mutex logger_mtx;
static wsrep::reporter* reporter = nullptr;
static void
logger_fn(wsrep::log::level l, const char* pfx, const char* msg)
{
wsrep::unique_lock<wsrep::mutex> lock(logger_mtx);
struct timespec time;
clock_gettime(CLOCK_REALTIME, &time);
time_t const tt(time.tv_sec);
struct tm date;
localtime_r(&tt, &date);
char date_str[85] = { '\0', };
snprintf(date_str, sizeof(date_str) - 1,
"%04d-%02d-%02d %02d:%02d:%02d.%03d",
date.tm_year + 1900, date.tm_mon + 1, date.tm_mday,
date.tm_hour, date.tm_min, date.tm_sec, (int)time.tv_nsec/1000000);
#define LOG_STR date_str << pfx << wsrep::log::to_c_string(l) << msg
if (l >= wsrep::log::error && reporter)
{
std::ostringstream os;
os << LOG_STR;
std::cerr << os.str() << std::endl;
auto const tstamp(double(time.tv_sec) + double(time.tv_nsec)*1.0e-9);
reporter->report_log_msg(wsrep::reporter::error, os.str(), tstamp);
}
else
{
std::cerr << LOG_STR << std::endl;
}
#undef LOG_STR
}
db::server::server(simulator& simulator, db::server::server(simulator& simulator,
const std::string& name, const std::string& name,
const std::string& address) const std::string& address,
const std::string& status_file)
: simulator_(simulator) : simulator_(simulator)
, storage_engine_(simulator_.params()) , storage_engine_(simulator_.params())
, mutex_() , mutex_()
@ -40,7 +82,15 @@ db::server::server(simulator& simulator,
, appliers_() , appliers_()
, clients_() , clients_()
, client_threads_() , client_threads_()
{ } {
wsrep::log::logger_fn(logger_fn);
reporter = new wsrep::reporter(mutex_, status_file, 3);
}
db::server::~server()
{
delete reporter;
}
void db::server::applier_thread() void db::server::applier_thread()
{ {
@ -129,3 +179,9 @@ wsrep::high_priority_service* db::server::streaming_applier_service()
throw wsrep::not_implemented_error(); throw wsrep::not_implemented_error();
} }
void db::server::log_state_change(enum wsrep::server_state::state from,
enum wsrep::server_state::state to)
{
wsrep::log_info() << "State changed " << from << " -> " << to;
if (reporter) reporter->report_state(to, 0);
}

View File

@ -41,7 +41,9 @@ namespace db
public: public:
server(simulator& simulator, server(simulator& simulator,
const std::string& name, const std::string& name,
const std::string& address); const std::string& address,
const std::string& status_file);
~server();
void applier_thread(); void applier_thread();
void start_applier(); void start_applier();
void stop_applier(); void stop_applier();
@ -58,6 +60,8 @@ namespace db
wsrep::client_state* local_client_state(); wsrep::client_state* local_client_state();
void release_client_state(wsrep::client_state*); void release_client_state(wsrep::client_state*);
wsrep::high_priority_service* streaming_applier_service(); wsrep::high_priority_service* streaming_applier_service();
void log_state_change(enum wsrep::server_state::state,
enum wsrep::server_state::state);
private: private:
void start_client(size_t id); void start_client(size_t id);

View File

@ -154,10 +154,9 @@ void db::server_service::log_state_change(
enum wsrep::server_state::state prev_state, enum wsrep::server_state::state prev_state,
enum wsrep::server_state::state current_state) enum wsrep::server_state::state current_state)
{ {
server_.log_state_change(prev_state, current_state);
wsrep::log_info() << "State changed "
<< prev_state << " -> " << current_state;
} }
int db::server_service::wait_committing_transactions(int) int db::server_service::wait_committing_transactions(int)
{ {
throw wsrep::not_implemented_error(); throw wsrep::not_implemented_error();

View File

@ -137,7 +137,8 @@ void db::simulator::start()
std::make_unique<db::server>( std::make_unique<db::server>(
*this, *this,
name_os.str(), name_os.str(),
address_os.str())))); address_os.str(),
name_os.str() + "_" + params_.status_file))));
if (it.second == false) if (it.second == false)
{ {
throw wsrep::runtime_error("Failed to add server"); throw wsrep::runtime_error("Failed to add server");

114
include/wsrep/reporter.hpp Normal file
View File

@ -0,0 +1,114 @@
/*
* Copyright (C) 2021 Codership Oy <info@codership.com>
*
* This file is part of wsrep-lib.
*
* Wsrep-lib is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Wsrep-lib 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 wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file info.hpp
*
* Interface to report application status to external programs
* via JSON file.
*/
#ifndef WSREP_REPORTER_HPP
#define WSREP_REPORTER_HPP
#include "mutex.hpp"
#include "server_state.hpp"
#include <string>
#include <deque>
namespace wsrep
{
class reporter
{
public:
reporter(mutex& mutex,
const std::string& file_name,
size_t max_msg);
virtual ~reporter();
// indefinite progress value
static float constexpr indefinite = -1.0f;
void report_state(enum server_state::state state,
float progress = indefinite);
enum log_level
{
error,
warning
};
// undefined timestamp value
static double constexpr undefined = 0.0;
void report_log_msg(log_level, const std::string& msg,
double timestamp = undefined);
private:
enum substates {
s_disconnected_disconnected,
s_disconnected_initializing,
s_disconnected_initialized,
s_connected_waiting, // to become joiner
s_joining_initialized,
s_joining_sst,
s_joining_initializing,
s_joining_ist,
s_joined_syncing,
s_synced_running,
s_donor_sending,
s_disconnecting_disconnecting,
substates_max
};
wsrep::mutex& mutex_;
std::string const file_name_;
char* template_;
substates state_;
float progress_;
bool initialized_;
typedef struct {
double tstamp;
std::string msg;
} log_msg;
std::deque<log_msg> err_msg_;
std::deque<log_msg> warn_msg_;
size_t const max_msg_;
static void write_log_msgs(std::ostream& os,
const std::string& label,
const std::deque<log_msg>& msgs);
static void write_log_msg(std::ostream& os,
const log_msg& msg);
substates substate_map(enum server_state::state state);
float progress_map(float progress) const;
void write_file(double timestamp);
// make uncopyable
reporter(const wsrep::reporter&);
void operator=(const wsrep::reporter&);
}; /* reporter */
} /* wsrep */
#endif /* WSREP_REPORTER_HPP */

View File

@ -19,5 +19,6 @@ add_library(wsrep-lib
tls_service_v1.cpp tls_service_v1.cpp
transaction.cpp transaction.cpp
uuid.cpp uuid.cpp
reporter.cpp
wsrep_provider_v26.cpp) wsrep_provider_v26.cpp)
target_link_libraries(wsrep-lib wsrep_api_v26 pthread ${WSREP_LIB_LIBDL}) target_link_libraries(wsrep-lib wsrep_api_v26 pthread ${WSREP_LIB_LIBDL})

321
src/reporter.cpp Normal file
View File

@ -0,0 +1,321 @@
/*
* Copyright (C) 2021 Codership Oy <info@codership.com>
*
* This file is part of wsrep-lib.
*
* Wsrep-lib is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Wsrep-lib 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 wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
*/
#include "wsrep/reporter.hpp"
#include "wsrep/logger.hpp"
#include <sstream>
#include <iomanip>
#include <cstring> // strerror()
#include <cstdlib> // mkstemp()
#include <cerrno> // errno
#include <unistd.h> // write()
#include <cstdio> // rename(), snprintf()
#include <ctime> // clock_gettime()
#include <cmath> // floor()
static std::string const TEMP_EXTENSION(".XXXXXX");
static inline double
timestamp()
{
struct timespec time;
clock_gettime(CLOCK_REALTIME, &time);
return (double(time.tv_sec) + double(time.tv_nsec)*1.0e-9);
}
wsrep::reporter::reporter(wsrep::mutex& mutex,
const std::string& file_name,
size_t const max_msg)
: mutex_(mutex)
, file_name_(file_name)
, template_(new char [file_name_.length() + TEMP_EXTENSION.length() + 1])
, state_(wsrep::reporter::s_disconnected_disconnected)
, progress_(indefinite)
, initialized_(false)
, err_msg_()
, warn_msg_()
, max_msg_(max_msg)
{
template_[file_name_.length() + TEMP_EXTENSION.length()] = '\0';
write_file(timestamp());
}
wsrep::reporter::~reporter()
{
delete [] template_;
}
wsrep::reporter::substates
wsrep::reporter::substate_map(enum wsrep::server_state::state const state)
{
switch (state)
{
case wsrep::server_state::s_disconnected:
initialized_ = false;
return s_disconnected_disconnected;
case wsrep::server_state::s_initializing:
if (s_disconnected_disconnected == state_)
return s_disconnected_initializing;
else if (s_joining_sst == state_)
return s_joining_initializing;
else
{
assert(0);
return state_;
}
case wsrep::server_state::s_initialized:
initialized_ = true;
if (s_disconnected_initializing >= state_)
return s_disconnected_initialized;
else if (s_joining_initializing == state_)
return s_joining_ist;
else
{
assert(0);
return state_;
}
case wsrep::server_state::s_connected:
return s_connected_waiting;
case wsrep::server_state::s_joiner:
if (initialized_)
return s_joining_initialized;
else
return s_joining_sst;
case wsrep::server_state::s_joined:
return s_joined_syncing;
case wsrep::server_state::s_donor:
return s_donor_sending;
case wsrep::server_state::s_synced:
return s_synced_running;
case wsrep::server_state::s_disconnecting:
return s_disconnecting_disconnecting;
default:
assert(0);
return state_;
}
}
static float const SST_SHARE = 0.5f; // SST share of JOINING progress
static float const INIT_SHARE = 0.1f; // initialization share of JOINING progress
static float const IST_SHARE = (1.0f - SST_SHARE - INIT_SHARE); // IST share
float
wsrep::reporter::progress_map(float const progress) const
{
assert(progress >= 0.0f);
assert(progress <= 1.0f);
switch (state_)
{
case s_disconnected_disconnected:
return indefinite;
case s_disconnected_initializing:
return indefinite;
case s_disconnected_initialized:
return indefinite;
case s_connected_waiting:
return indefinite;
case s_joining_initialized:
return progress;
case s_joining_sst:
return progress * SST_SHARE;
case s_joining_initializing:
return SST_SHARE + progress * INIT_SHARE;
case s_joining_ist:
return SST_SHARE + INIT_SHARE + progress * IST_SHARE;
case s_joined_syncing:
return progress;
case s_synced_running:
return 1.0;
case s_donor_sending:
return progress;
case s_disconnecting_disconnecting:
return indefinite;
default:
assert(0);
return progress;
}
}
void
wsrep::reporter::write_log_msg(std::ostream& os,
const log_msg& msg)
{
os << "\t\t{\n";
os << "\t\t\t\"timestamp\": " << std::showpoint << std::setprecision(18)
<< msg.tstamp << ",\n";
os << "\t\t\t\"msg\": \"" << msg.msg << "\"\n";
os << "\t\t}";
}
void
wsrep::reporter::write_log_msgs(std::ostream& os,
const std::string& label,
const std::deque<log_msg>& msgs)
{
os << "\t\"" << label << "\": [\n";
for (size_t i(0); i < msgs.size(); ++i)
{
write_log_msg(os, msgs[i]);
os << (i+1 < msgs.size() ? ",\n" : "\n");
}
os << "\t],\n";
}
// write data to temporary file and then rename it to target file for atomicity
void
wsrep::reporter::write_file(double const tstamp)
{
enum progress_type {
t_indefinite = -1, // indefinite wait
t_progressive, // measurable progress
t_final // final state
};
struct strings {
const char* state;
const char* comment;
progress_type type;
};
static const struct strings strings[substates_max] =
{
{ "DISCONNECTED", "Disconnected", t_indefinite },
{ "DISCONNECTED", "Initializing", t_indefinite },
{ "DISCONNECTED", "Connecting", t_indefinite },
{ "CONNECTED", "Waiting", t_indefinite },
{ "JOINING", "Receiving state", t_progressive },
{ "JOINING", "Receiving SST", t_progressive },
{ "JOINING", "Initializing", t_progressive },
{ "JOINING", "Receiving IST", t_progressive },
{ "JOINED", "Syncing", t_progressive },
{ "SYNCED", "Operational", t_final },
{ "DONOR", "Donating SST", t_progressive },
{ "DISCONNECTING", "Disconnecting", t_indefinite }
};
// prepare template for mkstemp()
file_name_.copy(template_, file_name_.length());
TEMP_EXTENSION.copy(template_ +file_name_.length(),TEMP_EXTENSION.length());
int const fd(mkstemp(template_));
if (fd < 0)
{
std::cerr << "Reporter could not open temporary file `" << template_
<< "': " << strerror(errno) << " (" << errno << ")";
return;
}
double const seconds(floor(tstamp));
time_t const tt = time_t(seconds);
struct tm date;
localtime_r(&tt, &date);
char date_str[85] = { '\0', };
snprintf(date_str, sizeof(date_str) - 1,
"%04d-%02d-%02d %02d:%02d:%02d.%03d",
date.tm_year + 1900, date.tm_mon + 1, date.tm_mday,
date.tm_hour, date.tm_min, date.tm_sec,
(int)((tstamp-seconds)*1000));
std::ostringstream os;
os << "{\n";
os << "\t\"date\": \"" << date_str << "\",\n";
os << "\t\"timestamp\": " << std::showpoint << std::setprecision(18)
<< tstamp << ",\n";
write_log_msgs(os, "errors", err_msg_);
write_log_msgs(os, "warnings", warn_msg_);
os << "\t\"status\": {\n";
os << "\t\t\"state\": \"" << strings[state_].state << "\",\n";
os << "\t\t\"comment\": \"" << strings[state_].comment << "\",\n";
os << "\t\t\"progress\": " << std::showpoint << std::setprecision(6)
<< progress_ << "\n";
os << "\t}\n";
os << "}\n";
std::string str(os.str());
ssize_t err(write(fd, str.c_str(), str.length()));
if (err < 0)
{
std::cerr << "Could not write " << str.length()
<< " bytes to temporary file '"
<< template_ << "': " << strerror(errno)
<< " (" << errno << ")";
return;
}
rename(template_, file_name_.c_str());
}
void
wsrep::reporter::report_state(enum server_state::state const s, float const p)
{
assert(p >= -1);
assert(p <= 1);
bool flush(false);
wsrep::unique_lock<wsrep::mutex> lock(mutex_);
substates const state(substate_map(s));
if (state != state_)
{
state_ = state;
flush = true;
}
float const progress(progress_map(p));
assert(progress >= -1);
assert(progress <= 1);
if (progress != progress_)
{
progress_ = progress;
flush = true;
}
if (flush)
{
write_file(timestamp());
}
}
void
wsrep::reporter::report_log_msg(log_level const lvl,
const std::string& msg,
double tstamp)
{
std::deque<log_msg>& deque(lvl == error ? err_msg_ : warn_msg_);
wsrep::unique_lock<wsrep::mutex> lock(mutex_);
if (deque.empty() || msg != deque.back().msg)
{
if (deque.size() == max_msg_) deque.pop_front();
if (tstamp <= undefined) tstamp = timestamp();
log_msg entry({tstamp, msg});
deque.push_back(entry);
write_file(tstamp);
}
}

View File

@ -2,7 +2,8 @@
# Copyright (C) 2018 Codership Oy <info@codership.com> # Copyright (C) 2018 Codership Oy <info@codership.com>
# #
add_executable(wsrep-lib_test
set(TEST_SOURCES
mock_client_state.cpp mock_client_state.cpp
mock_high_priority_service.cpp mock_high_priority_service.cpp
mock_storage_service.cpp mock_storage_service.cpp
@ -22,6 +23,14 @@ add_executable(wsrep-lib_test
wsrep-lib_test.cpp wsrep-lib_test.cpp
) )
if (WSREP_LIB_WITH_UNIT_TESTS_EXTRA)
set(TEST_SOURCES ${TEST_SOURCES}
reporter_test.cpp
)
endif()
add_executable(wsrep-lib_test ${TEST_SOURCES})
target_link_libraries(wsrep-lib_test wsrep-lib) target_link_libraries(wsrep-lib_test wsrep-lib)
add_test(NAME wsrep-lib_test add_test(NAME wsrep-lib_test

543
test/reporter_test.cpp Normal file
View File

@ -0,0 +1,543 @@
/*
* Copyright (C) 2021 Codership Oy <info@codership.com>
*
* This file is part of wsrep-lib.
*
* Wsrep-lib is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Wsrep-lib 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 wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
*/
#include "wsrep/reporter.hpp"
#include <boost/test/unit_test.hpp>
#include <boost/json/src.hpp>
#include <fstream>
#include <deque>
#include <vector>
#include <unistd.h> // unlink() for cleanup
namespace json = boost::json;
//////// HELPERS ///////
static inline double
timestamp()
{
struct timespec time;
clock_gettime(CLOCK_REALTIME, &time);
return (double(time.tv_sec) + double(time.tv_nsec)*1.0e-9);
}
static json::value
read_file(const char* const filename)
{
std::ifstream input(filename, std::ios::binary);
std::vector<char> buffer(std::istreambuf_iterator<char>(input), {});
json::stream_parser parser;
json::error_code err;
parser.write(buffer.data(), buffer.size(), err);
if (err)
{
assert(0);
return nullptr;
}
parser.finish(err);
if (err)
{
assert(0);
return nullptr;
}
return parser.release();
}
struct logs
{
std::deque<double> tstamp_;
std::deque<std::string> msg_;
};
struct result
{
logs errors_;
logs warnings_;
struct
{
std::string state_;
std::string comment_;
float progress_;
} status_;
};
static void
parse_result(const json::value& value, struct result& res,
const std::string& path = "")
{
//std::cout << "Parsing " << path << ": " << value << ": " << value.kind() << std::endl;
switch (value.kind())
{
case json::kind::object:
{
auto const obj(value.get_object());
if (!obj.empty())
{
for (auto it = obj.begin(); it != obj.end(); ++it)
{
std::string const key(it->key().data(), it->key().length());
parse_result(it->value(), res, path + "." + key);
}
}
return;
}
case json::kind::array:
{
auto const arr(value.get_array());
if (!arr.empty())
{
for (auto it = arr.begin(); it != arr.end(); ++it)
{
parse_result(*it, res, path + ".[]");
}
}
return;
}
case json::kind::string:
{
auto const val(value.get_string().c_str());
if (path == ".errors.[].msg")
{
res.errors_.msg_.push_back(val);
}
else if (path == ".warnings.[].msg")
{
res.warnings_.msg_.push_back(val);
}
else if (path == ".status.state")
{
res.status_.state_ = val;
}
else if (path == ".status.comment")
{
res.status_.comment_ = val;
}
return;
}
case json::kind::uint64:
return;
case json::kind::int64:
return;
case json::kind::double_:
if (path == ".status.progress")
{
res.status_.progress_ = float(value.get_double());
}
else if (path == ".errors.[].timestamp")
{
res.errors_.tstamp_.push_back(value.get_double());
}
else if (path == ".warnings.[].timestamp")
{
res.warnings_.tstamp_.push_back(value.get_double());
}
return;
case json::kind::bool_:
return;
case json::kind::null:
return;
}
assert(0);
}
static bool
equal(const std::string& left, const std::string& right)
{
return left == right;
}
static bool
equal(double const left, double const right)
{
return ::fabs(left - right) < 0.0001; // we are looking for ms precision
}
template <typename T>
static bool
operator!=(const std::deque<T>& left, const std::deque<T>& right)
{
if (left.size() != right.size()) return true;
for (size_t i(0); i < left.size(); ++i)
if (!equal(left[i], right[i])) return true;
return false;
}
static bool
operator!=(const logs& left, const logs& right)
{
if (left.tstamp_ != right.tstamp_) return true;
return (left.msg_ != right.msg_);
}
static bool
operator==(const result& left, const result& right)
{
if (left.errors_ != right.errors_) return false;
if (left.warnings_ != right.warnings_) return false;
if (left.status_.state_ != right.status_.state_) return false;
if (left.status_.comment_ != right.status_.comment_) return false;
if (left.status_.progress_ != right.status_.progress_) return false;
return true;
}
template <typename T>
static void
print_deque(std::ostream& os,const std::deque<T> left,const std::deque<T> right)
{
auto const max(std::max(left.size(), right.size()));
for (size_t i(0); i < max; ++i)
{
os << "|\t'";
if (i < left.size())
os << left[i] << "'";
else
os << "'\t";
if (i < right.size())
os << "\t'" << right[i] << "'";
else
os << "\t''";
if (!equal(left[i], right[i])) os << "\t!!!";
os << "\n";
}
}
static void
print_logs(std::ostream& os, const logs& left, const logs& right)
{
os << "|\t" << left.msg_.size() << "\t" << right.msg_.size() << "\n";
print_deque(os, left.tstamp_, right.tstamp_);
print_deque(os, left.msg_, right.msg_);
}
// print two results against each other
static std::string
print(const result& left, const result& right, size_t it)
{
std::ostringstream os;
os << std::showpoint << std::setprecision(18);
os << "Iteration " << it << "\nerrors:\n";
print_logs(os, left.errors_, right.errors_);
os << "warnings:\n";
print_logs(os, left.warnings_, right.warnings_);
os << "state:\n";
os << "\t" << left.status_.state_ << "\t" << right.status_.state_
<< "\n";
os << "\t" << left.status_.comment_ << "\t" << right.status_.comment_
<< "\n";
os << "\t" << left.status_.progress_ << "\t" << right.status_.progress_
<< "\n";
return os.str();
}
#define VERIFY_RESULT(left, right, it) \
BOOST_CHECK_MESSAGE(left == right, print(left, right, it));
static const char*
const REPORT = "report.json";
static auto
const indefinite(wsrep::reporter::indefinite);
static struct logs
const LOGS_INIT = { std::deque<double>(), std::deque<std::string>() };
static struct result
const RES_INIT = { LOGS_INIT, LOGS_INIT,
{ "DISCONNECTED", "Disconnected", indefinite } };
static void
test_log(const char* const fname,
wsrep::reporter& rep,
result& check,
wsrep::reporter::log_level const lvl,
double const tstamp,
const std::string& msg,
size_t const iteration)
{
// this is implementaiton detail, if it changes in the code, it needs
// to be changed here
size_t const MAX_ERROR(4);
logs& log(lvl == wsrep::reporter::error ? check.errors_ : check.warnings_);
log.tstamp_.push_back(tstamp);
if (log.tstamp_.size() > MAX_ERROR) log.tstamp_.pop_front();
log.msg_.push_back(msg);
if (log.msg_.size() > MAX_ERROR) log.msg_.pop_front();
rep.report_log_msg(lvl, msg, tstamp);
auto value = read_file(fname);
auto res = RES_INIT;
parse_result(value, res);
VERIFY_RESULT(res, check, iteration);
}
static size_t const MAX_MSG = 4;
BOOST_AUTO_TEST_CASE(log_msg_test)
{
wsrep::default_mutex m;
wsrep::reporter rep(m, REPORT, MAX_MSG);
auto value = read_file(REPORT);
BOOST_REQUIRE(value != nullptr);
struct result res(RES_INIT), check(RES_INIT);
parse_result(value, res);
VERIFY_RESULT(res, check, -1);
struct entry
{
double tstamp_;
std::string msg_;
};
std::vector<entry> msgs =
{
{ 0.1, "a" },
{ 0.2, "bb" },
{ 0.3, "ccc" },
{ 0.4, "dddd" },
{ 0.5, "eeeee" },
{ 0.6, "ffffff" }
};
for (size_t i(0); i < msgs.size(); ++i)
{
test_log(REPORT, rep, check,
wsrep::reporter::error, msgs[i].tstamp_, msgs[i].msg_, i);
test_log(REPORT, rep, check,
wsrep::reporter::warning, msgs[i].tstamp_, msgs[i].msg_, i);
}
// test indefinite timestmap
std::string const msg("err");
rep.report_log_msg(wsrep::reporter::error, msg);
value = read_file(REPORT);
res = RES_INIT;
parse_result(value, res);
BOOST_REQUIRE(res.errors_.tstamp_.back() > 0);
BOOST_REQUIRE(res.errors_.msg_.back() == msg);
::unlink(REPORT);
}
BOOST_AUTO_TEST_CASE(state_test)
{
using wsrep::server_state;
wsrep::default_mutex m;
wsrep::reporter rep(m, REPORT, MAX_MSG);
double const err_tstamp(timestamp());
std::string const err_msg("Error!");
struct test
{
struct
{
enum wsrep::server_state::state state;
float progress;
} input;
struct result output;
};
logs const ERRS_INIT = { {err_tstamp}, {err_msg} };
std::vector<test> tests =
{
{{ server_state::s_disconnected, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DISCONNECTED", "Disconnected", indefinite }}},
{{ server_state::s_initializing, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DISCONNECTED", "Initializing", indefinite }}},
{{ server_state::s_initialized, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DISCONNECTED", "Connecting", indefinite }}},
{{ server_state::s_connected, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "CONNECTED", "Waiting", indefinite }}},
{{ server_state::s_joiner, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "JOINING", "Receiving state", 0.0f }}},
{{ server_state::s_disconnecting, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DISCONNECTING", "Disconnecting", indefinite }}},
{{ server_state::s_disconnected, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DISCONNECTED", "Disconnected", indefinite }}},
{{ server_state::s_connected, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "CONNECTED", "Waiting", indefinite }}},
{{ server_state::s_joiner, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "JOINING", "Receiving SST", 0.0f }}},
{{ server_state::s_initializing, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "JOINING", "Initializing", 0.5f }}},
{{ server_state::s_initialized, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "JOINING", "Receiving IST", 0.6f }}},
{{ server_state::s_joined, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "JOINED", "Syncing", 0.0f }}},
{{ server_state::s_synced, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "SYNCED", "Operational", 1.0f }}},
{{ server_state::s_donor, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DONOR", "Donating SST", 0.0f }}},
{{ server_state::s_joined, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "JOINED", "Syncing", 0.0f }}},
{{ server_state::s_synced, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "SYNCED", "Operational", 1.0f }}},
{{ server_state::s_disconnecting, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DISCONNECTING", "Disconnecting", indefinite }}},
};
rep.report_log_msg(wsrep::reporter::error, err_msg, err_tstamp);
for (auto i(tests.begin()); i != tests.end(); ++i)
{
rep.report_state(i->input.state, i->input.progress);
auto value = read_file(REPORT);
result res(RES_INIT);
parse_result(value, res);
auto check(i->output);
VERIFY_RESULT(res, check, i - tests.begin());
}
::unlink(REPORT);
}
BOOST_AUTO_TEST_CASE(progress_test)
{
using wsrep::server_state;
wsrep::default_mutex m;
wsrep::reporter rep(m, REPORT, MAX_MSG);
double const warn_tstamp(timestamp());
std::string const warn_msg("Warn!");
struct test
{
struct
{
enum wsrep::server_state::state state;
float progress;
} input;
struct result output;
};
logs const WARN_INIT = { {warn_tstamp}, {warn_msg} };
std::vector<test> tests =
{
{{ server_state::s_initialized, 0 },
{ LOGS_INIT, WARN_INIT,
{ "DISCONNECTED", "Connecting", indefinite }}},
{{ server_state::s_connected, 0 },
{ LOGS_INIT, WARN_INIT,
{ "CONNECTED", "Waiting", indefinite }}},
{{ server_state::s_joiner, 0 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Receiving state", 0.0f }}},
{{ server_state::s_joiner, 0.5 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Receiving state", 0.5f }}},
{{ server_state::s_disconnected, 0 },
{ LOGS_INIT, WARN_INIT,
{ "DISCONNECTED", "Disconnected", indefinite }}},
{{ server_state::s_connected, 0 },
{ LOGS_INIT, WARN_INIT,
{ "CONNECTED", "Waiting", indefinite }}},
{{ server_state::s_joiner, 0 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Receiving SST", 0.0f }}},
{{ server_state::s_joiner, 0.5 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Receiving SST", 0.25f }}},
{{ server_state::s_initializing, 0 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Initializing", 0.5f }}},
{{ server_state::s_initializing, 0.5 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Initializing", 0.55f }}},
{{ server_state::s_initialized, 0 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Receiving IST", 0.6f }}},
{{ server_state::s_initialized, 0.5 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Receiving IST", 0.8f }}},
{{ server_state::s_joined, 0 },
{ LOGS_INIT, WARN_INIT,
{ "JOINED", "Syncing", 0.0f }}},
{{ server_state::s_joined, 0.5 },
{ LOGS_INIT, WARN_INIT,
{ "JOINED", "Syncing", 0.5f }}},
{{ server_state::s_synced, 0 },
{ LOGS_INIT, WARN_INIT,
{ "SYNCED", "Operational", 1.0f }}},
{{ server_state::s_donor, 0 },
{ LOGS_INIT, WARN_INIT,
{ "DONOR", "Donating SST", 0.0f }}},
{{ server_state::s_donor, 0.5 },
{ LOGS_INIT, WARN_INIT,
{ "DONOR", "Donating SST", 0.5f }}},
{{ server_state::s_joined, 0 },
{ LOGS_INIT, WARN_INIT,
{ "JOINED", "Syncing", 0.0f }}},
{{ server_state::s_synced, 0 },
{ LOGS_INIT, WARN_INIT,
{ "SYNCED", "Operational", 1.0f }}},
};
rep.report_log_msg(wsrep::reporter::warning, warn_msg, warn_tstamp);
for (auto i(tests.begin()); i != tests.end(); ++i)
{
rep.report_state(i->input.state, i->input.progress);
auto value = read_file(REPORT);
result res(RES_INIT);
parse_result(value, res);
auto check(i->output);
VERIFY_RESULT(res, check, i - tests.begin());
}
::unlink(REPORT);
}