Skip to content

Commit

Permalink
new(libsinsp_e2e): new misc tests
Browse files Browse the repository at this point in the history
Signed-off-by: Roberto Scolaro <[email protected]>
  • Loading branch information
therealbobo committed Apr 29, 2024
1 parent 7c60d90 commit fbf8f6f
Show file tree
Hide file tree
Showing 17 changed files with 1,829 additions and 7 deletions.
8 changes: 8 additions & 0 deletions test/libsinsp_e2e/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ configure_file (
)

add_executable(libsinsp_e2e_tests
capture_to_file_test.cpp
container/container.cpp
container/container_cgroup.cpp
container/docker_utils.cpp
Expand All @@ -50,6 +51,8 @@ add_executable(libsinsp_e2e_tests
threadinfo.cpp
thread_state.cpp
udp_client_server.cpp
unix_client_server.cpp
unix_udp_client_server.cpp
)

if(BUILD_BPF)
Expand Down Expand Up @@ -103,3 +106,8 @@ file(COPY
DESTINATION
${CMAKE_CURRENT_BINARY_DIR}/resources/
)

execute_process(
COMMAND tar xzf ${CMAKE_CURRENT_BINARY_DIR}/resources/fake-proc.tar.gz
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/resources/
)
73 changes: 73 additions & 0 deletions test/libsinsp_e2e/capture_to_file_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright (C) 2024 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 "sys_call_test.h"

#include <gtest/gtest.h>
#include <sys/stat.h>

#include <libsinsp/sinsp.h>

TEST_F(sys_call_test, can_consume_a_capture_file)
{
int callnum = 0;

event_filter_t filter = [&](sinsp_evt* evt)
{
std::string evt_name(evt->get_name());
return evt_name.find("stat") != std::string::npos &&
m_tid_filter(evt) && evt->get_direction() == SCAP_ED_OUT;
};

run_callback_t test = [](concurrent_object_handle<sinsp> inspector_handle)
{
struct stat sb;
for(int i = 0; i < 100; i++)
{
stat("/tmp", &sb);
}
};

captured_event_callback_t callback = [&](const callback_param& param) { callnum++; };
ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); });
EXPECT_EQ(100, callnum);

sinsp inspector;
sinsp_evt* event;

const ::testing::TestInfo* const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
auto filename = std::string(LIBSINSP_TEST_CAPTURES_PATH) + test_info->test_case_name() + "_" +
test_info->name() + ".scap";
inspector.open_savefile(filename);
callnum = 0;
int32_t res;
while((res = inspector.next(&event)) != SCAP_EOF)
{
ASSERT_EQ(SCAP_SUCCESS, res);
std::string evt_name(event->get_name());
if(evt_name.find("stat") != std::string::npos &&
m_tid_filter(event) && event->get_direction() == SCAP_ED_OUT)
{
callnum++;
}
}

ASSERT_EQ(SCAP_EOF, res);
ASSERT_EQ(100, callnum);
}
106 changes: 105 additions & 1 deletion test/libsinsp_e2e/container/container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ TEST_F(sys_call_test, container_libvirt)

if (system("virsh --help > /dev/null 2>&1") != 0)
{
printf("libvirt not installed, skipping test\n");
GTEST_SKIP() << "libvirt not installed, skipping test";
return;
}

Expand Down Expand Up @@ -722,6 +722,83 @@ static void healthcheck_helper(
ASSERT_EQ(cstate.healthcheck_seen, expect_healthcheck) << capture_stats_str;
}

static void healthcheck_tracefile_helper(
const std::string& dockerfile,
bool expect_healthcheck,
sinsp_threadinfo::command_category expected_cat = sinsp_threadinfo::CAT_HEALTHCHECK)
{
container_state cstate;

std::string build_cmdline("cd " LIBSINSP_TEST_RESOURCES_PATH "/docker/health_dockerfiles && docker build -t cont_health_ut_img -f "
+ dockerfile + " . > /dev/null 2>&1");
ASSERT_TRUE(system(build_cmdline.c_str()) == 0);

run_callback_t test = [](concurrent_object_handle<sinsp> inspector_handle)
{
// --network=none speeds up the container setup a bit.
ASSERT_TRUE((system("docker run --rm --network=none --name cont_health_ut cont_health_ut_img "
"/bin/sh -c '/bin/sleep 10' > /dev/null 2>&1")) == 0);
};

event_filter_t filter = [&](sinsp_evt* evt)
{
std::string evt_name(evt->get_name());
return evt_name.find("execve") != std::string::npos &&
evt->get_direction() == SCAP_ED_OUT;
};

captured_event_callback_t callback = [&](const callback_param& param) {return;};

ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); });

// Now reread the file we just wrote and pass it through
// update_container_state.

const ::testing::TestInfo* const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
auto dumpfile = std::string(LIBSINSP_TEST_CAPTURES_PATH) + test_info->test_case_name() + "_" +
test_info->name() + ".scap";

sinsp inspector;
inspector.set_hostname_and_port_resolution_mode(false);
inspector.set_filter("evt.type=execve and evt.dir=<");
inspector.open_savefile(dumpfile);
inspector.start_capture();

while (1)
{
sinsp_evt* ev;
int32_t res = inspector.next(&ev);

if (res == SCAP_TIMEOUT)
{
continue;
}
if (res == SCAP_FILTERED_EVENT)
{
continue;
}
else if (res == SCAP_EOF)
{
break;
}
ASSERT_TRUE(res == SCAP_SUCCESS);

update_container_state(&inspector, ev, cstate, expected_cat);
}

