]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
cls/hello: hello, world rados class 504/head
authorSage Weil <sage@inktank.com>
Fri, 16 Aug 2013 00:20:43 +0000 (17:20 -0700)
committerSage Weil <sage@inktank.com>
Fri, 16 Aug 2013 00:21:29 +0000 (17:21 -0700)
Simple example of a rados class doing read, write, and read/modify/write
methods.

Signed-off-by: Sage Weil <sage@inktank.com>
ceph.spec.in
debian/ceph-test.install
qa/workunits/cls/test_cls_hello.sh [new file with mode: 0755]
src/Makefile.am
src/cls/hello/cls_hello.cc [new file with mode: 0644]
src/test/cls_hello/test_cls_hello.cc [new file with mode: 0644]

index 1a22b12f5c49152f16f196ebd2524dafe6616000..0e18f2b656d0f58611968d14f2ecb94ac24427aa 100644 (file)
@@ -614,6 +614,7 @@ fi
 %{_bindir}/ceph_filestore_dump
 %{_bindir}/ceph_streamtest
 %{_bindir}/ceph_test_cfuse_cache_invalidate
+%{_bindir}/ceph_test_cls_hello
 %{_bindir}/ceph_test_cls_lock
 %{_bindir}/ceph_test_cls_log
 %{_bindir}/ceph_test_cls_rbd
index 764e6d1d57348d5de129d5e7857fa2d16f78156e..c5a5e0a97743e1ebf54b82bff9adceb097d7550a 100644 (file)
@@ -17,6 +17,7 @@ usr/bin/ceph_smalliobenchfs
 usr/bin/ceph_smalliobenchrbd
 usr/bin/ceph_streamtest
 usr/bin/ceph_test_cfuse_cache_invalidate
+usr/bin/ceph_test_cls_hello
 usr/bin/ceph_test_cls_lock
 usr/bin/ceph_test_cls_log
 usr/bin/ceph_test_cls_rbd
diff --git a/qa/workunits/cls/test_cls_hello.sh b/qa/workunits/cls/test_cls_hello.sh
new file mode 100755 (executable)
index 0000000..0a2e096
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/sh -e
+
+ceph_test_cls_hello
+
+exit 0
index e93bbb8f53727e691f77cc93a20eda954128eb99..de1b81fb4ba4ffbbbd987603b4d6bf7c65b61b75 100644 (file)
@@ -531,6 +531,18 @@ endif
 
 ## rados object classes
 
+radoslibdir = $(libdir)/rados-classes
+radoslib_LTLIBRARIES =
+
+# hello world class
+libcls_hello_la_SOURCES = cls/hello/cls_hello.cc
+libcls_hello_la_CFLAGS = ${AM_CFLAGS}
+libcls_hello_la_CXXFLAGS= ${AM_CXXFLAGS}
+libcls_hello_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
+libcls_hello_la_LDFLAGS = ${AM_LDFLAGS} -version-info 1:0:0 -export-symbols-regex '.*__cls_.*'
+
+radoslib_LTLIBRARIES += libcls_hello.la
+
 # rbd: rados block device class
 libcls_rbd_la_SOURCES = cls/rbd/cls_rbd.cc
 libcls_rbd_la_CFLAGS = ${AM_CFLAGS}
@@ -538,9 +550,7 @@ libcls_rbd_la_CXXFLAGS= ${AM_CXXFLAGS}
 libcls_rbd_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
 libcls_rbd_la_LDFLAGS = ${AM_LDFLAGS} -version-info 1:0:0 -export-symbols-regex '.*__cls_.*'
 
-radoslibdir = $(libdir)/rados-classes
-radoslib_LTLIBRARIES = libcls_rbd.la
-
+radoslib_LTLIBRARIES += libcls_rbd.la
 
 # lock class
 libcls_lock_la_SOURCES = cls/lock/cls_lock.cc
@@ -548,7 +558,6 @@ libcls_lock_la_CFLAGS = ${AM_CFLAGS}
 libcls_lock_la_CXXFLAGS= ${AM_CXXFLAGS}
 libcls_lock_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
 libcls_lock_la_LDFLAGS = ${AM_LDFLAGS} -version-info 1:0:0 -export-symbols-regex '.*__cls_.*'
-
 radoslib_LTLIBRARIES += libcls_lock.la
 
 # refcount class
@@ -1082,6 +1091,11 @@ ceph_test_cls_lock_LDADD =  libcls_lock_client.a librados.la ${UNITTEST_STATIC_L
 ceph_test_cls_lock_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS}
 bin_DEBUGPROGRAMS += ceph_test_cls_lock
 
+ceph_test_cls_hello_SOURCES = test/cls_hello/test_cls_hello.cc test/librados/test.cc
+ceph_test_cls_hello_LDADD = ${UNITTEST_LDADD} ${UNITTEST_STATIC_LDADD} $(LIBGLOBAL_LDA) $(CRYPTO_LIBS) librados.la
+ceph_test_cls_hello_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS}
+bin_DEBUGPROGRAMS += ceph_test_cls_hello
+
 if WITH_RADOSGW
 
 ceph_test_cls_rgw_SOURCES = test/cls_rgw/test_cls_rgw.cc \
