diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/cls/cmpomap | |
parent | Initial commit. (diff) | |
download | ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cls/cmpomap')
-rw-r--r-- | src/cls/cmpomap/CMakeLists.txt | 9 | ||||
-rw-r--r-- | src/cls/cmpomap/client.cc | 76 | ||||
-rw-r--r-- | src/cls/cmpomap/client.h | 68 | ||||
-rw-r--r-- | src/cls/cmpomap/ops.h | 100 | ||||
-rw-r--r-- | src/cls/cmpomap/server.cc | 302 | ||||
-rw-r--r-- | src/cls/cmpomap/types.h | 44 |
6 files changed, 599 insertions, 0 deletions
diff --git a/src/cls/cmpomap/CMakeLists.txt b/src/cls/cmpomap/CMakeLists.txt new file mode 100644 index 000000000..de8ca278c --- /dev/null +++ b/src/cls/cmpomap/CMakeLists.txt @@ -0,0 +1,9 @@ +add_library(cls_cmpomap SHARED server.cc) +set_target_properties(cls_cmpomap PROPERTIES + VERSION "1.0.0" + SOVERSION "1" + INSTALL_RPATH "" + CXX_VISIBILITY_PRESET hidden) +install(TARGETS cls_cmpomap DESTINATION ${cls_dir}) + +add_library(cls_cmpomap_client STATIC client.cc) diff --git a/src/cls/cmpomap/client.cc b/src/cls/cmpomap/client.cc new file mode 100644 index 000000000..e0fbbff18 --- /dev/null +++ b/src/cls/cmpomap/client.cc @@ -0,0 +1,76 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2020 Red Hat, Inc + * + * 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/rados/librados.hpp" +#include "client.h" +#include "ops.h" + +namespace cls::cmpomap { + +int cmp_vals(librados::ObjectReadOperation& op, + Mode mode, Op comparison, ComparisonMap values, + std::optional<ceph::bufferlist> default_value) +{ + if (values.size() > max_keys) { + return -E2BIG; + } + cmp_vals_op call; + call.mode = mode; + call.comparison = comparison; + call.values = std::move(values); + call.default_value = std::move(default_value); + + bufferlist in; + encode(call, in); + op.exec("cmpomap", "cmp_vals", in); + return 0; +} + +int cmp_set_vals(librados::ObjectWriteOperation& op, + Mode mode, Op comparison, ComparisonMap values, + std::optional<ceph::bufferlist> default_value) +{ + if (values.size() > max_keys) { + return -E2BIG; + } + cmp_set_vals_op call; + call.mode = mode; + call.comparison = comparison; + call.values = std::move(values); + call.default_value = std::move(default_value); + + bufferlist in; + encode(call, in); + op.exec("cmpomap", "cmp_set_vals", in); + return 0; +} + +int cmp_rm_keys(librados::ObjectWriteOperation& op, + Mode mode, Op comparison, ComparisonMap values) +{ + if (values.size() > max_keys) { + return -E2BIG; + } + cmp_rm_keys_op call; + call.mode = mode; + call.comparison = comparison; + call.values = std::move(values); + + bufferlist in; + encode(call, in); + op.exec("cmpomap", "cmp_rm_keys", in); + return 0; +} + +} // namespace cls::cmpomap diff --git a/src/cls/cmpomap/client.h b/src/cls/cmpomap/client.h new file mode 100644 index 000000000..013d85cc7 --- /dev/null +++ b/src/cls/cmpomap/client.h @@ -0,0 +1,68 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2020 Red Hat, Inc + * + * 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. + */ + +#pragma once + +#include <optional> +#include "include/rados/librados_fwd.hpp" +#include "types.h" + +namespace cls::cmpomap { + +/// requests with too many key comparisons will be rejected with -E2BIG +static constexpr uint32_t max_keys = 1000; + +/// process each of the omap value comparisons according to the same rules as +/// cmpxattr(), and return -ECANCELED if a comparison is unsuccessful. for +/// 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, + Mode mode, Op comparison, ComparisonMap values, + std::optional<ceph::bufferlist> default_value); + +/// process each of the omap value comparisons according to the same rules as +/// cmpxattr(). any key/value pairs that compare successfully are overwritten +/// with the corresponding input value. for comparisons with Mode::U64, failure +/// 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 +[[nodiscard]] int cmp_set_vals(librados::ObjectWriteOperation& writeop, + Mode mode, Op comparison, ComparisonMap values, + std::optional<ceph::bufferlist> default_value); + +/// process each of the omap value comparisons according to the same rules as +/// cmpxattr(). any key/value pairs that compare successfully are removed. for +/// comparisons with Mode::U64, failure 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 +[[nodiscard]] int cmp_rm_keys(librados::ObjectWriteOperation& writeop, + Mode mode, Op comparison, ComparisonMap values); + + +// bufferlist factories for comparison values +inline ceph::bufferlist string_buffer(std::string_view value) { + ceph::bufferlist bl; + bl.append(value); + return bl; +} +inline ceph::bufferlist u64_buffer(uint64_t value) { + ceph::bufferlist bl; + using ceph::encode; + encode(value, bl); + return bl; +} + +} // namespace cls::cmpomap diff --git a/src/cls/cmpomap/ops.h b/src/cls/cmpomap/ops.h new file mode 100644 index 000000000..39b1049e8 --- /dev/null +++ b/src/cls/cmpomap/ops.h @@ -0,0 +1,100 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2020 Red Hat, Inc + * + * 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. + */ + +#pragma once + +#include "types.h" +#include "include/encoding.h" + +namespace cls::cmpomap { + +struct cmp_vals_op { + Mode mode; + Op comparison; + ComparisonMap values; + std::optional<ceph::bufferlist> default_value; +}; + +inline void encode(const cmp_vals_op& o, ceph::bufferlist& bl, uint64_t f=0) +{ + ENCODE_START(1, 1, bl); + encode(o.mode, bl); + encode(o.comparison, bl); + encode(o.values, bl); + encode(o.default_value, bl); + ENCODE_FINISH(bl); +} + +inline void decode(cmp_vals_op& o, ceph::bufferlist::const_iterator& bl) +{ + DECODE_START(1, bl); + decode(o.mode, bl); + decode(o.comparison, bl); + decode(o.values, bl); + decode(o.default_value, bl); + DECODE_FINISH(bl); +} + +struct cmp_set_vals_op { + Mode mode; + Op comparison; + ComparisonMap values; + std::optional<ceph::bufferlist> default_value; +}; + +inline void encode(const cmp_set_vals_op& o, ceph::bufferlist& bl, uint64_t f=0) +{ + ENCODE_START(1, 1, bl); + encode(o.mode, bl); + encode(o.comparison, bl); + encode(o.values, bl); + encode(o.default_value, bl); + ENCODE_FINISH(bl); +} + +inline void decode(cmp_set_vals_op& o, ceph::bufferlist::const_iterator& bl) +{ + DECODE_START(1, bl); + decode(o.mode, bl); + decode(o.comparison, bl); + decode(o.values, bl); + decode(o.default_value, bl); + DECODE_FINISH(bl); +} + +struct cmp_rm_keys_op { + Mode mode; + Op comparison; + ComparisonMap values; +}; + +inline void encode(const cmp_rm_keys_op& o, ceph::bufferlist& bl, uint64_t f=0) +{ + ENCODE_START(1, 1, bl); + encode(o.mode, bl); + encode(o.comparison, bl); + encode(o.values, bl); + ENCODE_FINISH(bl); +} + +inline void decode(cmp_rm_keys_op& o, ceph::bufferlist::const_iterator& bl) +{ + DECODE_START(1, bl); + decode(o.mode, bl); + decode(o.comparison, bl); + decode(o.values, bl); + DECODE_FINISH(bl); +} + +} // namespace cls::cmpomap diff --git a/src/cls/cmpomap/server.cc b/src/cls/cmpomap/server.cc new file mode 100644 index 000000000..86e16d940 --- /dev/null +++ b/src/cls/cmpomap/server.cc @@ -0,0 +1,302 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2020 Red Hat, Inc + * + * 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 "objclass/objclass.h" +#include "ops.h" + +CLS_VER(1,0) +CLS_NAME(cmpomap) + +using namespace cls::cmpomap; + +// returns negative error codes or 0/1 for failed/successful comparisons +template <typename T> +static int compare_values(Op op, const T& lhs, const T& rhs) +{ + switch (op) { + case Op::EQ: return (lhs == rhs); + case Op::NE: return (lhs != rhs); + case Op::GT: return (lhs > rhs); + case Op::GTE: return (lhs >= rhs); + case Op::LT: return (lhs < rhs); + case Op::LTE: return (lhs <= rhs); + default: return -EINVAL; + } +} + +static int compare_values_u64(Op op, uint64_t lhs, const bufferlist& value) +{ + // empty values compare as 0 for backward compat + uint64_t rhs = 0; + if (value.length()) { + try { + // decode existing value as rhs + auto p = value.cbegin(); + using ceph::decode; + decode(rhs, p); + } catch (const buffer::error&) { + // failures to decode existing values are reported as EIO + return -EIO; + } + } + return compare_values(op, lhs, rhs); +} + +static int compare_value(Mode mode, Op op, const bufferlist& input, + const bufferlist& value) +{ + switch (mode) { + case Mode::String: + return compare_values(op, input, value); + case Mode::U64: + try { + // decode input value as lhs + uint64_t lhs; + auto p = input.cbegin(); + using ceph::decode; + decode(lhs, p); + return compare_values_u64(op, lhs, value); + } catch (const buffer::error&) { + // failures to decode input values are reported as EINVAL + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int cmp_vals(cls_method_context_t hctx, bufferlist *in, bufferlist *out) +{ + cmp_vals_op op; + try { + auto p = in->cbegin(); + decode(op, p); + } catch (const buffer::error&) { + CLS_LOG(1, "ERROR: cmp_vals(): failed to decode input"); + return -EINVAL; + } + + // collect the keys we need to read + std::set<std::string> keys; + for (const auto& kv : op.values) { + keys.insert(kv.first); + } + + // read the values for each key to compare + std::map<std::string, bufferlist> values; + int r = cls_cxx_map_get_vals_by_keys(hctx, keys, &values); + if (r < 0) { + CLS_LOG(4, "ERROR: cmp_vals() failed to read values r=%d", r); + return r; + } + + auto v = values.cbegin(); + for (const auto& [key, input] : op.values) { + bufferlist value; + if (v != values.end() && v->first == key) { + value = std::move(v->second); + ++v; + CLS_LOG(20, "cmp_vals() comparing key=%s mode=%d op=%d", + key.c_str(), (int)op.mode, (int)op.comparison); + } else if (!op.default_value) { + CLS_LOG(20, "cmp_vals() missing key=%s", key.c_str()); + return -ECANCELED; + } else { + // use optional default for missing keys + value = *op.default_value; + CLS_LOG(20, "cmp_vals() comparing missing key=%s mode=%d op=%d", + key.c_str(), (int)op.mode, (int)op.comparison); + } + + r = compare_value(op.mode, op.comparison, input, value); + if (r < 0) { + CLS_LOG(10, "cmp_vals() failed to compare key=%s r=%d", key.c_str(), r); + return r; + } + if (r == 0) { + CLS_LOG(10, "cmp_vals() comparison at key=%s returned false", key.c_str()); + return -ECANCELED; + } + CLS_LOG(20, "cmp_vals() comparison at key=%s returned true", key.c_str()); + } + + return 0; +} + +static int cmp_set_vals(cls_method_context_t hctx, bufferlist *in, bufferlist *out) +{ + cmp_set_vals_op op; + try { + auto p = in->cbegin(); + decode(op, p); + } catch (const buffer::error&) { + CLS_LOG(1, "ERROR: cmp_set_vals(): failed to decode input"); + return -EINVAL; + } + + // collect the keys we need to read + std::set<std::string> keys; + for (const auto& kv : op.values) { + keys.insert(kv.first); + } + + // read the values for each key to compare + std::map<std::string, bufferlist> values; + int r = cls_cxx_map_get_vals_by_keys(hctx, keys, &values); + if (r < 0) { + CLS_LOG(4, "ERROR: cmp_set_vals() failed to read values r=%d", r); + return r; + } + + auto v = values.begin(); + for (const auto& [key, input] : op.values) { + auto k = values.end(); + bufferlist value; + if (v != values.end() && v->first == key) { + value = std::move(v->second); + k = v++; + CLS_LOG(20, "cmp_set_vals() comparing key=%s mode=%d op=%d", + key.c_str(), (int)op.mode, (int)op.comparison); + } else if (!op.default_value) { + CLS_LOG(20, "cmp_set_vals() missing key=%s", key.c_str()); + continue; + } else { + // use optional default for missing keys + value = *op.default_value; + CLS_LOG(20, "cmp_set_vals() comparing missing key=%s mode=%d op=%d", + key.c_str(), (int)op.mode, (int)op.comparison); + } + + r = compare_value(op.mode, op.comparison, input, value); + if (r == -EIO) { + r = 0; // treat EIO as a failed comparison + } + if (r < 0) { + CLS_LOG(10, "cmp_set_vals() failed to compare key=%s r=%d", + key.c_str(), r); + return r; + } + if (r == 0) { + // unsuccessful comparison + if (k != values.end()) { + values.erase(k); // remove this key from the values to overwrite + CLS_LOG(20, "cmp_set_vals() not overwriting key=%s", key.c_str()); + } else { + CLS_LOG(20, "cmp_set_vals() not writing missing key=%s", key.c_str()); + } + } else { + // successful comparison + if (k != values.end()) { + // overwrite the value + k->second = std::move(input); + CLS_LOG(20, "cmp_set_vals() overwriting key=%s", key.c_str()); + } else { + // insert the value + values.emplace(key, std::move(input)); + CLS_LOG(20, "cmp_set_vals() overwriting missing key=%s", key.c_str()); + } + } + } + + if (values.empty()) { + CLS_LOG(20, "cmp_set_vals() has no values to overwrite"); + return 0; + } + + CLS_LOG(20, "cmp_set_vals() overwriting count=%d", (int)values.size()); + return cls_cxx_map_set_vals(hctx, &values); +} + +static int cmp_rm_keys(cls_method_context_t hctx, bufferlist *in, bufferlist *out) +{ + cmp_rm_keys_op op; + try { + auto p = in->cbegin(); + decode(op, p); + } catch (const buffer::error&) { + CLS_LOG(1, "ERROR: cmp_rm_keys(): failed to decode input"); + return -EINVAL; + } + + // collect the keys we need to read + std::set<std::string> keys; + for (const auto& kv : op.values) { + keys.insert(kv.first); + } + + // read the values for each key to compare + std::map<std::string, bufferlist> values; + int r = cls_cxx_map_get_vals_by_keys(hctx, keys, &values); + if (r < 0) { + CLS_LOG(4, "ERROR: cmp_rm_keys() failed to read values r=%d", r); + return r; + } + + auto v = values.cbegin(); + for (const auto& [key, input] : op.values) { + if (v == values.end() || v->first != key) { + CLS_LOG(20, "cmp_rm_keys() missing key=%s", key.c_str()); + continue; + } + CLS_LOG(20, "cmp_rm_keys() comparing key=%s mode=%d op=%d", + key.c_str(), (int)op.mode, (int)op.comparison); + + const bufferlist& value = v->second; + ++v; + + r = compare_value(op.mode, op.comparison, input, value); + if (r == -EIO) { + r = 0; // treat EIO as a failed comparison + } + if (r < 0) { + CLS_LOG(10, "cmp_rm_keys() failed to compare key=%s r=%d", + key.c_str(), r); + return r; + } + if (r == 0) { + // unsuccessful comparison + CLS_LOG(20, "cmp_rm_keys() preserving key=%s", key.c_str()); + } else { + // successful comparison + CLS_LOG(20, "cmp_rm_keys() removing key=%s", key.c_str()); + r = cls_cxx_map_remove_key(hctx, key); + if (r < 0) { + CLS_LOG(1, "ERROR: cmp_rm_keys() failed to remove key=%s r=%d", + key.c_str(), r); + return r; + } + } + } + + return 0; +} + +CLS_INIT(cmpomap) +{ + CLS_LOG(1, "Loaded cmpomap class!"); + + cls_handle_t h_class; + cls_method_handle_t h_cmp_vals; + cls_method_handle_t h_cmp_set_vals; + cls_method_handle_t h_cmp_rm_keys; + + cls_register("cmpomap", &h_class); + + cls_register_cxx_method(h_class, "cmp_vals", CLS_METHOD_RD, + cmp_vals, &h_cmp_vals); + cls_register_cxx_method(h_class, "cmp_set_vals", CLS_METHOD_RD | CLS_METHOD_WR, + cmp_set_vals, &h_cmp_set_vals); + cls_register_cxx_method(h_class, "cmp_rm_keys", CLS_METHOD_RD | CLS_METHOD_WR, + cmp_rm_keys, &h_cmp_rm_keys); +} diff --git a/src/cls/cmpomap/types.h b/src/cls/cmpomap/types.h new file mode 100644 index 000000000..11e39575f --- /dev/null +++ b/src/cls/cmpomap/types.h @@ -0,0 +1,44 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2020 Red Hat, Inc + * + * 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. + */ + +#pragma once + +#include <string> +#include <boost/container/flat_map.hpp> +#include "include/rados.h" // CEPH_OSD_CMPXATTR_* +#include "include/encoding.h" + +namespace cls::cmpomap { + +/// comparison operand type +enum class Mode : uint8_t { + String = CEPH_OSD_CMPXATTR_MODE_STRING, + U64 = CEPH_OSD_CMPXATTR_MODE_U64, +}; + +/// comparison operation, where the left-hand operand is the input value and +/// the right-hand operand is the stored value (or the optional default) +enum class Op : uint8_t { + EQ = CEPH_OSD_CMPXATTR_OP_EQ, + NE = CEPH_OSD_CMPXATTR_OP_NE, + GT = CEPH_OSD_CMPXATTR_OP_GT, + GTE = CEPH_OSD_CMPXATTR_OP_GTE, + LT = CEPH_OSD_CMPXATTR_OP_LT, + LTE = CEPH_OSD_CMPXATTR_OP_LTE, +}; + +/// mapping of omap keys to value comparisons +using ComparisonMap = boost::container::flat_map<std::string, ceph::bufferlist>; + +} // namespace cls::cmpomap |