mirror of
https://github.com/ceph/ceph
synced 2025-02-22 02:27:29 +00:00
cls/hello: hello, world rados class
Simple example of a rados class doing read, write, and read/modify/write methods. Signed-off-by: Sage Weil <sage@inktank.com>
This commit is contained in:
parent
359850b9f6
commit
895d531cc9
@ -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
|
||||
|
1
debian/ceph-test.install
vendored
1
debian/ceph-test.install
vendored
@ -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
|
||||
|
5
qa/workunits/cls/test_cls_hello.sh
Executable file
5
qa/workunits/cls/test_cls_hello.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
ceph_test_cls_hello
|
||||
|
||||
exit 0
|
@ -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 \
|
||||
|
307
src/cls/hello/cls_hello.cc
Normal file
307
src/cls/hello/cls_hello.cc
Normal file
@ -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);
|
||||
}
|
133
src/test/cls_hello/test_cls_hello.cc
Normal file
133
src/test/cls_hello/test_cls_hello.cc
Normal file
@ -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));
|
||||
}
|
Loading…
Reference in New Issue
Block a user