Skip to content

Commit 8ddd29d

Browse files
Add unit-tests for PerfdataWriterConnection
1 parent f2abc26 commit 8ddd29d

11 files changed

+202
-35
lines changed

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ if(ICINGA2_WITH_PERFDATA)
146146
perfdata-graphitewriter.cpp
147147
perfdata-influxdbwriter.cpp
148148
perfdata-opentsdbwriter.cpp
149+
perfdata-perfdatawriterconnection.cpp
149150
$<TARGET_OBJECTS:perfdata>
150151
)
151152
endif()

test/perfdata-elasticsearchwriter.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
#include <BoostTestTargetConfig.h>
44
#include "perfdata/elasticsearchwriter.hpp"
55
#include "test/base-testloggerfixture.hpp"
6-
#include "test/perfdata-perfdatawriterconnectionfixture.hpp"
76
#include "test/perfdata-perfdatawriterfixture.hpp"
7+
#include "test/utils.hpp"
88

99
using namespace icinga;
1010

@@ -32,7 +32,7 @@ BOOST_AUTO_TEST_CASE(connect)
3232
BOOST_AUTO_TEST_CASE(pause_with_pending_work)
3333
{
3434
ReceiveCheckResults(1, ServiceState::ServiceCritical, [](const CheckResult::Ptr& cr) {
35-
cr->SetOutput(GetRandomString("####", 1024 * 1024));
35+
cr->SetOutput(GetRandomString("####", 1024UL * 1024));
3636
});
3737

3838
// Accept the connection, but don't read from it to leave the client hanging.

test/perfdata-gelfwriter.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "perfdata/gelfwriter.hpp"
55
#include "test/base-testloggerfixture.hpp"
66
#include "test/perfdata-perfdatawriterfixture.hpp"
7+
#include "test/utils.hpp"
78

89
using namespace icinga;
910

@@ -29,7 +30,7 @@ BOOST_AUTO_TEST_CASE(pause_with_pending_work)
2930
{
3031
// Make GelfWriter fill up the connection's buffer with a huge check-result.
3132
ReceiveCheckResults(1, ServiceState::ServiceCritical, [](const CheckResult::Ptr& cr) {
32-
cr->SetOutput(GetRandomString("####", 1024 * 1024));
33+
cr->SetOutput(GetRandomString("####", 1024UL * 1024));
3334
});
3435

3536
// Accept the connection, but only read far enough so we know the writer is now stuck.

test/perfdata-graphitewriter.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
#include "perfdata/graphitewriter.hpp"
55
#include "test/base-testloggerfixture.hpp"
66
#include "test/perfdata-perfdatawriterfixture.hpp"
7+
#include "test/utils.hpp"
78

89
using namespace icinga;
910

10-
BOOST_FIXTURE_TEST_SUITE(perfdata_graphitewriter, PerfdataWriterFixture<GraphiteWriter>,
11-
*boost::unit_test::label("perfdata"))
11+
BOOST_FIXTURE_TEST_SUITE(
12+
perfdata_graphitewriter,
13+
PerfdataWriterFixture<GraphiteWriter>,
14+
*boost::unit_test::label("perfdata")
15+
)
1216

1317
BOOST_AUTO_TEST_CASE(connect)
1418
{
@@ -27,7 +31,7 @@ BOOST_AUTO_TEST_CASE(pause_with_pending_work)
2731
{
2832
// Make GraphiteWriter send a huge message that fills up the connection's buffer.
2933
ReceiveCheckResults(1, ServiceState::ServiceCritical, [&](const CheckResult::Ptr& cr) {
30-
cr->GetPerformanceData()->Add(new PerfdataValue{GetRandomString("aaaa", 24 * 1024 * 1024), 1});
34+
cr->GetPerformanceData()->Add(new PerfdataValue{GetRandomString("aaaa", 24UL * 1024 * 1024), 1});
3135
});
3236

3337
// Accept the connection, but don't read from it to leave the client hanging.

test/perfdata-opentsdbwriter.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "perfdata/opentsdbwriter.hpp"
55
#include "test/base-testloggerfixture.hpp"
66
#include "test/perfdata-perfdatawriterfixture.hpp"
7+
#include "test/utils.hpp"
78

89
using namespace icinga;
910

test/perfdata-perfdatawriterconnectionfixture.hpp renamed to test/perfdata-perfdatatargetfixture.hpp

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,36 @@
55
#include <BoostTestTargetConfig.h>
66
#include "base/io-engine.hpp"
77
#include "base/json.hpp"
8+
#include "base/tlsstream.hpp"
89
#include <boost/algorithm/string/classification.hpp>
910
#include <boost/algorithm/string/split.hpp>
1011
#include <boost/asio/read_until.hpp>
1112
#include <boost/asio/streambuf.hpp>
13+
#include <boost/asio/use_future.hpp>
1214
#include <boost/beast/http.hpp>
1315
#include <boost/beast/http/message.hpp>
1416
#include <boost/beast/http/parser.hpp>
1517
#include <boost/beast/http/string_body.hpp>
18+
#include <future>
1619

1720
namespace icinga {
1821

19-
class PerfdataWriterConnectionFixture
22+
class PerfdataWriterTargetFixture
2023
{
2124
public:
22-
PerfdataWriterConnectionFixture()
23-
: m_Socket(IoEngine::Get().GetIoContext()),
25+
PerfdataWriterTargetFixture()
26+
: icinga::PerfdataWriterTargetFixture(Shared<AsioTcpStream>::Make(IoEngine::Get().GetIoContext()))
27+
{
28+
}
29+
30+
explicit PerfdataWriterTargetFixture(const Shared<boost::asio::ssl::context>::Ptr& sslCtx)
31+
: icinga::PerfdataWriterTargetFixture(Shared<AsioTlsStream>::Make(IoEngine::Get().GetIoContext(), *sslCtx))
32+
{
33+
m_SslContext = sslCtx;
34+
}
35+
36+
explicit PerfdataWriterTargetFixture(AsioTlsOrTcpStream stream)
37+
: m_Stream(std::move(stream)),
2438
m_Acceptor(
2539
IoEngine::Get().GetIoContext(),
2640
boost::asio::ip::tcp::endpoint{boost::asio::ip::address_v4::loopback(), 0}
@@ -31,13 +45,29 @@ class PerfdataWriterConnectionFixture
3145
m_Acceptor.listen();
3246
}
3347

34-
unsigned int GetPort() { return m_Acceptor.local_endpoint().port(); }
48+
unsigned short GetPort() { return m_Acceptor.local_endpoint().port(); }
49+
50+
auto AsyncAccept()
51+
{
52+
return std::visit(
53+
[&](auto& stream) { return m_Acceptor.async_accept(stream->lowest_layer(), boost::asio::use_future); },
54+
m_Stream
55+
);
56+
}
3557

3658
void Accept()
3759
{
38-
boost::system::error_code ec;
39-
m_Acceptor.accept(m_Socket);
40-
BOOST_REQUIRE_MESSAGE(!ec, ec.message());
60+
auto f = AsyncAccept();
61+
BOOST_REQUIRE_NO_THROW(f.get());
62+
}
63+
64+
void Handshake()
65+
{
66+
BOOST_REQUIRE(std::holds_alternative<Shared<AsioTlsStream>::Ptr>(m_Stream));
67+
using handshake_type = UnbufferedAsioTlsStream::handshake_type;
68+
auto & stream = std::get<Shared<AsioTlsStream>::Ptr>(m_Stream);
69+
BOOST_REQUIRE_NO_THROW(stream->next_layer().handshake(handshake_type::server));
70+
BOOST_REQUIRE(stream->next_layer().IsVerifyOK());
4171
}
4272

4373
std::string GetRequestBody()
@@ -46,9 +76,8 @@ class PerfdataWriterConnectionFixture
4676
using namespace boost::beast;
4777

4878
boost::system::error_code ec;
49-
// flat_buffer buf;
5079
http::request_parser<boost::beast::http::string_body> parser;
51-
http::read(m_Socket, m_Buffer, parser, ec);
80+
std::visit([&](auto& stream) { http::read(*stream, m_Buffer, parser, ec); }, m_Stream);
5281
BOOST_REQUIRE(!ec);
5382

5483
return parser.get().body();
@@ -79,7 +108,10 @@ class PerfdataWriterConnectionFixture
79108
using namespace boost::asio::ip;
80109

81110
boost::system::error_code ec;
82-
auto bytesRead = boost::asio::read_until(m_Socket, m_Buffer, std::forward<T>(delim), ec);
111+
auto bytesRead = std::visit(
112+
[&](auto& stream) { return boost::asio::read_until(*stream, m_Buffer, std::forward<T>(delim), ec); },
113+
m_Stream
114+
);
83115
BOOST_REQUIRE_MESSAGE(!ec, ec.message());
84116

85117
std::string ret{
@@ -98,16 +130,20 @@ class PerfdataWriterConnectionFixture
98130
boost::system::error_code ec;
99131
http::response<boost::beast::http::empty_body> response;
100132
response.result(status);
101-
http::write(m_Socket, response, ec);
133+
std::visit([&](auto& stream) { http::write(*stream, response, ec); }, m_Stream);
102134
BOOST_REQUIRE_MESSAGE(!ec, ec.message());
103135
}
104136

105-
void CloseConnection() { m_Socket.lowest_layer().close(); }
137+
void CloseConnection()
138+
{
139+
std::visit([&](auto& stream) { stream->lowest_layer().close(); }, m_Stream);
140+
}
106141

107142
private:
108143
boost::asio::streambuf m_Buffer;
109-
boost::asio::ip::tcp::socket m_Socket;
144+
AsioTlsOrTcpStream m_Stream;
110145
boost::asio::ip::tcp::acceptor m_Acceptor;
146+
Shared<boost::asio::ssl::context>::Ptr m_SslContext;
111147
};
112148

113149
} // namespace icinga
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
2+
3+
#include <utility>
4+
5+
#include "perfdata/perfdatawriterconnection.hpp"
6+
#include "test/perfdata-perfdatatargetfixture.hpp"
7+
#include "test/perfdata-perfdatawriterfixture.hpp"
8+
#include "test/remote-certificate-fixture.hpp"
9+
#include "test/test-ctest.hpp"
10+
#include "test/utils.hpp"
11+
12+
using namespace icinga;
13+
14+
class TlsPerfdataWriterFixture : public CertificateFixture, public PerfdataWriterTargetFixture
15+
{
16+
public:
17+
// TODO: Improve initialization by constructing the stream fully here.
18+
TlsPerfdataWriterFixture() : PerfdataWriterTargetFixture(MakeContext())
19+
{
20+
auto pdwCert = EnsureCertFor("client");
21+
m_PdwSslContext = SetupSslContext(
22+
pdwCert.crtFile,
23+
pdwCert.keyFile,
24+
m_CaCrtFile.string(),
25+
"",
26+
DEFAULT_TLS_CIPHERS,
27+
DEFAULT_TLS_PROTOCOLMIN,
28+
DebugInfo()
29+
);
30+
31+
m_Conn = new PerfdataWriterConnection{"127.0.0.1", std::to_string(GetPort()), m_PdwSslContext};
32+
}
33+
34+
auto& GetConnection() { return *m_Conn; }
35+
36+
private:
37+
Shared<boost::asio::ssl::context>::Ptr MakeContext()
38+
{
39+
auto testCert = EnsureCertFor("server");
40+
return SetupSslContext(
41+
testCert.crtFile,
42+
testCert.keyFile,
43+
m_CaCrtFile.string(),
44+
"",
45+
DEFAULT_TLS_CIPHERS,
46+
DEFAULT_TLS_PROTOCOLMIN,
47+
DebugInfo()
48+
);
49+
}
50+
51+
Shared<boost::asio::ssl::context>::Ptr m_PdwSslContext;
52+
PerfdataWriterConnection::Ptr m_Conn;
53+
};
54+
55+
BOOST_FIXTURE_TEST_SUITE(perfdatawriterconnection, TlsPerfdataWriterFixture,
56+
*CTestProperties("FIXTURES_REQUIRED ssl_certs")
57+
*boost::unit_test::label("perfdata"))
58+
59+
BOOST_AUTO_TEST_CASE(tls_connection)
60+
{
61+
Logger::SetConsoleLogSeverity(LogDebug);
62+
Logger::EnableConsoleLog();
63+
BOOST_REQUIRE(!GetConnection().IsConnected());
64+
65+
std::thread mockTargetThread{[&]() {
66+
Accept();
67+
Handshake();
68+
auto ret = GetDataUntil('\0');
69+
BOOST_REQUIRE_EQUAL(ret, "foobar");
70+
}};
71+
72+
GetConnection().Send(boost::asio::const_buffer{"foobar", 7});
73+
74+
mockTargetThread.join();
75+
}
76+
77+
BOOST_AUTO_TEST_CASE(stuck_in_handshake)
78+
{
79+
BOOST_REQUIRE(!GetConnection().IsConnected());
80+
81+
std::thread mockTargetThread{[&]() {
82+
Accept();
83+
GetConnection().StartDisconnectTimeout(50ms);
84+
}};
85+
86+
BOOST_REQUIRE_EXCEPTION(
87+
GetConnection().Send(boost::asio::const_buffer{"foobar", 7}), boost::system::system_error, [&](const auto& ex) {
88+
return ex.code() == boost::system::errc::operation_canceled;
89+
}
90+
);
91+
92+
mockTargetThread.join();
93+
}
94+
95+
BOOST_AUTO_TEST_CASE(stuck_sending)
96+
{
97+
BOOST_REQUIRE(!GetConnection().IsConnected());
98+
99+
std::thread mockTargetThread{[&]() {
100+
Accept();
101+
Handshake();
102+
auto ret = GetDataUntil("#");
103+
BOOST_REQUIRE_EQUAL(ret, "foobar");
104+
GetConnection().StartDisconnectTimeout(50ms);
105+
}};
106+
107+
auto randomData = GetRandomString("foobar#", 1024UL * 1024);
108+
BOOST_REQUIRE_EXCEPTION(
109+
GetConnection().Send(boost::asio::const_buffer{randomData.data(), randomData.size()}),
110+
boost::system::system_error,
111+
[&](const auto& ex) { return ex.code() == boost::system::errc::operation_canceled; }
112+
);
113+
114+
mockTargetThread.join();
115+
}
116+
117+
BOOST_AUTO_TEST_SUITE_END()

test/perfdata-perfdatawriterfixture.hpp

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@
99
#include "config/configitem.hpp"
1010
#include "icinga/host.hpp"
1111
#include "test/base-testloggerfixture.hpp"
12-
#include "test/perfdata-perfdatawriterconnectionfixture.hpp"
12+
#include "test/perfdata-perfdatatargetfixture.hpp"
1313
#include <boost/hana.hpp>
14-
#include <boost/random.hpp>
1514

1615
namespace icinga {
1716

1817
template<typename Writer>
19-
class PerfdataWriterFixture : public PerfdataWriterConnectionFixture, public TestLoggerFixture
18+
class PerfdataWriterFixture : public PerfdataWriterTargetFixture, public TestLoggerFixture
2019
{
2120
public:
2221
PerfdataWriterFixture() : m_Writer(new Writer)
@@ -58,19 +57,6 @@ object Host "h1" {
5857
BOOST_REQUIRE(!m_Writer->IsPaused());
5958
}
6059

61-
static std::string GetRandomString(std::string prefix, std::size_t length)
62-
{
63-
std::string alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
64-
boost::random::mt19937 generator;
65-
boost::random::uniform_int_distribution<> distribution(0, alphabet.size() - 1);
66-
67-
for (auto i = 0U; i < length; i++) {
68-
prefix += alphabet[distribution(generator)];
69-
}
70-
71-
return prefix;
72-
}
73-
7460
/**
7561
* Make our test host receive a number of check-results.
7662
*

test/remote-certificate-fixture.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ struct CertificateFixture : ConfigurationDataDirFixture
3838
}
3939
}
4040

41+
CertificateFixture(const CertificateFixture&) = delete;
42+
CertificateFixture(CertificateFixture&&) = delete;
43+
CertificateFixture& operator=(const CertificateFixture&) = delete;
44+
CertificateFixture& operator=(CertificateFixture&&) = delete;
45+
4146
~CertificateFixture()
4247
{
4348
namespace fs = boost::filesystem;

test/utils.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <iomanip>
66
#include <sstream>
77
#include <boost/test/unit_test.hpp>
8+
#include <boost/random.hpp>
89

910
tm make_tm(std::string s)
1011
{
@@ -65,3 +66,16 @@ GlobalTimezoneFixture::~GlobalTimezoneFixture()
6566
#endif
6667
tzset();
6768
}
69+
70+
std::string GetRandomString(std::string prefix, std::size_t length)
71+
{
72+
std::string alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
73+
boost::random::mt19937 generator;
74+
boost::random::uniform_int_distribution<> distribution(0, alphabet.size() - 1);
75+
76+
for (auto i = 0U; i < length; i++) {
77+
prefix += alphabet[distribution(generator)];
78+
}
79+
80+
return prefix;
81+
}

0 commit comments

Comments
 (0)