rgw/cls: Make cmpomap workable for atomic updates with different values

1. Make cmp_vals() to take in ObjectOperation to allow
it work with ObjectWriteOperation.

2. Add comments for cmp_set_vals() regarding how different
omap values may be set.

3. Add unit tests to verify the transactional nature of
ObjectWriteOperation used when setting different omap values.

Fixes: https://tracker.ceph.com/issues/76622
Signed-off-by: Yixin Jin <yjin77@yahoo.ca>
This commit is contained in:
Yixin Jin 2026-07-08 09:46:12 -04:00
parent 1ecc86bc7c
commit b36fc5500b
3 changed files with 61 additions and 2 deletions

View File

@ -18,7 +18,7 @@
namespace cls::cmpomap {
int cmp_vals(librados::ObjectReadOperation& op,
int cmp_vals(librados::ObjectOperation& op,
Mode mode, Op comparison, ComparisonMap values,
std::optional<ceph::bufferlist> default_value)
{

View File

@ -28,7 +28,7 @@ static constexpr uint32_t max_keys = 1000;
/// comparisons with Mode::U64, failure to decode an input value is reported
/// as -EINVAL, an empty stored value is compared as 0, and failure to decode
/// a stored value is reported as -EIO
[[nodiscard]] int cmp_vals(librados::ObjectReadOperation& op,
[[nodiscard]] int cmp_vals(librados::ObjectOperation& op,
Mode mode, Op comparison, ComparisonMap values,
std::optional<ceph::bufferlist> default_value);
@ -38,6 +38,16 @@ static constexpr uint32_t max_keys = 1000;
/// to decode an input value is reported as -EINVAL. an empty stored value is
/// compared as 0, while decode failure of a stored value is treated as an
/// unsuccessful comparison and is not reported as an error
///
/// NOTE: This function cannot be used to set different values when Op::EQ
/// is used. To accomplish this, one may utilize the transactional operation
/// with librados::ObjectWriteOperation in combination with cmp_vals(). For example,
///
/// librados::ObjectWriteOperation op;
/// // cmp_vals() fails the operation with -ECANCELED if any comparison fails
/// cls::cmpomap::cmp_vals(op, mode, comparison, cmp_values, std::nullopt);
/// // write new values on success
/// op.omap_set(set_values);
[[nodiscard]] int cmp_set_vals(librados::ObjectWriteOperation& writeop,
Mode mode, Op comparison, ComparisonMap values,
std::optional<ceph::bufferlist> default_value);

View File

@ -446,6 +446,55 @@ TEST_F(CmpOmap, cmp_set_vals_str)
}
}
TEST_F(CmpOmap, atomic_omap_set_conditional_match)
{
const std::string oid = __PRETTY_FUNCTION__;
const bufferlist value1 = u64_buffer(1);
const bufferlist value2 = u64_buffer(42);
{
std::map<std::string, bufferlist> vals = {
{"eq", value1},
};
ASSERT_EQ(ioctx.omap_set(oid, vals), 0);
}
librados::ObjectWriteOperation op;
ASSERT_EQ(cmp_vals(op, Mode::U64, Op::EQ, {{"eq", value1}}, std::nullopt), 0);
op.omap_set({{"eq", value2}});
ASSERT_EQ(ioctx.operate(oid, &op), 0);
{
std::map<std::string, bufferlist> vals;
ASSERT_EQ(get_vals(oid, &vals), 0);
ASSERT_EQ(vals.size(), 1);
EXPECT_EQ(value2, vals["eq"]);
}
}
TEST_F(CmpOmap, atomic_omap_set_conditional_mismatch)
{
const std::string oid = __PRETTY_FUNCTION__;
const bufferlist value1 = u64_buffer(1);
const bufferlist value2 = u64_buffer(2);
const bufferlist value3 = u64_buffer(42);
{
std::map<std::string, bufferlist> vals = {
{"eq", value1},
};
ASSERT_EQ(ioctx.omap_set(oid, vals), 0);
}
librados::ObjectWriteOperation op;
ASSERT_EQ(cmp_vals(op, Mode::U64, Op::EQ, {{"eq", value2}}, std::nullopt), 0);
op.omap_set({{"eq", value3}});
ASSERT_EQ(ioctx.operate(oid, &op), -ECANCELED);
{
std::map<std::string, bufferlist> vals;
ASSERT_EQ(get_vals(oid, &vals), 0);
ASSERT_EQ(vals.size(), 1);
EXPECT_EQ(value1, vals["eq"]); // Nothing is changed
}
}
TEST_F(CmpOmap, cmp_set_vals_u64)
{
const std::string oid = __PRETTY_FUNCTION__;