std::string capture_stats_str = capture_stats(&inspector);

inspector.stop_capture();
inspector.close();

ASSERT_TRUE(cstate.root_cmd_seen) << capture_stats_str;
ASSERT_TRUE(cstate.second_cmd_seen) << capture_stats_str;
ASSERT_EQ(cstate.container_w_health_probe, expect_healthcheck) << capture_stats_str;
ASSERT_EQ(cstate.healthcheck_seen, expect_healthcheck) << capture_stats_str;
}


// Run container w/o health check, should not find any health check
// for the container. Should not identify either the entrypoint
// or a second process spawned after as a health check process.
Expand Down Expand Up @@ -791,6 +868,33 @@ TEST_F(sys_call_test, docker_container_readiness_probe)
sinsp_threadinfo::CAT_READINESS_PROBE);
}

// Identical to above tests, but read events from a trace file instead
// of live. Only doing selected cases.
TEST_F(sys_call_test, docker_container_healthcheck_trace)
{
healthcheck_tracefile_helper("Dockerfile.healthcheck", true);
}

TEST_F(sys_call_test, docker_container_healthcheck_cmd_overlap_trace)
{
healthcheck_tracefile_helper("Dockerfile.healthcheck_cmd_overlap", true);
}

TEST_F(sys_call_test, docker_container_liveness_probe_trace)
{
healthcheck_tracefile_helper("Dockerfile.healthcheck_liveness",
true,
sinsp_threadinfo::CAT_LIVENESS_PROBE);
}

TEST_F(sys_call_test, docker_container_readiness_probe_trace)
{
healthcheck_tracefile_helper("Dockerfile.healthcheck_readiness",
true,
sinsp_threadinfo::CAT_READINESS_PROBE);
}


TEST_F(sys_call_test, docker_container_large_json)
{
bool saw_container_evt = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
foreach(
dockerfile
Dockerfile.healthcheck
Dockerfile.healthcheck_shell
Dockerfile.healthcheck_cmd_overlap
Dockerfile.healthcheck_liveness
Dockerfile.healthcheck_readiness
Dockerfile.no_healthcheck
Dockerfile.none_healthcheck
)

configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/${dockerfile} ${CMAKE_CURRENT_BINARY_DIR}/${dockerfile}
COPYONLY
)

endforeach(dockerfile)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM busybox
RUN cp /bin/true /bin/ut-health-check
HEALTHCHECK --interval=0.5s CMD ["/bin/ut-health-check"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM busybox
HEALTHCHECK --interval=0.5s CMD ["/bin/sh", "-c", "/bin/sleep 10"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM busybox
RUN cp /bin/true /bin/ut-health-check

# This container runs a docker healthcheck, but due to the
# annotation.... label, it gets interpretated as if it were a k8s
# liveness check.
HEALTHCHECK --interval=0.5s CMD ["/bin/ut-health-check"]
LABEL annotation.kubectl.kubernetes.io/last-applied-configuration="{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"name\":\"mysql-app\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"env\":[{\"name\":\"MYSQL_ROOT_PASSWORD\",\"value\":\"no\"}],\"image\":\"mstemm/mysql:healthcheck\",\"livenessProbe\":{\"exec\":{\"command\":[\"/bin/ut-health-check\"]},\"initialDelaySeconds\":5,\"periodSeconds\":5},\"name\":\"mysql\"}]}}\n"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM busybox
RUN cp /bin/true /bin/ut-health-check

# This container runs a docker healthcheck, but due to the
# annotation.... label, it gets interpretated as if it were a k8s
# readiness check.
HEALTHCHECK --interval=0.5s CMD ["/bin/ut-health-check"]
LABEL annotation.kubectl.kubernetes.io/last-applied-configuration="{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"name\":\"mysql-app\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"env\":[{\"name\":\"MYSQL_ROOT_PASSWORD\",\"value\":\"no\"}],\"image\":\"mstemm/mysql:healthcheck\",\"readinessProbe\":{\"exec\":{\"command\":[\"/bin/ut-health-check\"]},\"initialDelaySeconds\":5,\"periodSeconds\":5},\"name\":\"mysql\"}]}}\n"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM busybox
RUN cp /bin/true /bin/ut-health-check
HEALTHCHECK --interval=0.5s CMD /bin/ut-health-check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM busybox:latest
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM busybox:latest
HEALTHCHECK NONE
Binary file added test/libsinsp_e2e/resources/fake-proc.tar.gz
Binary file not shown.
42 changes: 42 additions & 0 deletions test/libsinsp_e2e/resources/unix_client_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
import socket
import os, os.path
import time
import sys

PAYLOAD = "0123456789QWERTYUIOPASDFGHJKLZXCVBNM"
NAME = "/tmp/python_unix_sockets_example"

if sys.argv[1] == 'server':
if os.path.exists(NAME):
os.remove(NAME)

server = socket.socket( socket.AF_UNIX, socket.SOCK_STREAM )
server.bind(NAME)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

print "STARTED"
server.listen(5)

connect, address = server.accept()
resp = connect.recv( 1024 )
connect.send(resp)
connect.close()
server.close()
os.remove(NAME)

else:
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

if os.path.exists(NAME):
client = socket.socket( socket.AF_UNIX, socket.SOCK_STREAM )
client.connect(NAME)

print "STARTED"

client.send(PAYLOAD)
resp = client.recv(1024)
client.close()

else:
print >> sys.stderr, "Couldn't Connect!"
Loading

0 comments on commit fbf8f6f

Please sign in to comment.