summaryrefslogtreecommitdiffstats
path: root/src/cls/hello
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
commit19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch)
tree42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/cls/hello
parentInitial commit. (diff)
downloadceph-upstream/16.2.11+ds.tar.xz
ceph-upstream/16.2.11+ds.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 '')
-rw-r--r--src/cls/hello/cls_hello.cc373
1 files changed, 373 insertions, 0 deletions
diff --git a/src/cls/hello/cls_hello.cc b/src/cls/hello/cls_hello.cc
new file mode 100644
index 000000000..d7263b431
--- /dev/null
+++ b/src/cls/hello/cls_hello.cc
@@ -0,0 +1,373 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+/*
+ * This is a simple example RADOS class, designed to be usable as a
+ * template for implementing new methods.
+ *
+ * Our goal here is to illustrate the interface between the OSD and
+ * the class and demonstrate what kinds of things a class can do.
+ *
+ * Note that any *real* class will probably have a much more
+ * sophisticated protocol dealing with the in and out data buffers.
+ * For an example of the model that we've settled on for handling that
+ * in a clean way, please refer to cls_lock or cls_version for
+ * relatively simple examples of how the parameter encoding can be
+ * encoded in a way that allows for forward and backward compatibility
+ * between client vs class revisions.
+ */
+
+/*
+ * A quick note about bufferlists:
+ *
+ * The bufferlist class allows memory buffers to be concatenated,
+ * truncated, spliced, "copied," encoded/embedded, and decoded. For
+ * most operations no actual data is ever copied, making bufferlists
+ * very convenient for efficiently passing data around.
+ *
+ * bufferlist is actually a typedef of buffer::list, and is defined in
+ * include/buffer.h (and implemented in common/buffer.cc).
+ */
+
+#include <algorithm>
+#include <string>
+#include <sstream>
+#include <cerrno>
+
+#include "objclass/objclass.h"
+#include "osd/osd_types.h"
+
+using std::string;
+using std::ostringstream;
+
+using ceph::bufferlist;
+using ceph::decode;
+using ceph::encode;
+
+CLS_VER(1,0)
+CLS_NAME(hello)
+
+/**
+ * say hello - a "read" method that does not depend on the object
+ *
+ * This is an example of a method that does some computation and
+ * returns data to the caller, without depending on the local object
+ * content.
+ */
+static int say_hello(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ // see if the input data from the client matches what this method
+ // expects to receive. your class can fill this buffer with what it
+ // wants.
+ if (in->length() > 100)
+ return -EINVAL;
+
+ // we generate our reply
+ out->append("Hello, ");
+ if (in->length() == 0)
+ out->append("world");
+ else
+ out->append(*in);
+ out->append("!");
+
+ // this return value will be returned back to the librados caller
+ return 0;
+}
+
+/**
+ * record hello - a "write" method that creates an object
+ *
+ * This method modifies a local object (in this case, by creating it
+ * if it doesn't exist). We make multiple write calls (write,
+ * setxattr) which are accumulated and applied as an atomic
+ * transaction.
+ */
+static int record_hello(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ // we can write arbitrary stuff to the ceph-osd debug log. each log
+ // message is accompanied by an integer log level. smaller is
+ // "louder". how much of this makes it into the log is controlled
+ // by the debug_cls option on the ceph-osd, similar to how other log
+ // levels are controlled. this message, at level 20, will generally
+ // not be seen by anyone unless debug_cls is set at 20 or higher.
+ CLS_LOG(20, "in record_hello");
+
+ // see if the input data from the client matches what this method
+ // expects to receive. your class can fill this buffer with what it
+ // wants.
+ if (in->length() > 100)
+ return -EINVAL;
+
+ // only say hello to non-existent objects
+ if (cls_cxx_stat(hctx, NULL, NULL) == 0)
+ return -EEXIST;
+
+ bufferlist content;
+ content.append("Hello, ");
+ if (in->length() == 0)
+ content.append("world");
+ else
+ content.append(*in);
+ content.append("!");
+
+ // create/write the object
+ int r = cls_cxx_write_full(hctx, &content);
+ if (r < 0)
+ return r;
+
+ // also make note of who said it
+ entity_inst_t origin;
+ cls_get_request_origin(hctx, &origin);
+ ostringstream ss;
+ ss << origin;
+ bufferlist attrbl;
+ attrbl.append(ss.str());
+ r = cls_cxx_setxattr(hctx, "said_by", &attrbl);
+ if (r < 0)
+ return r;
+
+ // For write operations, there are two possible outcomes:
+ //
+ // * For a failure, we return a negative error code. The out
+ // buffer can contain any data that we want, and that data will
+ // be returned to the caller. No change is made to the object.
+ //
+ // * For a success, we must return 0 and *no* data in the out
+ // buffer. This is becaues the OSD does not log write result
+ // codes or output buffers and we need a replayed/resent
+ // operation (e.g., after a TCP disconnect) to be idempotent.
+ //
+ // If a class returns a positive value or puts data in the out
+ // buffer, the OSD code will ignore it and return 0 to the
+ // client.
+ return 0;
+}
+
+static int write_return_data(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ // make some change to the object
+ bufferlist attrbl;
+ attrbl.append("bar");
+ int r = cls_cxx_setxattr(hctx, "foo", &attrbl);
+ if (r < 0)
+ return r;
+
+ if (in->length() > 0) {
+ // note that if we return anything < 0 (an error), this
+ // operation/transaction will abort, and the setattr above will
+ // never happen. however, we *can* return data on error.
+ out->append("too much input data!");
+ return -EINVAL;
+ }
+
+ // try to return some data. note that this will only reach the client
+ // if the client has set the CEPH_OSD_FLAG_RETURNVEC flag on the op.
+ out->append("you might see this");
+
+ // client will only see a >0 value with the RETURNVEC flag is set; otherwise
+ // they will see 0.
+ return 42;
+}
+
+static int write_too_much_return_data(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ // make some change to the object
+ bufferlist attrbl;
+ attrbl.append("bar");
+ int r = cls_cxx_setxattr(hctx, "foo", &attrbl);
+ if (r < 0)
+ return r;
+
+ // try to return too much data. this should be enough to exceed
+ // osd_max_write_op_reply_len, which defaults to a pretty small number.
+ for (unsigned i=0; i < 10; ++i) {
+ out->append("you should not see this because it is toooooo long. ");
+ }
+
+ return 42;
+}
+
+/**
+ * replay - a "read" method to get a previously recorded hello
+ *
+ * This is a read method that will retrieve a previously recorded
+ * hello statement.
+ */
+static int replay(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ // read contents out of the on-disk object. our behavior can be a
+ // function of either the request alone, or the request and the
+ // on-disk state, depending on whether the RD flag is specified when
+ // registering the method (see the __cls__init function below).
+ int r = cls_cxx_read(hctx, 0, 1100, out);
+ if (r < 0)
+ return r;
+
+ // note that our return value need not be the length of the returned
+ // data; it can be whatever value we want: positive, zero or
+ // negative (this is a read).
+ return 0;
+}
+
+/**
+ * turn_it_to_11 - a "write" method that mutates existing object data
+ *
+ * A write method can depend on previous object content (i.e., perform
+ * a read/modify/write operation). This atomically transitions the
+ * object state from the old content to the new content.
+ */
+static int turn_it_to_11(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ // see if the input data from the client matches what this method
+ // expects to receive. your class can fill this buffer with what it
+ // wants.
+ if (in->length() != 0)
+ return -EINVAL;
+
+ bufferlist previous;
+ int r = cls_cxx_read(hctx, 0, 1100, &previous);
+ if (r < 0)
+ return r;
+
+ std::string str(previous.c_str(), previous.length());
+ std::transform(str.begin(), str.end(), str.begin(), ::toupper);
+ previous.clear();
+ previous.append(str);
+
+ // replace previous byte data content (write_full == truncate(0) + write)
+ r = cls_cxx_write_full(hctx, &previous);
+ if (r < 0)
+ return r;
+
+ // record who did it
+ entity_inst_t origin;
+ cls_get_request_origin(hctx, &origin);
+ ostringstream ss;
+ ss << origin;
+ bufferlist attrbl;
+ attrbl.append(ss.str());
+ r = cls_cxx_setxattr(hctx, "amplified_by", &attrbl);
+ if (r < 0)
+ return r;
+
+ // return value is 0 for success; out buffer is empty.
+ return 0;
+}
+
+/**
+ * example method that does not behave
+ *
+ * This method is registered as WR but tries to read
+ */
+static int bad_reader(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ return cls_cxx_read(hctx, 0, 100, out);
+}
+
+/**
+ * example method that does not behave
+ *
+ * This method is registered as RD but tries to write
+ */
+static int bad_writer(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ return cls_cxx_write_full(hctx, in);
+}
+
+
+class PGLSHelloFilter : public PGLSFilter {
+ string val;
+public:
+ int init(bufferlist::const_iterator& params) override {
+ try {
+ decode(xattr, params);
+ decode(val, params);
+ } catch (ceph::buffer::error &e) {
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ ~PGLSHelloFilter() override {}
+ bool filter(const hobject_t& obj,
+ const bufferlist& xattr_data) const override
+ {
+ return xattr_data.contents_equal(val.c_str(), val.size());
+ }
+};
+
+
+PGLSFilter *hello_filter()
+{
+ return new PGLSHelloFilter();
+}
+
+
+/**
+ * initialize class
+ *
+ * We do two things here: we register the new class, and then register
+ * all of the class's methods.
+ */
+CLS_INIT(hello)
+{
+ // this log message, at level 0, will always appear in the ceph-osd
+ // log file.
+ CLS_LOG(0, "loading cls_hello");
+
+ cls_handle_t h_class;
+ cls_method_handle_t h_say_hello;
+ cls_method_handle_t h_record_hello;
+ cls_method_handle_t h_replay;
+ cls_method_handle_t h_write_return_data;
+ cls_method_handle_t h_writes_dont_return_data;
+ cls_method_handle_t h_write_too_much_return_data;
+ cls_method_handle_t h_turn_it_to_11;
+ cls_method_handle_t h_bad_reader;
+ cls_method_handle_t h_bad_writer;
+
+ cls_register("hello", &h_class);
+
+ // There are two flags we specify for methods:
+ //
+ // RD : whether this method (may) read prior object state
+ // WR : whether this method (may) write or update the object
+ //
+ // A method can be RD, WR, neither, or both. If a method does
+ // neither, the data it returns to the caller is a function of the
+ // request and not the object contents.
+
+ cls_register_cxx_method(h_class, "say_hello",
+ CLS_METHOD_RD,
+ say_hello, &h_say_hello);
+ cls_register_cxx_method(h_class, "record_hello",
+ CLS_METHOD_WR | CLS_METHOD_PROMOTE,
+ record_hello, &h_record_hello);
+ cls_register_cxx_method(h_class, "write_return_data",
+ CLS_METHOD_WR,
+ write_return_data, &h_write_return_data);
+ // legacy alias for this method for pre-octopus clients
+ cls_register_cxx_method(h_class, "writes_dont_return_data",
+ CLS_METHOD_WR,
+ write_return_data, &h_writes_dont_return_data);
+ cls_register_cxx_method(h_class, "write_too_much_return_data",
+ CLS_METHOD_WR,
+ write_too_much_return_data, &h_write_too_much_return_data);
+ cls_register_cxx_method(h_class, "replay",
+ CLS_METHOD_RD,
+ replay, &h_replay);
+
+ // RD | WR is a read-modify-write method.
+ cls_register_cxx_method(h_class, "turn_it_to_11",
+ CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PROMOTE,
+ turn_it_to_11, &h_turn_it_to_11);
+
+ // counter-examples
+ cls_register_cxx_method(h_class, "bad_reader", CLS_METHOD_WR,
+ bad_reader, &h_bad_reader);
+ cls_register_cxx_method(h_class, "bad_writer", CLS_METHOD_RD,
+ bad_writer, &h_bad_writer);
+
+ // A PGLS filter
+ cls_register_cxx_filter(h_class, "hello", hello_filter);
+}