Skip to content

Commit 1494bec

Browse files
authored
Merge pull request #8 from innogames/tech_27700
Add support for draining LB Nodes
2 parents f019fca + bb4564a commit 1494bec

40 files changed

+1309
-1130
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
*.o
33
obj/*
44
testtool
5+
build

CMakeLists.txt

+86-14
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,106 @@
11
cmake_minimum_required(VERSION 3.11)
22
project(testtool)
3+
enable_testing()
34

45
set(default_build_type "RelWithDebInfo")
56

67
add_compile_options(
78
"-Wall" "-pedantic" "-std=c++11"
89
)
910

10-
include_directories(/usr/local/include)
11-
link_directories(/usr/local/lib)
11+
if (APPLE)
12+
# Include packages installed from brew
13+
set(OPENSSL_ROOT_DIR /usr/local/opt/openssl)
14+
set(nlohmann_json_DIR /usr/local/opt/nlohmann-json/lib/cmake/nlohmann_json/)
15+
endif()
1216

1317
set(Boost_USE_STATIC_LIBS ON)
14-
find_package(OpenSSL REQUIRED)
1518
find_package(Boost REQUIRED COMPONENTS system)
16-
find_package(PostgreSQL REQUIRED)
19+
list(APPEND _include_dirs ${Boost_INCLUDE_DIR})
20+
list(APPEND _libs ${Boost_LIBRARIES})
21+
22+
find_package(fmt 3.3.0 REQUIRED)
23+
1724
find_package(nlohmann_json 3.3.0 REQUIRED)
25+
if (APPLE)
26+
# Does not provide include dir as it seems
27+
list(APPEND _include_dirs /usr/local/opt/nlohmann-json/include)
28+
endif()
29+
list(APPEND _libs nlohmann_json::nlohmann_json)
1830

19-
file(GLOB testtool_src "src/*.cpp")
20-
add_executable(testtool ${testtool_src})
21-
target_link_libraries(testtool
22-
event.a event_core.a event_pthreads.a event_openssl.a
23-
thr.a pq.a intl.a
24-
fmt
25-
${OPENSSL_LIBRARIES}
26-
${Boost_LIBRARIES}
27-
nlohmann_json::nlohmann_json
28-
)
31+
# find_package(libevent REQUIRED) # Does not come with cmake support?
32+
# Libraries will hopefully be in default directory
33+
if (APPLE)
34+
list(APPEND _include_dirs /usr/local/opt/libevent/include)
35+
list(APPEND _link_dirs /usr/local/opt/libevent/lib)
36+
endif()
37+
if(UNIX AND NOT APPLE AND NOT LINUX)
38+
set(_link_dirs /usr/local/lib)
39+
set(_include_dirs /usr/local/include)
40+
endif()
41+
list(APPEND _libs event.a event_core.a event_pthreads.a event_openssl.a)
42+
43+
find_package(OpenSSL REQUIRED)
44+
list(APPEND _include_dirs ${OPENSSL_INCLUDE_DIR})
45+
list(APPEND _libs ${OPENSSL_LIBRARIES})
46+
47+
find_package(PostgreSQL REQUIRED)
48+
list(APPEND _libs libpq.a)
49+
50+
find_package(yaml-cpp REQUIRED)
51+
list(APPEND _include_dirs ${YAML_CPP_INCLUDE_DIR})
52+
list(APPEND _libs ${YAML_CPP_LIBRARIES})
53+
54+
if(UNIX AND NOT APPLE AND NOT LINUX)
55+
# Only on FreeBSD
56+
list(APPEND _libs thr.a)
57+
list(APPEND _libs libintl.a)
58+
endif()
59+
60+
include_directories(${_include_dirs})
61+
link_directories(${_link_dirs})
62+
63+
file(GLOB testtool_sources "src/*.cpp")
64+
file(GLOB testtool_headers "src/*.h")
65+
foreach(testtool_source ${testtool_sources})
66+
string(REGEX REPLACE ".*/" "" testtool_library ${testtool_source})
67+
add_library("${testtool_library}" OBJECT ${testtool_source} ${testool_headers})
68+
list(APPEND testtool_libraries ${testtool_library})
69+
endforeach()
70+
71+
add_executable(testtool)
72+
target_link_libraries(testtool ${_libs} ${testtool_libraries})
2973
install(TARGETS testtool DESTINATION sbin)
3074
install(
3175
PROGRAMS rc/testtool_freebsd
3276
DESTINATION etc/rc.d
3377
RENAME testtool
3478
)
79+
80+
list(APPEND testtool_test_libraries ${testtool_libraries})
81+
list(FILTER testtool_test_libraries EXCLUDE REGEX "^(msg|pfctl_worker|pfctl|testtool).cpp$")
82+
83+
find_package(GTest REQUIRED)
84+
include(GoogleTest)
85+
86+
configure_file("tests/cmake_dirs.h.in" ${CMAKE_BINARY_DIR}/generated/cmake_dirs.h)
87+
include_directories(${CMAKE_BINARY_DIR}/generated ${CMAKE_SOURCE_DIR}/src)
88+
89+
file(GLOB testtool_test_src "tests/*.cpp" "tests/*.h")
90+
add_executable(testtool_test ${testtool_test_src})
91+
add_dependencies(testtool_test ${testtool_test_libraries})
92+
gtest_discover_tests(testtool_test)
93+
94+
target_link_libraries(testtool_test
95+
GTest::GTest
96+
GTest::Main
97+
${testtool_test_libraries}
98+
${_libs}
99+
)
100+
101+
# Create autoconf-compatible "check" commmand which also fixes
102+
# the issue of CMake not including dependency from test to test binaries
103+
add_custom_target(check
104+
COMMAND ${CMAKE_CTEST_COMMAND}
105+
DEPENDS testtool_test
106+
)