diff --git a/src/cls/hello/cls_hello.cc b/src/cls/hello/cls_hello.cc
new file mode 100644 (file)
index 0000000..0d5c78b
--- /dev/null
@@ -0,0 +1,307 @@
+// -*- 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 from 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 <errno.h>
+
+#include "objclass/objclass.h"
+
+CLS_VER(1,0)
+CLS_NAME(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_writes_dont_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;
+
+/**
+ * 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 writes_dont_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 *won't* reach the
+  // client!  see the matching test case in test_cls_hello.cc.
+  out->append("you will never see this");
+
+  // if we try to return anything > 0 here the client will see 0.
+  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);
+}
+
+
+/**
+ * initialize class
+ *
+ * We do two things here: we register the new class, and then register
+ * all of the class's methods.
+ */
+void __cls_init()
+{
+  // this log message, at level 0, will always appear in the ceph-osd
+  // log file.
+  CLS_LOG(0, "loading cls_hello");
+
+  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,
+                         record_hello, &h_record_hello);
+  cls_register_cxx_method(h_class, "writes_dont_return_data",
+                         CLS_METHOD_WR,
+                         writes_dont_return_data, &h_writes_dont_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,
+                         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);
+}
diff --git a/src/test/cls_hello/test_cls_hello.cc b/src/test/cls_hello/test_cls_hello.cc
new file mode 100644 (file)
index 0000000..58ecb97
--- /dev/null
@@ -0,0 +1,133 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2013 Inktank
+ *
+ * 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 <iostream>
+#include <errno.h>
+
+#include "include/rados/librados.hpp"
+#include "test/librados/test.h"
+#include "gtest/gtest.h"
+
+using namespace librados;
+
+TEST(ClsHello, SayHello) {
+  Rados cluster;
+  std::string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+  IoCtx ioctx;
+  cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+  bufferlist in, out;
+  ASSERT_EQ(-ENOENT, ioctx.exec("myobject", "hello", "say_hello", in, out));
+  ASSERT_EQ(0, ioctx.write_full("myobject", in));
+  ASSERT_EQ(0, ioctx.exec("myobject", "hello", "say_hello", in, out));
+  ASSERT_EQ(std::string("Hello, world!"), std::string(out.c_str(), out.length()));
+
+  out.clear();
+  in.append("Tester");
+  ASSERT_EQ(0, ioctx.exec("myobject", "hello", "say_hello", in, out));
+  ASSERT_EQ(std::string("Hello, Tester!"), std::string(out.c_str(), out.length()));
+
+  out.clear();
+  in.clear();
+  char buf[4096];
+  memset(buf, 1, sizeof(buf));
+  in.append(buf, sizeof(buf));
+  ASSERT_EQ(-EINVAL, ioctx.exec("myobject", "hello", "say_hello", in, out));
+
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster));
+}
+
+TEST(ClsHello, RecordHello) {
+  Rados cluster;
+  std::string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+  IoCtx ioctx;
+  cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+  bufferlist in, out;
+  ASSERT_EQ(0, ioctx.exec("myobject", "hello", "record_hello", in, out));
+  ASSERT_EQ(-EEXIST, ioctx.exec("myobject", "hello", "record_hello", in, out));
+
+  in.append("Tester");
+  ASSERT_EQ(0, ioctx.exec("myobject2", "hello", "record_hello", in, out));
+  ASSERT_EQ(-EEXIST, ioctx.exec("myobject2", "hello", "record_hello", in, out));
+  ASSERT_EQ(0u, out.length());
+
+  in.clear();
+  out.clear();
+  ASSERT_EQ(0, ioctx.exec("myobject", "hello", "replay", in, out));
+  ASSERT_EQ(std::string("Hello, world!"), std::string(out.c_str(), out.length()));
+  out.clear();
+  ASSERT_EQ(0, ioctx.exec("myobject2", "hello", "replay", in, out));
+  ASSERT_EQ(std::string("Hello, Tester!"), std::string(out.c_str(), out.length()));
+
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster));
+}
+
+TEST(ClsHello, WriteReturnData) {
+  Rados cluster;
+  std::string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+  IoCtx ioctx;
+  cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+  bufferlist in, out;
+  ASSERT_EQ(0, ioctx.exec("myobject", "hello", "writes_dont_return_data", in, out));
+  ASSERT_EQ(std::string(), std::string(out.c_str(), out.length()));
+
+  char buf[4096];
+  memset(buf, 1, sizeof(buf));
+  in.append(buf, sizeof(buf));
+  ASSERT_EQ(-EINVAL, ioctx.exec("myobject2", "hello", "writes_dont_return_data", in, out));
+  ASSERT_EQ(std::string("too much input data!"), std::string(out.c_str(), out.length()));
+  ASSERT_EQ(-ENOENT, ioctx.getxattr("myobject2", "foo", out));
+
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster));
+}
+
+TEST(ClsHello, Loud) {
+  Rados cluster;
+  std::string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+  IoCtx ioctx;
+  cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+  bufferlist in, out;
+  ASSERT_EQ(0, ioctx.exec("myobject", "hello", "record_hello", in, out));
+  ASSERT_EQ(0, ioctx.exec("myobject", "hello", "replay", in, out));
+  ASSERT_EQ(std::string("Hello, world!"), std::string(out.c_str(), out.length()));
+
+  ASSERT_EQ(0, ioctx.exec("myobject", "hello", "turn_it_to_11", in, out));
+  ASSERT_EQ(0, ioctx.exec("myobject", "hello", "replay", in, out));
+  ASSERT_EQ(std::string("HELLO, WORLD!"), std::string(out.c_str(), out.length()));
+
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster));
+}
+
+TEST(ClsHello, BadMethods) {
+  Rados cluster;
+  std::string pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+  IoCtx ioctx;
+  cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+  bufferlist in, out;
+
+  ASSERT_EQ(0, ioctx.write_full("myobject", in));
+  ASSERT_EQ(-EIO, ioctx.exec("myobject", "hello", "bad_reader", in, out));
+  ASSERT_EQ(-EIO, ioctx.exec("myobject", "hello", "bad_writer", in, out));
+
+  ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster));
+}