From: Igor Golikov Date: Tue, 18 Mar 2025 12:55:21 +0000 (+0000) Subject: test: add sanity and integration test for zerocopy X-Git-Tag: testing/wip-vshankar-testing-20250325.080107-debug^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=f39f5d0d3412c3435adf43f1994421144a8480d2;p=ceph-ci.git test: add sanity and integration test for zerocopy add sanity unit test for libcephfs zerocopy illegal params add integration test as a part of the Client test for zerocopy RW Signed-off-by: Igor Golikov Fixes: https://tracker.ceph.com/issues/69919 --- diff --git a/src/test/client/CMakeLists.txt b/src/test/client/CMakeLists.txt index b085a954fb7..99ec776a868 100644 --- a/src/test/client/CMakeLists.txt +++ b/src/test/client/CMakeLists.txt @@ -6,6 +6,7 @@ if(${WITH_CEPHFS}) nonblocking.cc commands.cc syncio.cc + zerocopy.cc ) target_link_libraries(ceph_test_client client @@ -18,4 +19,10 @@ if(${WITH_CEPHFS}) ) install(TARGETS ceph_test_client DESTINATION ${CMAKE_INSTALL_BINDIR}) + add_executable(ceph_test_zerocopy_sanity + zerocopy_sanity.cc + ) + add_ceph_unittest(ceph_test_zerocopy_sanity) + target_link_libraries(ceph_test_zerocopy_sanity ceph-common cephfs global) + install(TARGETS ceph_test_zerocopy_sanity DESTINATION ${CMAKE_INSTALL_BINDIR}) endif(${WITH_CEPHFS}) diff --git a/src/test/client/TestClient.h b/src/test/client/TestClient.h index 7de90373d5d..5f18f8cd23c 100644 --- a/src/test/client/TestClient.h +++ b/src/test/client/TestClient.h @@ -114,6 +114,9 @@ public: int walk(std::string_view path, struct walk_dentry_result* result, const UserPerm& perms, bool followsym=true) { return Client::walk(path, result, perms, followsym); } + std::pair get_copy_metrics() { + return {total_io_copy_ops, total_io_copy_size}; + } }; class TestClient : public ::testing::Test { diff --git a/src/test/client/zerocopy.cc b/src/test/client/zerocopy.cc new file mode 100644 index 00000000000..c0df14c8c5b --- /dev/null +++ b/src/test/client/zerocopy.cc @@ -0,0 +1,176 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2022 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include +#include +#include "gtest/gtest.h" +#include +#include "TestClient.h" + +/* + * These tests provide basic testing for zerocopy RW ops. We test here only blocking calls + * since the decision (in the client) on either to copy the data or not doesn't depend on + * whether the IO will be blocking or non blocking + */ +class ZeroCopyTest : public TestClient { +protected: + Fh *fh; + static constexpr char out0[] = "hello"; + static constexpr char out1[] = "big"; + static constexpr char out2[] = "world!"; + static constexpr int IOV_COUNT = 3; + struct iovec iov_out[IOV_COUNT]; + + bool write = true; + bool zerocopy = true; + + ssize_t data_length; + + void SetUp() override { + TestClient::SetUp(); + int mypid = getpid(); + char filename[256]; + sprintf(filename, "test_zerocopy%u", mypid); + Inode *root, *file; + root = client->get_root(); + ASSERT_NE(root, (Inode *) NULL); + struct ceph_statx stx; + ASSERT_EQ(0, client->ll_createx(root, filename, 0666, + O_RDWR | O_CREAT | O_TRUNC, + &file, &fh, &stx, 0, 0, myperm)); + memset(iov_out, 0, sizeof(iov_out)); + iov_out[0].iov_base = const_cast(out0); + iov_out[0].iov_len = sizeof(out0); + iov_out[1].iov_base = const_cast(out1); + iov_out[1].iov_len = sizeof(out1); + iov_out[2].iov_base = const_cast(out2); + iov_out[2].iov_len = sizeof(out2); + + data_length = iov_out[0].iov_len + iov_out[1].iov_len + iov_out[2].iov_len; + + write = true; + zerocopy = true; + } +}; + +TEST_F(ZeroCopyTest, LlPreadvPwritevZerocopyObjectCacherEnabled) { + client->cct->_conf->client_oc = true; + auto rc = client->ll_preadv_pwritev(fh, iov_out, IOV_COUNT, 0, write, nullptr, nullptr, false, false, zerocopy); + ASSERT_EQ(rc, -EINVAL); +} + +TEST_F(ZeroCopyTest, LlPreadvPwritevZerocopyInvalidParams) { + client->cct->_conf->client_oc = false; + bufferlist bl; + // for read we must provide bufferlist so we will not and it should fail + write = false; + auto rc = client->ll_preadv_pwritev(fh, iov_out, IOV_COUNT, 0, write, nullptr, nullptr, false, false, zerocopy); + ASSERT_EQ(rc, -EINVAL); + // write, with valid iov but negative iov_count should fail as well + write = true; + rc = client->ll_preadv_pwritev(fh, iov_out, -1, 0, write, nullptr, &bl, false, false, zerocopy); + ASSERT_EQ(rc, -EINVAL); + // write, without iovec + rc = client->ll_preadv_pwritev(fh, nullptr, IOV_COUNT, 0, write, nullptr, &bl, false, false, zerocopy); + ASSERT_EQ(rc, -EINVAL); +} + +TEST_F(ZeroCopyTest, LlPreadvPwritevZerocopyWrite) { + client->cct->_conf->client_oc = false; + + int64_t rc; + bufferlist bl; + + // first test with zerocopy =true + zerocopy = true; + write = true; + + auto metrics = client->get_copy_metrics(); + auto copy_ops_before = metrics.first; + auto copy_size_before = metrics.second; + // context is null so it will be blocking call + rc = client->ll_preadv_pwritev(fh, iov_out, IOV_COUNT, 0, write, nullptr, &bl, false, false, zerocopy); + ASSERT_EQ(data_length, rc); + metrics = client->get_copy_metrics(); + ASSERT_EQ(copy_ops_before, metrics.first); + ASSERT_EQ(copy_size_before, metrics.second); + + // now with zerocopy set to false + zerocopy = false; + metrics = client->get_copy_metrics(); + copy_ops_before = metrics.first; + copy_size_before = metrics.second; + // context is null so it will be blocking call + rc = client->ll_preadv_pwritev(fh, iov_out, IOV_COUNT, 0, write, nullptr, &bl, false, false, zerocopy); + ASSERT_EQ(data_length, rc); + metrics = client->get_copy_metrics(); + ASSERT_EQ(copy_ops_before + IOV_COUNT, metrics.first); + ASSERT_EQ(copy_size_before + data_length, metrics.second); +} + +TEST_F(ZeroCopyTest, LlPreadvPwritevZerocopyRead) { + client->cct->_conf->client_oc = false; + // first write the file + auto metrics = client->get_copy_metrics(); + auto copy_ops_before = metrics.first; + auto copy_size_before = metrics.second; + auto rc = client->ll_preadv_pwritev(fh, iov_out, IOV_COUNT, 0, write, nullptr, nullptr, false, false, zerocopy); + ASSERT_EQ(data_length, rc); + metrics = client->get_copy_metrics(); + ASSERT_EQ(copy_ops_before, metrics.first); + ASSERT_EQ(copy_size_before, metrics.second); + + // now read, with zerocopy it should be into the bufferlist + std::vector> in_vector; + in_vector.emplace_back(iov_out[0].iov_len); + in_vector.emplace_back(iov_out[1].iov_len); + in_vector.emplace_back(iov_out[2].iov_len); + struct iovec iov_in[IOV_COUNT] = { + {in_vector[0].data(), in_vector[0].size()}, + {in_vector[1].data(), in_vector[1].size()}, + {in_vector[2].data(), in_vector[2].size()}, + }; + + bufferlist bl; + write = false; + copy_ops_before = metrics.first; + copy_size_before = metrics.second; + rc = client->ll_preadv_pwritev(fh, iov_in, IOV_COUNT, 0, write, nullptr, &bl, false, false, zerocopy); + ASSERT_EQ(data_length, rc); + metrics = client->get_copy_metrics(); + ASSERT_EQ(copy_ops_before, metrics.first); + ASSERT_EQ(copy_size_before, metrics.second); + copy_bufferlist_to_iovec(iov_in, IOV_COUNT, &bl, rc); + + ASSERT_EQ(0, strncmp((const char*)iov_in[0].iov_base, (const char*)iov_out[0].iov_base, iov_out[0].iov_len)); + ASSERT_EQ(0, strncmp((const char*)iov_in[1].iov_base, (const char*)iov_out[1].iov_base, iov_out[1].iov_len)); + ASSERT_EQ(0, strncmp((const char*)iov_in[2].iov_base, (const char*)iov_out[2].iov_base, iov_out[2].iov_len)); + + // read without zerocopy + for (auto& vec : in_vector) { + std::fill(vec.begin(), vec.end(), 0); + } + zerocopy = false; + bl.clear(); + rc = client->ll_preadv_pwritev(fh, iov_in, IOV_COUNT, 0, write, nullptr, &bl, false, false, zerocopy); + ASSERT_EQ(data_length, rc); + metrics = client->get_copy_metrics(); + ASSERT_EQ(copy_ops_before + IOV_COUNT, metrics.first); + ASSERT_EQ(copy_size_before + data_length, metrics.second); + copy_bufferlist_to_iovec(iov_in, IOV_COUNT, &bl, rc); + + ASSERT_EQ(0, strncmp((const char*)iov_in[0].iov_base, (const char*)iov_out[0].iov_base, iov_out[0].iov_len)); + ASSERT_EQ(0, strncmp((const char*)iov_in[1].iov_base, (const char*)iov_out[1].iov_base, iov_out[1].iov_len)); + ASSERT_EQ(0, strncmp((const char*)iov_in[2].iov_base, (const char*)iov_out[2].iov_base, iov_out[2].iov_len)); +} \ No newline at end of file diff --git a/src/test/client/zerocopy_sanity.cc b/src/test/client/zerocopy_sanity.cc new file mode 100644 index 00000000000..adbb80cddd0 --- /dev/null +++ b/src/test/client/zerocopy_sanity.cc @@ -0,0 +1,39 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2022 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include +#include +#include "gtest/gtest.h" +#include +#include "TestClient.h" + +// the only unittest we can do is to test the comibnation of +// invalid parameters in libcephfs functions that can use zerocopy +TEST(libcephfs, zerocopy_sanity_invalid_params) { + struct ceph_mount_info *mount_info = nullptr; + auto res = ceph_create(&mount_info, "test_mount"); + ASSERT_EQ(0, res); + struct ceph_ll_io_info io_info; + struct iovec iov; + io_info.iov = &iov; + io_info.iovcnt = 0; + io_info.write = false; + io_info.zerocopy = true; + + res = ceph_ll_readv_writev(mount_info, &io_info); + ASSERT_EQ(res, -EINVAL); + + res = ceph_ll_nonblocking_readv_writev(mount_info, &io_info); + ASSERT_EQ(res, -EINVAL); +} \ No newline at end of file