snap_id(CEPH_NOSNAP),
snap_exists(true),
read_only(ro),
+ read_only_flags(ro ? IMAGE_READ_ONLY_FLAG_USER : 0U),
exclusive_locked(false),
name(image_name),
image_watcher(NULL),
bool snap_exists; // false if our snap_id was deleted
// whether the image was opened read-only. cannot be changed after opening
bool read_only;
+ uint32_t read_only_flags = 0U;
+ uint32_t read_only_mask = ~0U;
std::map<rados::cls::lock::locker_id_t,
rados::cls::lock::locker_info_t> lockers;
OPEN_FLAG_IGNORE_MIGRATING = 1 << 2
};
+enum ImageReadOnlyFlag {
+ IMAGE_READ_ONLY_FLAG_USER = 1 << 0,
+ IMAGE_READ_ONLY_FLAG_NON_PRIMARY = 1 << 1,
+};
+
struct MigrationInfo {
int64_t pool_id = -1;
std::string pool_namespace;
m_parent_image_ctx = I::create("", m_parent_spec.image_id, nullptr,
m_parent_io_ctx, false);
+ // ensure non-primary images can be modified
+ m_parent_image_ctx->read_only_mask &= ~IMAGE_READ_ONLY_FLAG_NON_PRIMARY;
+
auto ctx = create_context_callback<
DetachChildRequest<I>,
&DetachChildRequest<I>::handle_clone_v2_open_parent>(this);
template <typename I>
Context *OpenRequest<I>::send_register_watch(int *result) {
- if (m_image_ctx->read_only) {
+ if ((m_image_ctx->read_only_flags & IMAGE_READ_ONLY_FLAG_USER) != 0U) {
return send_set_snap(result);
}
switch(m_migration_spec.header_type) {
case cls::rbd::MIGRATION_HEADER_TYPE_SRC:
- if (!m_image_ctx.read_only) {
+ if (!m_read_only) {
lderr(cct) << "image being migrated" << dendl;
*result = -EROFS;
return m_on_finish;
}
}
+ {
+ std::shared_lock image_locker{m_image_ctx.image_lock};
+ m_read_only = m_image_ctx.read_only;
+ m_read_only_flags = m_image_ctx.read_only_flags;
+ }
+
memcpy(&v1_header, m_out_bl.c_str(), sizeof(v1_header));
m_order = v1_header.options.order;
m_size = v1_header.image_size;
{
std::shared_lock image_locker{m_image_ctx.image_lock};
snap_id = m_image_ctx.snap_id;
+ m_read_only = m_image_ctx.read_only;
+ m_read_only_flags = m_image_ctx.read_only_flags;
}
- bool read_only = m_image_ctx.read_only || snap_id != CEPH_NOSNAP;
+ // mask out the non-primary read-only flag since its state can change
+ bool read_only = (
+ ((m_read_only_flags & ~IMAGE_READ_ONLY_FLAG_NON_PRIMARY) != 0) ||
+ (snap_id != CEPH_NOSNAP));
librados::ObjectReadOperation op;
cls_client::get_size_start(&op, CEPH_NOSNAP);
cls_client::get_features_start(&op, read_only);
m_incomplete_update = true;
}
+ if (((m_incompatible_features & RBD_FEATURE_NON_PRIMARY) != 0U) &&
+ ((m_read_only_flags & IMAGE_READ_ONLY_FLAG_NON_PRIMARY) == 0U) &&
+ ((m_image_ctx.read_only_mask & IMAGE_READ_ONLY_FLAG_NON_PRIMARY) != 0U)) {
+ // implies we opened a non-primary image in R/W mode
+ ldout(cct, 5) << "adding non-primary read-only image flag" << dendl;
+ m_read_only_flags |= IMAGE_READ_ONLY_FLAG_NON_PRIMARY;
+ } else if ((((m_incompatible_features & RBD_FEATURE_NON_PRIMARY) == 0U) ||
+ ((m_image_ctx.read_only_mask &
+ IMAGE_READ_ONLY_FLAG_NON_PRIMARY) == 0U)) &&
+ ((m_read_only_flags & IMAGE_READ_ONLY_FLAG_NON_PRIMARY) != 0U)) {
+ ldout(cct, 5) << "removing non-primary read-only image flag" << dendl;
+ m_read_only_flags &= ~IMAGE_READ_ONLY_FLAG_NON_PRIMARY;
+ }
+ m_read_only = (m_read_only_flags != 0U);
+
send_v2_get_parent();
return nullptr;
}
template <typename I>
void RefreshRequest<I>::send_v2_init_exclusive_lock() {
if ((m_features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0 ||
- m_image_ctx.read_only || !m_image_ctx.snap_name.empty() ||
+ m_read_only || !m_image_ctx.snap_name.empty() ||
m_image_ctx.exclusive_lock != nullptr) {
send_v2_open_object_map();
return;
void RefreshRequest<I>::send_v2_open_journal() {
bool journal_disabled = (
(m_features & RBD_FEATURE_JOURNALING) == 0 ||
- m_image_ctx.read_only ||
+ m_read_only ||
!m_image_ctx.snap_name.empty() ||
m_image_ctx.journal != nullptr ||
m_image_ctx.exclusive_lock == nullptr ||
if ((m_features & RBD_FEATURE_OBJECT_MAP) == 0 ||
m_image_ctx.object_map != nullptr ||
(m_image_ctx.snap_name.empty() &&
- (m_image_ctx.read_only ||
+ (m_read_only ||
m_image_ctx.exclusive_lock == nullptr ||
!m_image_ctx.exclusive_lock->is_lock_owner()))) {
send_v2_open_journal();
std::scoped_lock locker{m_image_ctx.owner_lock, m_image_ctx.image_lock};
+ m_image_ctx.read_only_flags = m_read_only_flags;
+ m_image_ctx.read_only = m_read_only;
m_image_ctx.size = m_size;
m_image_ctx.lockers = m_lockers;
m_image_ctx.lock_tag = m_lock_tag;
uint64_t m_incompatible_features = 0;
uint64_t m_flags = 0;
uint64_t m_op_features = 0;
+ uint32_t m_read_only_flags = 0U;
+ bool m_read_only = false;
librados::IoCtx m_pool_metadata_io_ctx;
std::map<std::string, bufferlist> m_metadata;
void expect_open(MockImageCtx &mock_image_ctx, int r) {
EXPECT_CALL(*mock_image_ctx.state, open(true, _))
- .WillOnce(WithArg<1>(Invoke([this, r](Context* ctx) {
+ .WillOnce(WithArg<1>(Invoke([this, &mock_image_ctx, r](Context* ctx) {
+ EXPECT_EQ(0U, mock_image_ctx.read_only_mask &
+ IMAGE_READ_ONLY_FLAG_NON_PRIMARY);
image_ctx->op_work_queue->queue(ctx, r);
})));
if (r == 0) {
ASSERT_EQ(0, ctx.wait());
}
+TEST_F(TestMockImageRefreshRequest, NonPrimaryFeature) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockRefreshImageCtx mock_image_ctx(*ictx);
+ MockRefreshParentRequest mock_refresh_parent_request;
+ MockExclusiveLock mock_exclusive_lock;
+ expect_op_work_queue(mock_image_ctx);
+ expect_test_features(mock_image_ctx);
+
+ InSequence seq;
+
+ // ensure the image is put into read-only mode
+ expect_get_mutable_metadata(mock_image_ctx,
+ ictx->features | RBD_FEATURE_NON_PRIMARY, 0);
+ expect_get_parent(mock_image_ctx, 0);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 0);
+ expect_apply_metadata(mock_image_ctx, 0);
+ expect_get_group(mock_image_ctx, 0);
+ expect_refresh_parent_is_required(mock_refresh_parent_request, false);
+
+ C_SaferCond ctx1;
+ auto req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx1);
+ req->send();
+
+ ASSERT_EQ(0, ctx1.wait());
+ ASSERT_TRUE(mock_image_ctx.read_only);
+ ASSERT_EQ(IMAGE_READ_ONLY_FLAG_NON_PRIMARY, mock_image_ctx.read_only_flags);
+
+ // try again but permit R/W against non-primary image
+ mock_image_ctx.read_only_mask = ~IMAGE_READ_ONLY_FLAG_NON_PRIMARY;
+
+ expect_get_mutable_metadata(mock_image_ctx,
+ ictx->features | RBD_FEATURE_NON_PRIMARY, 0);
+ expect_get_parent(mock_image_ctx, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 0);
+ expect_apply_metadata(mock_image_ctx, 0);
+ expect_get_group(mock_image_ctx, 0);
+ expect_refresh_parent_is_required(mock_refresh_parent_request, false);
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0);
+ }
+
+ C_SaferCond ctx2;
+ req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx2);
+ req->send();
+
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_FALSE(mock_image_ctx.read_only);
+ ASSERT_EQ(0U, mock_image_ctx.read_only_flags);
+}
+
} // namespace image
} // namespace librbd
snap_ids(image_ctx.snap_ids),
old_format(image_ctx.old_format),
read_only(image_ctx.read_only),
+ read_only_flags(image_ctx.read_only_flags),
+ read_only_mask(image_ctx.read_only_mask),
clone_copy_on_read(image_ctx.clone_copy_on_read),
lockers(image_ctx.lockers),
exclusive_locked(image_ctx.exclusive_locked),
bool old_format;
bool read_only;
+ uint32_t read_only_flags;
+ uint32_t read_only_mask;
bool clone_copy_on_read;