README.rst

+20-5
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ HTTP and HTTPS
3131

3232
The check sends the following HTTP request::
3333

34-
$hc_query HTTP/1.1
35-
Host: $ip_addr
36-
Connection: close
34+
$hc_query HTTP/1.1
35+
Host: $ip_addr
36+
User-agent: testtool
37+
Connection: close
3738

3839
The request is sent to the specified port, or port 80 for `http` or port
3940
443 for `https`. Health check port number has nothing to do with forwarded
@@ -51,8 +52,22 @@ Fails on:
5152
Type-specific attributes:
5253

5354
* `hc_port`: Port number to connect to
54-
* `hc_query`: HTTP method and the URL (path) to test
55-
(e.g. "HEAD /backend/lb_check.php")
55+
* `hc_query`: HTTP method and the URL (path) to test, like
56+
"HEAD /backend/lb_check.php?pool={POOL_NAME}".
57+
There are a few macros available which can be put into the query.
58+
For expansion of a single IP address the address of the same address family
59+
as the check is choosen. For expansion of multiple addresses both IPv4 and
60+
IPv6 addresses will be sent.
61+
62+
- `{POOL_NAME}` - hostname of currently checked LB Pool
63+
- `{POOL_ADDRESS}` - IP address of currently checked LB Pool
64+
- `{NODE_NAME}` - hostname of currently checked LB Node
65+
- `{NODE_ADDRESS}` - IP address of currently checked LB Node
66+
- `{ACTIVE_NODES_NAMES}` - hostnames of LB Nodes of currently checked LB Pool
67+
considered up by testtool
68+
- `{ACTIVE_NODES_ADDRESSES}` - IP addresses of LB Nodes of currently checked
69+
LB Pool considered up by testtool
70+
5671
* `hc_ok_codes`: List of HTTP codes treated as "OK"
5772
* `hc_host`: Host header to be sent
5873

bsd_port/Makefile

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
VALID_CATEGORIES+= igports
22
PORTNAME= testtool
3-
PORTVERSION= _PV_
3+
DISTVERSION= _DV_
44
CATEGORIES= igports
55

66
MAINTAINER= kajetan.staszkiewicz@innogames.de
77
COMMENT= Testtool checks health of loadbalanced services.
88

99
USES= cmake
1010

11-
LIB_DEPENDS= libfmt.so:devel/libfmt \
12-
libyaml-cpp.so:devel/yaml-cpp
11+
LIB_DEPENDS= libyaml-cpp.so:devel/yaml-cpp
1312

