mirror of
https://github.com/openappsec/openappsec.git
synced 2025-09-30 03:34:26 +03:00
First release of open-appsec source code
This commit is contained in:
3
components/health_check_manager/CMakeLists.txt
Executable file
3
components/health_check_manager/CMakeLists.txt
Executable file
@@ -0,0 +1,3 @@
|
||||
add_library(health_check_manager health_check_manager.cc)
|
||||
|
||||
add_subdirectory(health_check_manager_ut)
|
250
components/health_check_manager/health_check_manager.cc
Executable file
250
components/health_check_manager/health_check_manager.cc
Executable file
@@ -0,0 +1,250 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "health_check_manager.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
|
||||
#include "health_check_status/health_check_status.h"
|
||||
#include "i_rest_api.h"
|
||||
#include "config.h"
|
||||
#include "cereal/archives/json.hpp"
|
||||
#include "customized_cereal_map.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_HEALTH_CHECK_MANAGER);
|
||||
|
||||
class HealthCheckOnDemand : public ServerRest, Singleton::Consume<I_Health_Check_Manager>
|
||||
{
|
||||
public:
|
||||
void
|
||||
doCall() override
|
||||
{
|
||||
string output_path = getProfileAgentSettingWithDefault<string>(
|
||||
"/tmp/cpnano_health_check_output.txt",
|
||||
"agent.healthCheck.outputTmpFilePath"
|
||||
);
|
||||
ofstream health_check_output_file;
|
||||
health_check_output_file.open(output_path, ofstream::out | ofstream::trunc);
|
||||
|
||||
auto manager = Singleton::Consume<I_Health_Check_Manager>::by<HealthCheckOnDemand>();
|
||||
manager->printRepliesHealthStatus(health_check_output_file);
|
||||
|
||||
health_check_output_file.close();
|
||||
}
|
||||
};
|
||||
|
||||
class HealthCheckError
|
||||
{
|
||||
public:
|
||||
HealthCheckError(const string &comp_name, const string &error)
|
||||
:
|
||||
code_name(comp_name),
|
||||
is_internal(true)
|
||||
{
|
||||
message.push_back(error);
|
||||
}
|
||||
|
||||
template<class Archive>
|
||||
void
|
||||
serialize(Archive &ar)
|
||||
{
|
||||
ar(
|
||||
cereal::make_nvp("code", code_name),
|
||||
cereal::make_nvp("message", message),
|
||||
cereal::make_nvp("internal", is_internal)
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
string code_name;
|
||||
bool is_internal;
|
||||
vector<string> message;
|
||||
};
|
||||
|
||||
class HealthCheckValue
|
||||
{
|
||||
public:
|
||||
HealthCheckValue() = default;
|
||||
|
||||
HealthCheckValue(HealthCheckStatus raw_status, const map<string, HealthCheckStatusReply> &descriptions)
|
||||
:
|
||||
status(raw_status)
|
||||
{
|
||||
for (const pair<string, HealthCheckStatusReply> &single_stat : descriptions) {
|
||||
if (single_stat.second.getStatus() == HealthCheckStatus::HEALTHY) {
|
||||
dbgTrace(D_HEALTH_CHECK_MANAGER) << "Ignoring healthy status reply. Comp name: " << single_stat.first;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const pair<string, string> &status : single_stat.second.getExtendedStatus()) {
|
||||
errors.push_back(HealthCheckError(single_stat.first + " " + status.first, status.second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class Archive>
|
||||
void
|
||||
serialize(Archive &ar)
|
||||
{
|
||||
ar(
|
||||
cereal::make_nvp("status", HealthCheckStatusReply::convertHealthCheckStatusToStr(status)),
|
||||
cereal::make_nvp("errors", errors)
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
HealthCheckStatus status = HealthCheckStatus::IGNORED;
|
||||
vector<HealthCheckError> errors;
|
||||
};
|
||||
|
||||
class HealthCheckPatch : public ClientRest
|
||||
{
|
||||
public:
|
||||
HealthCheckPatch(HealthCheckStatus raw_status, const map<string, HealthCheckStatusReply> &descriptions)
|
||||
{
|
||||
health_check = HealthCheckValue(raw_status, descriptions);
|
||||
}
|
||||
|
||||
C2S_LABEL_PARAM(HealthCheckValue, health_check, "healthCheck");
|
||||
};
|
||||
|
||||
class HealthCheckManager::Impl
|
||||
:
|
||||
Singleton::Provide<I_Health_Check_Manager>::From<HealthCheckManager>
|
||||
{
|
||||
public:
|
||||
void
|
||||
init()
|
||||
{
|
||||
auto rest = Singleton::Consume<I_RestApi>::by<HealthCheckManager>();
|
||||
rest->addRestCall<HealthCheckOnDemand>(RestAction::SHOW, "health-check-on-demand");
|
||||
|
||||
int interval_in_seconds =
|
||||
getProfileAgentSettingWithDefault<int>(30, "agent.healthCheck.intervalInSeconds");
|
||||
|
||||
auto i_mainloop = Singleton::Consume<I_MainLoop>::by<HealthCheckManager>();
|
||||
i_mainloop->addRecurringRoutine(
|
||||
I_MainLoop::RoutineType::System,
|
||||
chrono::seconds(interval_in_seconds),
|
||||
[this]() { executeHealthCheck(); },
|
||||
"Health check manager periodic check"
|
||||
);
|
||||
|
||||
auto is_orch = Singleton::Consume<I_Environment>::by<HealthCheckManager>()->get<bool>("Is Orchestrator");
|
||||
should_patch_report = is_orch.ok() && *is_orch;
|
||||
}
|
||||
|
||||
HealthCheckStatus
|
||||
getAggregatedStatus()
|
||||
{
|
||||
executeHealthCheck();
|
||||
return general_health_aggregated_status;
|
||||
}
|
||||
|
||||
void
|
||||
printRepliesHealthStatus(ofstream &oputput_file)
|
||||
{
|
||||
getRegisteredComponentsHealthStatus();
|
||||
cereal::JSONOutputArchive ar(oputput_file);
|
||||
ar(cereal::make_nvp("allComponentsHealthCheckReplies", all_comps_health_status));
|
||||
}
|
||||
|
||||
private:
|
||||
bool
|
||||
sendHealthCheckPatch()
|
||||
{
|
||||
dbgFlow(D_HEALTH_CHECK_MANAGER);
|
||||
|
||||
HealthCheckPatch patch_to_send(general_health_aggregated_status, all_comps_health_status);
|
||||
auto messaging = Singleton::Consume<I_Messaging>::by<HealthCheckManager>();
|
||||
return messaging->sendNoReplyObject(patch_to_send, I_Messaging::Method::PATCH, "/agents");
|
||||
}
|
||||
|
||||
void
|
||||
getRegisteredComponentsHealthStatus()
|
||||
{
|
||||
vector<HealthCheckStatusReply> health_check_event_reply = HealthCheckStatusEvent().query();
|
||||
all_comps_health_status.clear();
|
||||
for (const auto &reply : health_check_event_reply) {
|
||||
if (reply.getStatus() != HealthCheckStatus::IGNORED) {
|
||||
all_comps_health_status.emplace(reply.getCompName(), reply);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
calcGeneralHealthAggregatedStatus()
|
||||
{
|
||||
general_health_aggregated_status = HealthCheckStatus::HEALTHY;
|
||||
|
||||
for (const pair<string, HealthCheckStatusReply> &reply : all_comps_health_status) {
|
||||
HealthCheckStatus status = reply.second.getStatus();
|
||||
|
||||
dbgTrace(D_HEALTH_CHECK_MANAGER)
|
||||
<< "Current aggregated status is: "
|
||||
<< HealthCheckStatusReply::convertHealthCheckStatusToStr(
|
||||
general_health_aggregated_status
|
||||
)
|
||||
<< ". Got health status: "
|
||||
<< HealthCheckStatusReply::convertHealthCheckStatusToStr(status)
|
||||
<< "for component: "
|
||||
<< reply.first;
|
||||
|
||||
switch (status) {
|
||||
case HealthCheckStatus::UNHEALTHY : {
|
||||
general_health_aggregated_status = HealthCheckStatus::UNHEALTHY;
|
||||
return;
|
||||
}
|
||||
case HealthCheckStatus::DEGRADED : {
|
||||
general_health_aggregated_status = HealthCheckStatus::DEGRADED;
|
||||
break;
|
||||
}
|
||||
case HealthCheckStatus::IGNORED : break;
|
||||
case HealthCheckStatus::HEALTHY : break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
executeHealthCheck()
|
||||
{
|
||||
dbgFlow(D_HEALTH_CHECK_MANAGER) << "Collecting health status from all registered components.";
|
||||
|
||||
getRegisteredComponentsHealthStatus();
|
||||
calcGeneralHealthAggregatedStatus();
|
||||
|
||||
dbgTrace(D_HEALTH_CHECK_MANAGER)
|
||||
<< "Aggregated status: "
|
||||
<< HealthCheckStatusReply::convertHealthCheckStatusToStr(general_health_aggregated_status);
|
||||
|
||||
if (!should_patch_report) return;
|
||||
|
||||
if (!sendHealthCheckPatch()) {
|
||||
dbgWarning(D_HEALTH_CHECK_MANAGER) << "Failed to send periodic health check patch to the fog";
|
||||
} else {
|
||||
dbgDebug(D_HEALTH_CHECK_MANAGER) << "Successfully sent periodic health check patch to the fog";
|
||||
};
|
||||
}
|
||||
|
||||
HealthCheckStatus general_health_aggregated_status;
|
||||
map<string, HealthCheckStatusReply> all_comps_health_status;
|
||||
bool should_patch_report;
|
||||
};
|
||||
|
||||
HealthCheckManager::HealthCheckManager() : Component("HealthCheckManager"), pimpl(make_unique<Impl>()) {}
|
||||
HealthCheckManager::~HealthCheckManager() {}
|
||||
|
||||
void HealthCheckManager::init() { pimpl->init(); }
|
8
components/health_check_manager/health_check_manager_ut/CMakeLists.txt
Executable file
8
components/health_check_manager/health_check_manager_ut/CMakeLists.txt
Executable file
@@ -0,0 +1,8 @@
|
||||
include_directories(${CMAKE_SOURCE_DIR}/components/include)
|
||||
link_directories(${BOOST_ROOT}/lib)
|
||||
|
||||
add_unit_test(
|
||||
health_check_manager_ut
|
||||
"health_check_manager_ut.cc"
|
||||
"singleton;mainloop;health_check_manager;event_is;metric;-lboost_regex"
|
||||
)
|
@@ -0,0 +1,219 @@
|
||||
#include "health_check_manager.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
|
||||
#include "health_check_status/health_check_status.h"
|
||||
#include "environment.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "cptest.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_messaging.h"
|
||||
#include "mock/mock_rest_api.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
USE_DEBUG_FLAG(D_HEALTH_CHECK);
|
||||
|
||||
class TestHealthCheckStatusListener : public Listener<HealthCheckStatusEvent>
|
||||
{
|
||||
public:
|
||||
void upon(const HealthCheckStatusEvent &) override {}
|
||||
|
||||
HealthCheckStatusReply
|
||||
respond(const HealthCheckStatusEvent &) override
|
||||
{
|
||||
map<string, string> extended_status;
|
||||
extended_status["team"] = team;
|
||||
extended_status["city"] = city;
|
||||
HealthCheckStatusReply reply(comp_name, status, extended_status);
|
||||
return reply;
|
||||
}
|
||||
|
||||
void setStatus(HealthCheckStatus new_status) { status = new_status; }
|
||||
|
||||
string getListenerName() const { return "TestHealthCheckStatusListener"; }
|
||||
|
||||
private:
|
||||
static const string comp_name;
|
||||
HealthCheckStatus status = HealthCheckStatus::HEALTHY;
|
||||
static const string team;
|
||||
static const string city;
|
||||
};
|
||||
|
||||
const string TestHealthCheckStatusListener::comp_name = "Test";
|
||||
const string TestHealthCheckStatusListener::team = "Hapoel";
|
||||
const string TestHealthCheckStatusListener::city = "Tel-Aviv";
|
||||
|
||||
class TestEnd {};
|
||||
|
||||
class HealthCheckManagerTest : public Test
|
||||
{
|
||||
public:
|
||||
HealthCheckManagerTest()
|
||||
{
|
||||
Debug::setNewDefaultStdout(&debug_output);
|
||||
Debug::setUnitTestFlag(D_HEALTH_CHECK, Debug::DebugLevel::INFO);
|
||||
|
||||
EXPECT_CALL(mock_ml, addRecurringRoutine(_, _, _, _, _)).WillRepeatedly(
|
||||
DoAll(SaveArg<2>(&health_check_periodic_routine), Return(1))
|
||||
);
|
||||
|
||||
EXPECT_CALL(mock_rest, mockRestCall(RestAction::ADD, "declare-boolean-variable", _)).WillOnce(Return(true));
|
||||
|
||||
EXPECT_CALL(mock_rest, mockRestCall(RestAction::SHOW, "health-check-on-demand", _)).WillOnce(
|
||||
WithArg<2>(Invoke(this, &HealthCheckManagerTest::setHealthCheckOnDemand))
|
||||
);
|
||||
|
||||
env.preload();
|
||||
event_listener.registerListener();
|
||||
|
||||
env.init();
|
||||
|
||||
ScopedContext ctx;
|
||||
ctx.registerValue<bool>("Is Orchestrator", true);
|
||||
|
||||
health_check_manager.init();
|
||||
i_health_check_manager = Singleton::Consume<I_Health_Check_Manager>::from(health_check_manager);
|
||||
}
|
||||
|
||||
~HealthCheckManagerTest()
|
||||
{
|
||||
env.fini();
|
||||
Debug::setNewDefaultStdout(&cout);
|
||||
}
|
||||
|
||||
bool
|
||||
setHealthCheckOnDemand(const unique_ptr<RestInit> &rest_ptr)
|
||||
{
|
||||
health_check_server = rest_ptr->getRest();
|
||||
return true;
|
||||
}
|
||||
|
||||
I_MainLoop::Routine health_check_periodic_routine;
|
||||
StrictMock<MockMainLoop> mock_ml;
|
||||
StrictMock<MockRestApi> mock_rest;
|
||||
StrictMock<MockMessaging> mock_message;
|
||||
stringstream debug_output;
|
||||
ConfigComponent config;
|
||||
Config::I_Config *i_config = nullptr;
|
||||
::Environment env;
|
||||
HealthCheckManager health_check_manager;
|
||||
I_Health_Check_Manager *i_health_check_manager;
|
||||
unique_ptr<ServerRest> health_check_server;
|
||||
TestHealthCheckStatusListener event_listener;
|
||||
};
|
||||
|
||||
TEST_F(HealthCheckManagerTest, runPeriodicHealthCheckTest)
|
||||
{
|
||||
string actual_body;
|
||||
EXPECT_CALL(
|
||||
mock_message,
|
||||
sendMessage(
|
||||
false,
|
||||
_,
|
||||
I_Messaging::Method::PATCH,
|
||||
"/agents",
|
||||
"",
|
||||
_,
|
||||
_,
|
||||
MessageTypeTag::GENERIC
|
||||
)
|
||||
).Times(4).WillRepeatedly(DoAll(SaveArg<1>(&actual_body), Return(string())));
|
||||
|
||||
try {
|
||||
health_check_periodic_routine();
|
||||
} catch (const TestEnd &t) {}
|
||||
|
||||
HealthCheckStatus aggregated_status = i_health_check_manager->getAggregatedStatus();
|
||||
string aggregated_status_str = HealthCheckStatusReply::convertHealthCheckStatusToStr(aggregated_status);
|
||||
|
||||
string expected_healthy_body(
|
||||
"{\n"
|
||||
" \"healthCheck\": {\n"
|
||||
" \"status\": \"Healthy\",\n"
|
||||
" \"errors\": []\n"
|
||||
" }\n"
|
||||
"}"
|
||||
);
|
||||
EXPECT_EQ(actual_body, expected_healthy_body);
|
||||
EXPECT_EQ("Healthy", aggregated_status_str);
|
||||
|
||||
event_listener.setStatus(HealthCheckStatus::DEGRADED);
|
||||
try {
|
||||
health_check_periodic_routine();
|
||||
} catch (const TestEnd &t) {}
|
||||
|
||||
aggregated_status = i_health_check_manager->getAggregatedStatus();
|
||||
aggregated_status_str = HealthCheckStatusReply::convertHealthCheckStatusToStr(aggregated_status);
|
||||
|
||||
string expected_degraded_body(
|
||||
"{\n"
|
||||
" \"healthCheck\": {\n"
|
||||
" \"status\": \"Degraded\",\n"
|
||||
" \"errors\": [\n"
|
||||
" {\n"
|
||||
" \"code\": \"Test city\",\n"
|
||||
" \"message\": [\n"
|
||||
" \"Tel-Aviv\"\n"
|
||||
" ],\n"
|
||||
" \"internal\": true\n"
|
||||
" },\n"
|
||||
" {\n"
|
||||
" \"code\": \"Test team\",\n"
|
||||
" \"message\": [\n"
|
||||
" \"Hapoel\"\n"
|
||||
" ],\n"
|
||||
" \"internal\": true\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
"}"
|
||||
);
|
||||
EXPECT_EQ(actual_body, expected_degraded_body);
|
||||
EXPECT_EQ("Degraded", aggregated_status_str);
|
||||
}
|
||||
|
||||
TEST_F(HealthCheckManagerTest, runOnDemandHealthCheckTest)
|
||||
{
|
||||
const vector<string> health_check{""};
|
||||
CPTestTempfile health_check_tmp_file(health_check);
|
||||
|
||||
string config_json =
|
||||
"{"
|
||||
" \"agentSettings\": [\n"
|
||||
" {\n"
|
||||
" \"id\": \"yallaHapoel\",\n"
|
||||
" \"key\": \"agent.healthCheck.outputTmpFilePath\",\n"
|
||||
" \"value\": \"" + health_check_tmp_file.fname + "\"\n"
|
||||
" }]\n"
|
||||
"}";
|
||||
|
||||
istringstream ss(config_json);
|
||||
config.preload();
|
||||
Singleton::Consume<Config::I_Config>::from(config)->loadConfiguration(ss);
|
||||
|
||||
stringstream is;
|
||||
is << "{}";
|
||||
health_check_server->performRestCall(is);
|
||||
|
||||
string expected_status =
|
||||
"{\n"
|
||||
" \"allComponentsHealthCheckReplies\": {\n"
|
||||
" \"Test\": {\n"
|
||||
" \"status\": \"Healthy\",\n"
|
||||
" \"extendedStatus\": {\n"
|
||||
" \"city\": \"Tel-Aviv\",\n"
|
||||
" \"team\": \"Hapoel\"\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}";
|
||||
|
||||
string health_check_res = health_check_tmp_file.readFile();
|
||||
EXPECT_EQ(health_check_res, expected_status);
|
||||
}
|
Reference in New Issue
Block a user