Skip to content

Commit

Permalink
tests: reproduce stored backend truncation bug
Browse files Browse the repository at this point in the history
When a chunk is filled to exactly chunk_size-1 bytes, another write will
ignore the remaining byte and will write to the next chunk. However, the
buffer written in this way will lose its last byte.

The new test sd.droplet_write_read will provoke this situation and check
that data can be re-read correctly.
  • Loading branch information
arogge committed Aug 26, 2021
1 parent b948074 commit 0ec05df
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 7 deletions.
11 changes: 9 additions & 2 deletions core/src/tests/CMakeLists.txt
Expand Up @@ -38,7 +38,9 @@ macro(bareos_gtest_add_tests testname)
message("NOT DISABELING ${found_tests}")
endif()

set_tests_properties(${found_tests} PROPERTIES TIMEOUT 120)
set_tests_properties(
${found_tests} PROPERTIES TIMEOUT 120 SKIP_RETURN_CODE 77
)
if(HAVE_WIN32)
set_tests_properties(
${found_tests} PROPERTIES ENVIRONMENT "WINEPATH=${WINEPATH}"
Expand Down Expand Up @@ -291,7 +293,9 @@ if(NOT client-only)
endif() # NOT client-only

bareos_add_test(thread_list LINK_LIBRARIES bareos GTest::gtest_main)
set_tests_properties(gtest:thread_list.thread_list_startup_and_shutdown PROPERTIES LABELS broken)
set_tests_properties(
gtest:thread_list.thread_list_startup_and_shutdown PROPERTIES LABELS broken
)

bareos_add_test(
thread_specific_data LINK_LIBRARIES bareos Threads::Threads GTest::gtest_main
Expand Down Expand Up @@ -330,6 +334,9 @@ endif()

if(NOT client-only)
bareos_add_test(sd_backend LINK_LIBRARIES ${LINK_LIBRARIES})
if(TARGET droplet)
target_compile_definitions(sd_backend PRIVATE HAVE_DROPLET)
endif()
endif()

if(NOT client-only)
Expand Down
2 changes: 2 additions & 0 deletions core/src/tests/configs/sd_backend/.gitignore
@@ -0,0 +1,2 @@
droplet.profile
bareos-sd.d/device/droplet.conf
@@ -0,0 +1,13 @@
Device {
Name = droplet
Media Type = S3
Device Type = droplet
Device Options = "profile=@CMAKE_CURRENT_SOURCE_DIR@/src/tests/configs/sd_backend/droplet.profile,bucket=bareos-test,chunksize=10M"
Maximum Concurrent Jobs = 1
Archive Device = "Cloud Storage"
LabelMedia = yes
Random Access = yes
Automatic Mount = yes
AlwaysOpen = no
RemovableMedia = no
}
9 changes: 9 additions & 0 deletions core/src/tests/configs/sd_backend/droplet.profile.in
@@ -0,0 +1,9 @@
host = localhost:9000
use_https = false
access_key = minioadmin
secret_key = minioadmin
pricing_dir = ""
aws_auth_sign_version = 4 # Currently, AWS S3 uses version 4. The Ceph S3 gateway uses version 2.
aws_region = eu-central-1
backend = posix
base_path = @CMAKE_CURRENT_BINARY_DIR@/src/tests
181 changes: 176 additions & 5 deletions core/src/tests/sd_backend.cc
Expand Up @@ -49,12 +49,8 @@
# include "stored/sd_backends.h"
#endif

#include "bsock_mock.h"
#include "include/make_unique.h"

using ::testing::Assign;
using ::testing::DoAll;
using ::testing::Return;
using namespace storagedaemon;

namespace storagedaemon {
Expand All @@ -73,7 +69,7 @@ void sd::SetUp()
OSDependentInit();
InitMsg(NULL, NULL);

debug_level = 1000;
debug_level = 900;

/* configfile is a global char* from stored_globals.h */
configfile = strdup(RELATIVE_PROJECT_SOURCE_DIR "/configs/sd_backend/");
Expand Down Expand Up @@ -152,3 +148,178 @@ TEST_F(sd, backend_load_unload)
Dmsg0(100, "cleanup\n");
FreeJcr(jcr);
}

void droplet_write_reread_testdata(std::vector<std::vector<char>>& test_data)
{
const char* name = "sd_backend_test";
const char* dev_name = "droplet";
const char* volname
= ::testing::UnitTest::GetInstance()->current_test_info()->name();

JobControlRecord* jcr = SetupDummyJcr(name, nullptr, nullptr);
ASSERT_TRUE(jcr);

DeviceResource* device_resource
= (DeviceResource*)my_config->GetResWithName(R_DEVICE, dev_name);

Device* dev = FactoryCreateDevice(jcr, device_resource);
ASSERT_TRUE(dev);

// write to device
{
dev->setVolCatName(volname);
auto fd = dev->d_open(volname, O_CREAT | O_RDWR | O_BINARY, 0640);
dev->d_truncate(
nullptr); // dcr parameter is unused, so nullptr should be fine

for (auto& buf : test_data) { dev->d_write(fd, buf.data(), buf.size()); }
dev->d_close(fd);
}

// read from device
{
dev->setVolCatName(volname);
auto fd = dev->d_open(volname, O_CREAT | O_RDWR | O_BINARY, 0640);

for (auto& buf : test_data) {
std::vector<char> tmp(buf.size());
dev->d_read(fd, tmp.data(), buf.size());
ASSERT_EQ(buf, tmp);
}
dev->d_close(fd);
}
delete dev;
FreeJcr(jcr);
}

// write-request writing 1 byte to this chunk and rest to next chunk
TEST_F(sd, droplet_off_by_one_short)
{
#if !defined HAVE_DROPLET
std::cerr << "\nThis test requires droplet backend, which has not been "
"built. Skipping.\n";
exit(77);
#else
using namespace std::string_literals;
std::vector<std::vector<char>> test_data;

{
std::vector<char> tmp(1024 * 1024 - 1);
std::fill(tmp.begin(), tmp.end(), '0');
test_data.push_back(tmp);
}
for (char& c : "123456789abcdefghijklmnopqrstuvwxyz"s) {
std::vector<char> tmp(1024 * 1024);
std::fill(tmp.begin(), tmp.end(), c);
test_data.push_back(tmp);
}

droplet_write_reread_testdata(test_data);
#endif
}

// write-request crossing the chunk-border by exactly 1 byte
TEST_F(sd, droplet_off_by_one_long)
{
#if !defined HAVE_DROPLET
std::cerr << "\nThis test requires droplet backend, which has not been "
"built. Skipping.\n";
exit(77);
#else
using namespace std::string_literals;
std::vector<std::vector<char>> test_data;

{
std::vector<char> tmp(1024 * 1024 + 1);
std::fill(tmp.begin(), tmp.end(), '0');
test_data.push_back(tmp);
}
for (char& c : "123456789abcdefghijklmnopqrstuvwxyz"s) {
std::vector<char> tmp(1024 * 1024);
std::fill(tmp.begin(), tmp.end(), c);
test_data.push_back(tmp);
}

droplet_write_reread_testdata(test_data);
#endif
}

// write-request hitting chunk-border exactly
TEST_F(sd, droplet_aligned)
{
#if !defined HAVE_DROPLET
std::cerr << "\nThis test requires droplet backend, which has not been "
"built. Skipping.\n";
exit(77);
#else
using namespace std::string_literals;
std::vector<std::vector<char>> test_data;
for (char& c : "0123456789abcdefghijklmnopqrstuvwxyz"s) {
std::vector<char> tmp(1024 * 1024);
std::fill(tmp.begin(), tmp.end(), c);
test_data.push_back(tmp);
}

droplet_write_reread_testdata(test_data);
#endif
}

// write-request same size as chunk
TEST_F(sd, droplet_fullchunk)
{
#if !defined HAVE_DROPLET
std::cerr << "\nThis test requires droplet backend, which has not been "
"built. Skipping.\n";
exit(77);
#else
using namespace std::string_literals;
std::vector<std::vector<char>> test_data;
for (char& c : "0123"s) {
std::vector<char> tmp(10 * 1024 * 1024);
std::fill(tmp.begin(), tmp.end(), c);
test_data.push_back(tmp);
}

droplet_write_reread_testdata(test_data);
#endif
}

// write-request larger than chunk
TEST_F(sd, droplet_oversized_write)
{
#if !defined HAVE_DROPLET
std::cerr << "\nThis test requires droplet backend, which has not been "
"built. Skipping.\n";
exit(77);
#else
using namespace std::string_literals;
std::vector<std::vector<char>> test_data;
for (char& c : "0123"s) {
std::vector<char> tmp(11 * 1024 * 1024);
std::fill(tmp.begin(), tmp.end(), c);
test_data.push_back(tmp);
}

droplet_write_reread_testdata(test_data);
#endif
}

// write-request larger than two chunks
TEST_F(sd, droplet_double_oversized_write)
{
#if !defined HAVE_DROPLET
std::cerr << "\nThis test requires droplet backend, which has not been "
"built. Skipping.\n";
exit(77);
#else
using namespace std::string_literals;
std::vector<std::vector<char>> test_data;
for (char& c : "0123"s) {
std::vector<char> tmp(21 * 1024 * 1024);
std::fill(tmp.begin(), tmp.end(), c);
test_data.push_back(tmp);
}

droplet_write_reread_testdata(test_data);
#endif
}

0 comments on commit 0ec05df

Please sign in to comment.