]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
test: add sanity and integration test for zerocopy
authorIgor Golikov <igolikov@ibm.com>
Tue, 18 Mar 2025 12:55:21 +0000 (12:55 +0000)
committerIgor Golikov <igolikov@ibm.com>
Tue, 18 Mar 2025 13:01:12 +0000 (13:01 +0000)
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 <igolikov@ibm.com>
Fixes: https://tracker.ceph.com/issues/69919
src/test/client/CMakeLists.txt
src/test/client/TestClient.h
src/test/client/zerocopy.cc [new file with mode: 0644]
src/test/client/zerocopy_sanity.cc [new file with mode: 0644]

index b085a954fb7a884a3a762c756c4b7f570c940a26..99ec776a868111f885a8d5e9bf3d98418adc2ead 100644 (file)
@@ -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})
index 7de90373d5d6a573a71fd4c2ebd9bfef7e58a831..5f18f8cd23cf85a244dc321b6dc4211450972589 100644 (file)
@@ -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<uint64_t, uint64_t> 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 (file)
index 0000000..c0df14c
--- /dev/null
@@ -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 <iostream>
+#include <errno.h>
+#include "gtest/gtest.h"
+#include <include/cephfs/libcephfs.h>
+#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<char*>(out0);
+      iov_out[0].iov_len = sizeof(out0);
+      iov_out[1].iov_base = const_cast<char*>(out1);
+      iov_out[1].iov_len = sizeof(out1);
+      iov_out[2].iov_base = const_cast<char*>(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<std::vector<char>> 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 (file)
index 0000000..adbb80c
--- /dev/null
@@ -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 <iostream>
+#include <errno.h>
+#include "gtest/gtest.h"
+#include <include/cephfs/libcephfs.h>
+#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