// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
+#include "test/librbd/test_fixture.h"
#include "include/int_types.h"
#include "include/stringify.h"
#include "include/rados/librados.h"
using namespace ceph;
-#define REQUIRE_FEATURE(feature) { \
- if (!is_feature_enabled(feature)) { \
- std::cout << "SKIPPING" << std::endl; \
- return SUCCEED(); \
- } \
-}
-
-static bool get_features(uint64_t *features) {
- const char *c = getenv("RBD_FEATURES");
- if (c == NULL) {
- return false;
- }
-
- std::stringstream ss(c);
- if (!(ss >> *features)) {
- return false;
- }
- return true;
-}
-
-static int create_image_pp(librbd::RBD &rbd, librados::IoCtx &ioctx,
- const std::string &name, uint64_t size) {
- uint64_t features = 0;
- get_features(&features);
- int order = 0;
- return rbd.create2(ioctx, name.c_str(), size, features, &order);
-}
-
-static bool is_feature_enabled(uint64_t feature) {
- uint64_t features;
- return (get_features(&features) && (features & feature) == feature);
-}
-
-class TestImageWatcher : public ::testing::Test {
+class TestImageWatcher : public TestFixture {
public:
TestImageWatcher() : m_watch_ctx(NULL), m_aio_completion_restarts(),
uint64_t m_handle;
};
- static void SetUpTestCase() {
- _pool_name = get_temp_pool_name();
- ASSERT_EQ("", create_one_pool_pp(_pool_name, _rados));
- }
-
- static void TearDownTestCase() {
- ASSERT_EQ(0, destroy_one_pool_pp(_pool_name, _rados));
- }
-
- static std::string get_temp_image_name() {
- ++_image_number;
- return "image" + stringify(_image_number);
- }
-
- virtual void SetUp() {
- ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), m_ioctx));
-
- m_image_name = get_temp_image_name();
- m_image_size = 2 << 20;
- ASSERT_EQ(0, create_image_pp(m_rbd, m_ioctx, m_image_name, m_image_size));
- }
-
virtual void TearDown() {
- unlock_image();
deregister_image_watch();
-
- for (std::vector<librbd::ImageCtx *>::iterator iter = m_ictxs.begin();
- iter != m_ictxs.end(); ++iter) {
- librbd::close_image(*iter);
- }
-
- m_ioctx.close();
- }
-
- int open_image(const std::string &image_name, librbd::ImageCtx **ictx) {
- *ictx = new librbd::ImageCtx(image_name.c_str(), "", NULL, m_ioctx, false);
- m_ictxs.push_back(*ictx);
- return librbd::open_image(*ictx);
+ TestFixture::TearDown();
}
int deregister_image_watch() {
return m_notify_acks.empty();
}
- int lock_image(librbd::ImageCtx &ictx, ClsLockType lock_type,
- const std::string &cookie) {
- int r = rados::cls::lock::lock(&m_ioctx, ictx.header_oid, RBD_LOCK_NAME,
- lock_type, cookie, "internal", "", utime_t(),
- 0);
- if (r == 0) {
- m_lock_object = ictx.header_oid;
- m_lock_cookie = cookie;
- }
- return r;
- }
-
- int unlock_image() {
- int r = 0;
- if (!m_lock_cookie.empty()) {
- r = rados::cls::lock::unlock(&m_ioctx, m_lock_object, RBD_LOCK_NAME,
- m_lock_cookie);
- m_lock_cookie = "";
- }
- return r;
- }
librbd::AioCompletion *create_aio_completion(librbd::ImageCtx &ictx) {
librbd::AioCompletion *aio_completion = new librbd::AioCompletion();
(result != 0 || m_aio_completion_restarts > 0));
}
- static std::string _pool_name;
- static librados::Rados _rados;
- static uint64_t _image_number;
-
- librados::IoCtx m_ioctx;
- librbd::RBD m_rbd;
-
- std::string m_image_name;
- uint64_t m_image_size;
-
- std::vector<librbd::ImageCtx *> m_ictxs;
-
typedef std::pair<NotifyOp, bufferlist> NotifyOpPayload;
typedef std::list<NotifyOpPayload> NotifyOpPayloads;
Mutex m_callback_lock;
Cond m_callback_cond;
- std::string m_lock_object;
- std::string m_lock_cookie;
};
-std::string TestImageWatcher::_pool_name;
-librados::Rados TestImageWatcher::_rados;
-uint64_t TestImageWatcher::_image_number = 0;
-
TEST_F(TestImageWatcher, IsLockSupported) {
REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
--- /dev/null
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+#include "test/librbd/test_fixture.h"
+#include "include/stringify.h"
+#include "cls/lock/cls_lock_client.h"
+#include "cls/lock/cls_lock_types.h"
+#include "librbd/internal.h"
+#include "test/librados/test.h"
+#include <iostream>
+#include <sstream>
+#include <stdlib.h>
+
+bool get_features(uint64_t *features) {
+ const char *c = getenv("RBD_FEATURES");
+ if (c == NULL) {
+ return false;
+ }
+
+ std::stringstream ss(c);
+ if (!(ss >> *features)) {
+ return false;
+ }
+ return true;
+}
+
+bool is_feature_enabled(uint64_t feature) {
+ uint64_t features;
+ return (get_features(&features) && (features & feature) == feature);
+}
+
+int create_image_pp(librbd::RBD &rbd, librados::IoCtx &ioctx,
+ const std::string &name, uint64_t size) {
+ uint64_t features = 0;
+ get_features(&features);
+ int order = 0;
+ return rbd.create2(ioctx, name.c_str(), size, features, &order);
+}
+
+std::string TestFixture::_pool_name;
+librados::Rados TestFixture::_rados;
+uint64_t TestFixture::_image_number = 0;
+
+TestFixture::TestFixture() {
+}
+
+void TestFixture::SetUpTestCase() {
+ _pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool_pp(_pool_name, _rados));
+}
+
+void TestFixture::TearDownTestCase() {
+ ASSERT_EQ(0, destroy_one_pool_pp(_pool_name, _rados));
+}
+
+std::string TestFixture::get_temp_image_name() {
+ ++_image_number;
+ return "image" + stringify(_image_number);
+}
+
+void TestFixture::SetUp() {
+ ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), m_ioctx));
+
+ m_image_name = get_temp_image_name();
+ m_image_size = 2 << 20;
+ ASSERT_EQ(0, create_image_pp(m_rbd, m_ioctx, m_image_name, m_image_size));
+}
+
+void TestFixture::TearDown() {
+ unlock_image();
+ for (std::set<librbd::ImageCtx *>::iterator iter = m_ictxs.begin();
+ iter != m_ictxs.end(); ++iter) {
+ librbd::close_image(*iter);
+ }
+
+ m_ioctx.close();
+}
+
+int TestFixture::open_image(const std::string &image_name,
+ librbd::ImageCtx **ictx) {
+ *ictx = new librbd::ImageCtx(image_name.c_str(), "", NULL, m_ioctx, false);
+ m_ictxs.insert(*ictx);
+ return librbd::open_image(*ictx);
+}
+
+void TestFixture::close_image(librbd::ImageCtx *ictx) {
+ m_ictxs.erase(ictx);
+ librbd::close_image(ictx);
+}
+
+int TestFixture::lock_image(librbd::ImageCtx &ictx, ClsLockType lock_type,
+ const std::string &cookie) {
+ int r = rados::cls::lock::lock(&m_ioctx, ictx.header_oid, RBD_LOCK_NAME,
+ lock_type, cookie, "internal", "", utime_t(),
+ 0);
+ if (r == 0) {
+ m_lock_object = ictx.header_oid;
+ m_lock_cookie = cookie;
+ }
+ return r;
+}
+
+int TestFixture::unlock_image() {
+ int r = 0;
+ if (!m_lock_cookie.empty()) {
+ r = rados::cls::lock::unlock(&m_ioctx, m_lock_object, RBD_LOCK_NAME,
+ m_lock_cookie);
+ m_lock_cookie = "";
+ }
+ return r;
+}
--- /dev/null
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+#include "test/librbd/test_fixture.h"
+#include "librbd/ImageWatcher.h"
+#include "librbd/internal.h"
+#include <boost/scope_exit.hpp>
+#include <utility>
+#include <vector>
+
+class TestInternal : public TestFixture {
+public:
+
+ TestInternal() {}
+
+ typedef std::vector<std::pair<std::string, bool> > Snaps;
+
+ virtual void TearDown() {
+ for (Snaps::iterator iter = m_snaps.begin(); iter != m_snaps.end(); ++iter) {
+ librbd::ImageCtx *ictx;
+ EXPECT_EQ(0, open_image(m_image_name, &ictx));
+ if (iter->second) {
+ EXPECT_EQ(0, librbd::snap_unprotect(ictx, iter->first.c_str()));
+ }
+ EXPECT_EQ(0, librbd::snap_remove(ictx, iter->first.c_str()));
+ }
+
+ TestFixture::TearDown();
+ }
+
+ int create_snapshot(const char *snap_name, bool snap_protect) {
+ librbd::ImageCtx *ictx;
+ int r = open_image(m_image_name, &ictx);
+ if (r < 0) {
+ return r;
+ }
+
+ r = librbd::snap_create(ictx, snap_name);
+ if (r < 0) {
+ return r;
+ }
+
+ m_snaps.push_back(std::make_pair(snap_name, snap_protect));
+ if (snap_protect) {
+ r = librbd::snap_protect(ictx, snap_name);
+ if (r < 0) {
+ return r;
+ }
+ }
+ close_image(ictx);
+ return 0;
+ }
+
+ Snaps m_snaps;
+};
+
+class DummyContext : public Context {
+public:
+ virtual void finish(int r) {
+ }
+};
+
+TEST_F(TestInternal, IsExclusiveLockOwner) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ bool is_owner;
+ ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+ ASSERT_FALSE(is_owner);
+
+ {
+ RWLock::WLocker l(ictx->owner_lock);
+ ASSERT_EQ(0, ictx->image_watcher->try_lock());
+ }
+
+ ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+ ASSERT_TRUE(is_owner);
+}
+
+TEST_F(TestInternal, ResizeLocksImage) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ librbd::NoOpProgressContext no_op;
+ ASSERT_EQ(0, librbd::resize(ictx, m_image_size >> 1, no_op));
+
+ bool is_owner;
+ ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+ ASSERT_TRUE(is_owner);
+}
+
+TEST_F(TestInternal, ResizeFailsToLockImage) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, "manually locked"));
+
+ librbd::NoOpProgressContext no_op;
+ ASSERT_EQ(-EROFS, librbd::resize(ictx, m_image_size >> 1, no_op));
+}
+
+TEST_F(TestInternal, SnapCreateLocksImage) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ BOOST_SCOPE_EXIT( (ictx) ) {
+ ASSERT_EQ(0, librbd::snap_remove(ictx, "snap1"));
+ } BOOST_SCOPE_EXIT_END;
+
+ bool is_owner;
+ ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+ ASSERT_TRUE(is_owner);
+}
+
+TEST_F(TestInternal, SnapCreateFailsToLockImage) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, "manually locked"));
+
+ ASSERT_EQ(-EROFS, librbd::snap_create(ictx, "snap1"));
+}
+
+TEST_F(TestInternal, SnapRollbackLocksImage) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+ ASSERT_EQ(0, create_snapshot("snap1", false));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ librbd::NoOpProgressContext no_op;
+ ASSERT_EQ(0, librbd::snap_rollback(ictx, "snap1", no_op));
+
+ bool is_owner;
+ ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+ ASSERT_TRUE(is_owner);
+}
+
+TEST_F(TestInternal, SnapRollbackFailsToLockImage) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+
+ ASSERT_EQ(0, create_snapshot("snap1", false));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, "manually locked"));
+
+ librbd::NoOpProgressContext no_op;
+ ASSERT_EQ(-EROFS, librbd::snap_rollback(ictx, "snap1", no_op));
+}
+
+TEST_F(TestInternal, SnapSetReleasesLock) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+ ASSERT_EQ(0, create_snapshot("snap1", false));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_set(ictx, "snap1"));
+
+ bool is_owner;
+ ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+ ASSERT_FALSE(is_owner);
+}
+
+TEST_F(TestInternal, FlattenLocksImage) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_LAYERING);
+
+ ASSERT_EQ(0, create_snapshot("snap1", true));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ uint64_t features;
+ ASSERT_EQ(0, librbd::get_features(ictx, &features));
+
+ std::string clone_name = get_temp_image_name();
+ int order = ictx->order;
+ ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx,
+ clone_name.c_str(), features, &order, 0, 0));
+
+ librbd::ImageCtx *ictx2;
+ ASSERT_EQ(0, open_image(clone_name, &ictx2));
+
+ librbd::NoOpProgressContext no_op;
+ ASSERT_EQ(0, librbd::flatten(ictx2, no_op));
+
+ bool is_owner;
+ ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx2, &is_owner));
+ ASSERT_TRUE(is_owner);
+}
+
+TEST_F(TestInternal, FlattenFailsToLockImage) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_LAYERING);
+
+ ASSERT_EQ(0, create_snapshot("snap1", true));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ uint64_t features;
+ ASSERT_EQ(0, librbd::get_features(ictx, &features));
+
+ std::string clone_name = get_temp_image_name();
+ int order = ictx->order;
+ ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx,
+ clone_name.c_str(), features, &order, 0, 0));
+ BOOST_SCOPE_EXIT( (&m_ioctx) (clone_name) ) {
+ librbd::NoOpProgressContext no_op;
+ ASSERT_EQ(0, librbd::remove(m_ioctx, clone_name.c_str(), no_op));
+ } BOOST_SCOPE_EXIT_END;
+
+ librbd::ImageCtx *ictx2;
+ ASSERT_EQ(0, open_image(clone_name, &ictx2));
+
+ TestInternal *parent = this;
+ BOOST_SCOPE_EXIT( (parent) (ictx2) ) {
+ parent->close_image(ictx2);
+ } BOOST_SCOPE_EXIT_END;
+
+ ASSERT_EQ(0, lock_image(*ictx2, LOCK_EXCLUSIVE, "manually locked"));
+
+ librbd::NoOpProgressContext no_op;
+ ASSERT_EQ(-EROFS, librbd::flatten(ictx2, no_op));
+}
+
+TEST_F(TestInternal, AioWriteRequestsLock) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, "manually locked"));
+
+ std::string buffer(256, '1');
+ DummyContext *ctx = new DummyContext();
+ librbd::AioCompletion *c =
+ librbd::aio_create_completion_internal(ctx, librbd::rbd_ctx_cb);
+ ASSERT_EQ(0, aio_write(ictx, 0, buffer.size(), buffer.c_str(), c, 0));
+
+ bool is_owner;
+ ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+ ASSERT_FALSE(is_owner);
+
+ ASSERT_TRUE(ictx->image_watcher->has_pending_aio_operations());
+}
+
+TEST_F(TestInternal, AioDiscardRequestsLock) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, "manually locked"));
+
+ DummyContext *ctx = new DummyContext();
+ librbd::AioCompletion *c =
+ librbd::aio_create_completion_internal(ctx, librbd::rbd_ctx_cb);
+ ASSERT_EQ(0, aio_discard(ictx, 0, 256, c));
+
+ bool is_owner;
+ ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+ ASSERT_FALSE(is_owner);
+
+ ASSERT_TRUE(ictx->image_watcher->has_pending_aio_operations());
+}