First release of open-appsec source code

This commit is contained in:
roybarda
2022-10-26 19:33:19 +03:00
parent 3883109caf
commit a883352f79
1353 changed files with 276290 additions and 1 deletions

View File

@@ -0,0 +1,3 @@
add_library(health_check_manager health_check_manager.cc)
add_subdirectory(health_check_manager_ut)

View 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(); }

View 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"
)

View File

@@ -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);
}