1413
BUILD_DEPENDS= ${NONEXISTENT}:devel/libevent \
1514
${NONEXISTENT}:devel/boost-libs \
1615
${NONEXISTENT}:databases/postgresql94-client \
17-
${NONEXISTENT}:devel/nlohmann-json
16+
${NONEXISTENT}:devel/nlohmann-json \
17+
${NONEXISTENT}:devel/googletest \
18+
${NONEXISTENT}:devel/libfmt
1819

1920
PLIST_FILES= /usr/local/sbin/testtool \
2021
/usr/local/etc/rc.d/testtool

src/config.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@ template bool safe_get(const nlohmann::json &, const char *, bool);
2929
template int safe_get(const nlohmann::json &, const char *, int);
3030
template std::string safe_get(const nlohmann::json &, const char *,
3131
std::string);
32-
template std::vector<std::string> safe_get(const nlohmann::json &, const char *,
33-
std::vector<std::string>);
32+
template std::vector<int> safe_get(const nlohmann::json &, const char *,
33+
std::vector<int>);

src/healthcheck.cpp

+61-38
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// Copyright (c) 2018 InnoGames GmbH
55
//
66

7+
#define FMT_HEADER_ONLY
8+
79
#include <fmt/format.h>
810
#include <fmt/printf.h>
911
#include <iostream>
@@ -15,6 +17,7 @@
1517
#include "config.h"
1618
#include "healthcheck.h"
1719
#include "healthcheck_dns.h"
20+
#include "healthcheck_dummy.h"
1821
#include "healthcheck_http.h"
1922
#include "healthcheck_ping.h"
2023
#include "healthcheck_postgres.h"
@@ -91,15 +94,15 @@ Healthcheck::Healthcheck(const nlohmann::json &config,
9194
this->ran = false;
9295

9396
// Initialize healthchecks state basing on state of parent node.
94-
// Proper initial state for the healthcheck quarantees no
97+
// Proper initial state for the healthcheck guarantees no
9598
// unnecessary messages.
96-
if (parent_lbnode->get_state() == LbNode::STATE_UP) {
97-
hard_state = STATE_UP;
98-
last_state = STATE_UP;
99+
if (parent_lbnode->is_up()) {
100+
hard_state = HealthcheckState::STATE_UP;
101+
last_state = HealthcheckState::STATE_UP;
99102
failure_counter = 0;
100103
} else {
101-
hard_state = STATE_DOWN;
102-
last_state = STATE_DOWN;
104+
hard_state = HealthcheckState::STATE_DOWN;
105+
last_state = HealthcheckState::STATE_DOWN;
103106
failure_counter = max_failed_checks;
104107
}
105108
}
@@ -130,10 +133,12 @@ Healthcheck *Healthcheck::healthcheck_factory(const nlohmann::json &config,
130133
new Healthcheck_postgres(config, _parent_lbnode, ip_address);
131134
else if (type == "dns")
132135
new_healthcheck = new Healthcheck_dns(config, _parent_lbnode, ip_address);
136+
else if (type == "dummy")
137+
new_healthcheck = new Healthcheck_dummy(config, _parent_lbnode, ip_address);
133138
else
134139
return NULL;
135140

136-
log(MSG_INFO, new_healthcheck, "state: created");
141+
log(MessageType::MSG_INFO, new_healthcheck, "state: created");
137142

138143
return new_healthcheck;
139144
}
@@ -158,7 +163,7 @@ int Healthcheck::schedule_healthcheck(struct timespec *now) {
158163
is_running = true;
159164

160165
if (verbose > 1)
161-
log(MSG_INFO, this, "scheduling");
166+
log(MessageType::MSG_INFO, this, "scheduling");
162167

163168
return true;
164169
}
@@ -176,26 +181,33 @@ void Healthcheck::finalize() {
176181
// the health check. If it wouldn't be called, the process is not
177182
// going to continue.
178183
void Healthcheck::end_check(HealthcheckResult result, string message) {
179-
msgType log_type;
184+
MessageType log_type;
180185
string statemsg;
181186

182187
switch (result) {
183-
case HC_PASS:
184-
log_type = MSG_STATE_UP;
185-
this->last_state = STATE_UP;
188+
case HealthcheckResult::HC_PASS:
189+
log_type = MessageType::MSG_STATE_UP;
190+
this->last_state = HealthcheckState::STATE_UP;
186191
statemsg = fmt::sprintf("state: up message: %s", message);
187192
this->handle_result(statemsg);
188193
break;
189194

190-
case HC_FAIL:
191-
log_type = MSG_STATE_DOWN;
192-
this->last_state = STATE_DOWN;
195+
case HealthcheckResult::HC_FAIL:
196+
log_type = MessageType::MSG_STATE_DOWN;
197+
this->last_state = HealthcheckState::STATE_DOWN;
193198
statemsg = fmt::sprintf("state: down message: %s", message);
194199
this->handle_result(statemsg);
195200
break;
196201

197-
case HC_PANIC:
198-
log_type = MSG_CRIT;
202+
case HealthcheckResult::HC_DRAIN:
203+
log_type = MessageType::MSG_STATE_DOWN;
204+
this->last_state = HealthcheckState::STATE_DRAIN;
205+
statemsg = fmt::sprintf("state: down with draining, message: %s", message);
206+
this->handle_result(statemsg);
207+
break;
208+
209+
case HealthcheckResult::HC_PANIC:
210+
log_type = MessageType::MSG_CRIT;
199211
statemsg = fmt::sprintf("state: failure message: %s", message);
200212
log(log_type, this, statemsg);
201213
exit(2);
@@ -212,33 +224,44 @@ void Healthcheck::end_check(HealthcheckResult result, string message) {
212224
void Healthcheck::handle_result(string message) {
213225
string fail_message;
214226
bool changed = false;
215-
int log_level = MSG_INFO;
227+
MessageType log_level = MessageType::MSG_INFO;
216228

217229
// If a healtcheck has passed, zero the failure counter.
218-
if (last_state == STATE_UP)
230+
if (last_state == HealthcheckState::STATE_UP)
219231
failure_counter = 0;
220232

221-
// Change from DOWN to UP. The healthcheck has passed again.
222-
if (hard_state == STATE_DOWN && last_state == STATE_UP) {
223-
hard_state = STATE_UP;
233+
if (hard_state == HealthcheckState::STATE_UP) {
234+
switch (last_state) {
235+
case HealthcheckState::STATE_UP:
236+
// No change, make compiler happy.
237+
break;
238+
case HealthcheckState::STATE_DOWN:
239+
// Change from UP to DOWN. The healthcheck has failed.
240+
changed = true;
241+
log_level = MessageType::MSG_STATE_DOWN;
242+
failure_counter++;
243+
fail_message =
244+
fmt::sprintf("failure: %d of %d", failure_counter, max_failed_checks);
245+
// Mark the hard DOWN state only after the number of failed checks is
246+
// reached.
247+
if (failure_counter >= max_failed_checks) {
248+
hard_state = HealthcheckState::STATE_DOWN;
249+
}
250+
break;
251+
case HealthcheckState::STATE_DRAIN:
252+
// Change from UP to DRAIN. The healthcheck has failed with draining.
253+
changed = true;
254+
log_level = MessageType::MSG_STATE_DOWN;
255+
// Make it fail immediately not waiting for max_failed.
256+
hard_state = HealthcheckState::STATE_DRAIN;
257+
break;
258+
}
259+
} else if (last_state == HealthcheckState::STATE_UP) {
260+
// Change from any state to UP. The healthcheck has passed again.
261+
hard_state = HealthcheckState::STATE_UP;
224262
failure_counter = 0;
225263
changed = true;
226-
log_level = MSG_STATE_UP;
227-
}
228-
// Change from UP to DOWN. The healthcheck has failed.
229-
else if (hard_state == STATE_UP && last_state == STATE_DOWN) {
230-
changed = true;
231-
log_level = MSG_STATE_DOWN;
232-
233-
failure_counter++;
234-
fail_message =
235-
fmt::sprintf("failure: %d of %d", failure_counter, max_failed_checks);
236-
237-
// Mark the hard DOWN state only after the number of failed checks is
238-
// reached.
239-
if (failure_counter >= max_failed_checks) {
240-
hard_state = STATE_DOWN;
241-
}
264+
log_level = MessageType::MSG_STATE_UP;
242265
}
243266

244267
if (changed || verbose) {

0 commit comments

Comments
 (0)