TEST_OP_APPEND,
TEST_OP_APPEND_EXCL,
TEST_OP_SET_REDIRECT,
- TEST_OP_UNSET_REDIRECT
+ TEST_OP_UNSET_REDIRECT,
+ TEST_OP_CHUNK_READ
};
class TestWatchContext : public librados::WatchCtx2 {
redirect_objs[oid] = target;
}
+ void update_object_chunk_target(const string &oid, uint64_t offset, ChunkDesc info)
+ {
+ for (map<int, map<string,ObjectDesc> >::const_reverse_iterator i =
+ pool_obj_cont.rbegin();
+ i != pool_obj_cont.rend();
+ ++i) {
+ if (i->second.count(oid) != 0) {
+ ObjectDesc obj_desc = i->second.find(oid)->second;
+ obj_desc.chunk_info[offset] = info;
+ update_object_full(oid, obj_desc);
+ return ;
+ }
+ }
+ return;
+ }
+
bool object_existed_at(const string &oid, int snap = -1) const
{
ObjectDesc contents;
}
};
+class ChunkReadOp : public TestOp {
+public:
+ vector<librados::AioCompletion *> completions;
+ librados::ObjectReadOperation op;
+ string oid;
+ ObjectDesc old_value;
+ ObjectDesc tgt_value;
+ int snap;
+ bool balance_reads;
+
+ ceph::shared_ptr<int> in_use;
+
+ vector<bufferlist> results;
+ vector<int> retvals;
+ vector<bool> is_sparse_read;
+ uint64_t waiting_on;
+
+ vector<bufferlist> checksums;
+ vector<int> checksum_retvals;
+ uint32_t offset;
+ uint32_t length;
+ string tgt_oid;
+ string tgt_pool_name;
+ uint32_t tgt_offset;
+
+ ChunkReadOp(int n,
+ RadosTestContext *context,
+ const string &oid,
+ string tgt_pool_name,
+ bool balance_reads,
+ TestOpStat *stat = 0)
+ : TestOp(n, context, stat),
+ completions(2),
+ oid(oid),
+ snap(0),
+ balance_reads(balance_reads),
+ results(2),
+ retvals(2),
+ waiting_on(0),
+ checksums(2),
+ checksum_retvals(2),
+ tgt_pool_name(tgt_pool_name)
+ {}
+
+ void _do_read(librados::ObjectReadOperation& read_op, uint32_t offset, uint32_t length, int index) {
+ read_op.read(offset,
+ length,
+ &results[index],
+ &retvals[index]);
+ if (index != 0) {
+ bufferlist init_value_bl;
+ ::encode(static_cast<uint32_t>(-1), init_value_bl);
+ read_op.checksum(LIBRADOS_CHECKSUM_TYPE_CRC32C, init_value_bl, offset, length,
+ 0, &checksums[index], &checksum_retvals[index]);
+ }
+
+ }
+
+ void _begin() override
+ {
+ context->state_lock.Lock();
+ std::cout << num << ": chunk read oid " << oid << " snap " << snap << std::endl;
+ done = 0;
+ for (uint32_t i = 0; i < 2; i++) {
+ completions[i] = context->rados.aio_create_completion((void *) this, &read_callback, 0);
+ }
+
+ context->find_object(oid, &old_value);
+
+ if (old_value.chunk_info.size() == 0) {
+ std::cout << ": no chunks" << std::endl;
+ context->kick();
+ context->state_lock.Unlock();
+ done = true;
+ return;
+ }
+
+ context->oid_in_use.insert(oid);
+ context->oid_not_in_use.erase(oid);
+ if (old_value.deleted()) {
+ std::cout << num << ": expect deleted" << std::endl;
+ } else {
+ std::cout << num << ": expect " << old_value.most_recent() << std::endl;
+ }
+
+ int rand_index = rand() % old_value.chunk_info.size();
+ auto iter = old_value.chunk_info.begin();
+ for (int i = 0; i < rand_index; i++) {
+ iter++;
+ }
+ offset = iter->first;
+ offset += (rand() % iter->second.length)/2;
+ uint32_t t_length = rand() % iter->second.length;
+ while (t_length + offset > iter->first + iter->second.length) {
+ t_length = rand() % iter->second.length;
+ }
+ length = t_length;
+ tgt_offset = iter->second.offset + offset - iter->first;
+ tgt_oid = iter->second.oid;
+
+ std::cout << num << ": ori offset " << iter->first << " req offset " << offset
+ << " ori length " << iter->second.length << " req length " << length
+ << " ori tgt_offset " << iter->second.offset << " req tgt_offset " << tgt_offset
+ << " tgt_oid " << tgt_oid << std::endl;
+
+ TestWatchContext *ctx = context->get_watch_context(oid);
+ context->state_lock.Unlock();
+ if (ctx) {
+ assert(old_value.exists);
+ TestAlarm alarm;
+ std::cerr << num << ": about to start" << std::endl;
+ ctx->start();
+ std::cerr << num << ": started" << std::endl;
+ bufferlist bl;
+ context->io_ctx.set_notify_timeout(600);
+ int r = context->io_ctx.notify2(context->prefix+oid, bl, 0, NULL);
+ if (r < 0) {
+ std::cerr << "r is " << r << std::endl;
+ ceph_abort();
+ }
+ std::cerr << num << ": notified, waiting" << std::endl;
+ ctx->wait();
+ }
+ context->state_lock.Lock();
+
+ _do_read(op, offset, length, 0);
+
+ unsigned flags = 0;
+ if (balance_reads)
+ flags |= librados::OPERATION_BALANCE_READS;
+
+ assert(!context->io_ctx.aio_operate(context->prefix+oid, completions[0], &op,
+ flags, NULL));
+ waiting_on++;
+
+ _do_read(op, tgt_offset, length, 1);
+ assert(!context->io_ctx.aio_operate(context->prefix+tgt_oid, completions[1], &op,
+ flags, NULL));
+
+ waiting_on++;
+ context->state_lock.Unlock();
+ }
+
+ void _finish(CallbackInfo *info) override
+ {
+ Mutex::Locker l(context->state_lock);
+ assert(!done);
+ assert(waiting_on > 0);
+ if (--waiting_on) {
+ return;
+ }
+
+ context->oid_in_use.erase(oid);
+ context->oid_not_in_use.insert(oid);
+ int retval = completions[0]->get_return_value();
+ std::cout << ": finish!! ret: " << retval << std::endl;
+ context->find_object(tgt_oid, &tgt_value);
+
+ for (int i = 0; i < 2; i++) {
+ assert(completions[i]->is_complete());
+ uint64_t version = completions[i]->get_version64();
+ int err = completions[i]->get_return_value();
+ if (err != retval) {
+ cerr << num << ": Error: oid " << oid << " read returned different error codes: "
+ << retval << " and " << err << std::endl;
+ ceph_abort();
+ }
+ if (err) {
+ if (!(err == -ENOENT && old_value.deleted())) {
+ cerr << num << ": Error: oid " << oid << " read returned error code "
+ << err << std::endl;
+ ceph_abort();
+ }
+ }
+ if (version != tgt_value.version) {
+ cerr << num << ": oid " << oid << " version is " << version
+ << " and expected " << tgt_value.version << std::endl;
+ assert(version == tgt_value.version);
+ }
+ }
+
+ if (!retval) {
+ if (old_value.deleted()) {
+ std::cout << num << ": expect deleted" << std::endl;
+ assert(0 == "expected deleted");
+ } else {
+ std::cout << num << ": expect " << old_value.most_recent() << std::endl;
+ }
+ if (tgt_value.has_contents()) {
+ uint32_t checksum[2] = {0};
+ if (checksum_retvals[1] == 0) {
+ try {
+ auto bl_it = checksums[1].begin();
+ uint32_t csum_count;
+ ::decode(csum_count, bl_it);
+ ::decode(checksum[1], bl_it);
+ } catch (const buffer::error &err) {
+ checksum_retvals[1] = -EBADMSG;
+ }
+ }
+
+ if (checksum_retvals[1] != 0) {
+ cerr << num << ": oid " << oid << " checksum retvals " << checksums[0]
+ << " error " << std::endl;
+ context->errors++;
+ }
+
+ checksum[0] = results[0].crc32c(-1);
+
+ if (checksum[0] != checksum[1]) {
+ cerr << num << ": oid " << oid << " checksum src " << checksum[0]
+ << " chunksum tgt " << checksum[1] << " incorrect, expecting "
+ << results[0].crc32c(-1)
+ << std::endl;
+ context->errors++;
+ }
+ if (context->errors) ceph_abort();
+ }
+ }
+ for (vector<librados::AioCompletion *>::iterator it = completions.begin();
+ it != completions.end(); ++it) {
+ (*it)->release();
+ }
+ context->kick();
+ done = true;
+ }
+
+ bool finished() override
+ {
+ return done;
+ }
+
+ string getType() override
+ {
+ return "ChunkReadOp";
+ }
+};
+
+class SetChunkOp : public TestOp {
+public:
+ string oid, oid_tgt, tgt_pool_name;
+ ObjectDesc src_value, tgt_value;
+ librados::ObjectWriteOperation op;
+ librados::ObjectReadOperation rd_op;
+ librados::AioCompletion *comp;
+ ceph::shared_ptr<int> in_use;
+ int done;
+ int r;
+ uint64_t offset;
+ uint32_t length;
+ uint64_t tgt_offset;
+ SetChunkOp(int n,
+ RadosTestContext *context,
+ const string &oid,
+ uint64_t offset,
+ uint32_t length,
+ const string &oid_tgt,
+ const string &tgt_pool_name,
+ uint64_t tgt_offset,
+ TestOpStat *stat = 0)
+ : TestOp(n, context, stat),
+ oid(oid), oid_tgt(oid_tgt), tgt_pool_name(tgt_pool_name),
+ comp(NULL), done(0),
+ r(0), offset(offset), length(length),
+ tgt_offset(tgt_offset)
+ {}
+
+ void _begin() override
+ {
+ Mutex::Locker l(context->state_lock);
+ context->oid_in_use.insert(oid);
+ context->oid_not_in_use.erase(oid);
+
+ context->find_object(oid, &src_value);
+
+ comp = context->rados.aio_create_completion();
+ rd_op.stat(NULL, NULL, NULL);
+ context->io_ctx.aio_operate(context->prefix+oid, comp, &rd_op,
+ librados::OPERATION_ORDER_READS_WRITES,
+ NULL);
+ comp->wait_for_safe();
+ if ((r = comp->get_return_value()) && !src_value.deleted()) {
+ cerr << "Error: oid " << oid << " stat returned error code "
+ << r << std::endl;
+ ceph_abort();
+ }
+ context->update_object_version(oid, comp->get_version64());
+ comp->release();
+
+ context->find_object(oid, &src_value);
+ context->find_object(oid_tgt, &tgt_value);
+
+ if (src_value.version != 0 && !src_value.deleted())
+ op.assert_version(src_value.version);
+ op.set_chunk(offset, length, context->io_ctx, context->prefix+oid_tgt, tgt_offset);
+
+ pair<TestOp*, TestOp::CallbackInfo*> *cb_arg =
+ new pair<TestOp*, TestOp::CallbackInfo*>(this,
+ new TestOp::CallbackInfo(0));
+ comp = context->rados.aio_create_completion((void*) cb_arg, NULL,
+ &write_callback);
+ context->io_ctx.aio_operate(context->prefix+oid, comp, &op,
+ librados::OPERATION_ORDER_READS_WRITES);
+ }
+
+ void _finish(CallbackInfo *info) override
+ {
+ Mutex::Locker l(context->state_lock);
+
+ if (info->id == 0) {
+ assert(comp->is_complete());
+ cout << num << ": finishing set_chunk to oid " << oid << std::endl;
+ if ((r = comp->get_return_value())) {
+ if (r == -ENOENT && src_value.deleted()) {
+ cout << num << ": got expected ENOENT (src dne)" << std::endl;
+ } else if (r == -EOPNOTSUPP) {
+ bool is_overlapped = false;
+ for (auto &p : src_value.chunk_info) {
+ if ((p.first <= offset && p.first + p.second.length > offset) ||
+ (p.first > offset && p.first <= offset + length)) {
+ cout << " range is overlapped offset: " << offset << " length: " << length
+ << " chunk_info offset: " << p.second.offset << " length "
+ << p.second.length << std::endl;
+ is_overlapped = true;
+ context->update_object_version(oid, comp->get_version64());
+ }
+ }
+ if (!is_overlapped) {
+ cerr << "Error: oid " << oid << " set_chunk " << oid_tgt << " returned error code "
+ << r << " offset: " << offset << " length: " << length << std::endl;
+ ceph_abort();
+ }
+ } else {
+ cerr << "Error: oid " << oid << " set_chunk " << oid_tgt << " returned error code "
+ << r << std::endl;
+ ceph_abort();
+ }
+ } else {
+ ChunkDesc info;
+ info.offset = tgt_offset;
+ info.length = length;
+ info.oid = oid_tgt;
+ context->update_object_chunk_target(oid, offset, info);
+ context->update_object_version(oid, comp->get_version64());
+ }
+ }
+
+ if (++done == 1) {
+ context->oid_in_use.erase(oid);
+ context->oid_not_in_use.insert(oid);
+ context->kick();
+ }
+ }
+
+ bool finished() override
+ {
+ return done == 1;
+ }
+
+ string getType() override
+ {
+ return "SetChunkOp";
+ }
+};
+
class SetRedirectOp : public TestOp {
public:
string oid, oid_tgt, tgt_pool_name;
int max_seconds,
bool ec_pool,
bool balance_reads,
- bool set_redirect) :
+ bool set_redirect,
+ bool set_chunk) :
m_nextop(NULL), m_op(0), m_ops(ops), m_seconds(max_seconds),
m_objects(objects), m_stats(stats),
m_total_weight(0),
m_ec_pool(ec_pool),
m_balance_reads(balance_reads),
- m_set_redirect(set_redirect)
+ m_set_redirect(set_redirect),
+ m_set_chunk(set_chunk)
{
m_start = time(0);
for (map<TestOpType, unsigned int>::const_iterator it = op_weights.begin();
m_weight_sums.insert(pair<TestOpType, unsigned int>(it->first,
m_total_weight));
}
- if (m_set_redirect) {
- /* create redirect objects + set-redirect*/
- m_redirect_objects = objects*2; // for copy_from + set-redirect test
- m_initial_redirected_objects = objects;
- m_ops = ops+m_redirect_objects+m_initial_redirected_objects;
+ if (m_set_redirect || m_set_chunk) {
+ m_redirect_objects = objects*2; // for creating objects using copy_from
+ m_initial_redirected_objects = objects; // for rediect or set-chunk
+ // we need pre-initialization process before run the test
+ // so, m_ops is increased
+ if (m_set_redirect) {
+ m_ops = ops+m_redirect_objects+m_initial_redirected_objects;
+ } else {
+ /* create 10 chunks per an object*/
+ m_ops = ops+m_redirect_objects+m_initial_redirected_objects+m_objects*10;
+ }
}
}
return NULL;
}
- if (m_set_redirect) {
+ if (m_set_redirect || m_set_chunk) {
if (pre_init_extensible_tier(context, retval)) {
return retval;
}
- }
+ }
if (m_nextop) {
retval = m_nextop;
return retval;
}
- bool pre_init_extensible_tier(RadosTestContext &context, TestOp *& op)
- {
+ bool pre_init_extensible_tier(RadosTestContext &context, TestOp *& op) {
/*
- * set-redirect test
- * 1. create objects (copy from)
- * 2. set-redirect
+ * set-redirect or set-chunk test
+ * 0. create default objects
+ * 1. create target objects (using copy from)
+ * 2. set-redirect or set-chunk
+ * 3. wait for set-* completion
*/
int create_objects_end = m_objects + m_redirect_objects;
int set_redirect_end = create_objects_end + m_initial_redirected_objects;
+ if (m_set_chunk) {
+ /* create 10 chunks per an object*/
+ set_redirect_end += set_redirect_end + m_objects * 10;
+ }
if (m_op <= create_objects_end) {
stringstream oid;
if ((_oid2) % 2) {
oid2 << " " << string(300, 'o');
}
- cout << m_op << ": " << "(create redirect oid) copy_from oid " << oid.str()
+ cout << m_op << ": " << "(create target oid) copy_from oid " << oid.str()
<< " from oid " << oid2.str() << std::endl;
op = new CopyFromOp(m_op, &context, oid.str(), oid2.str(), m_stats);
return true;
} else if (m_op <= set_redirect_end) {
- stringstream oid;
- int _oid = m_op-create_objects_end;
- oid << _oid;
- if ((_oid) % 2) {
- oid << " " << string(300, 'o');
- }
- stringstream oid2;
- int _oid2 = _oid + m_objects;
- oid2 << _oid2;
- if ((_oid2) % 2) {
- oid2 << " " << string(300, 'o');
+ if (m_set_redirect) {
+ stringstream oid;
+ int _oid = m_op-create_objects_end;
+ oid << _oid;
+ if ((_oid) % 2) {
+ oid << " " << string(300, 'o');
+ }
+ stringstream oid2;
+ int _oid2 = _oid + m_objects;
+ oid2 << _oid2;
+ if ((_oid2) % 2) {
+ oid2 << " " << string(300, 'o');
+ }
+ cout << m_op << ": " << "set_redirect oid " << oid.str() << " target oid "
+ << oid2.str() << std::endl;
+ op = new SetRedirectOp(m_op, &context, oid.str(), oid2.str(), context.pool_name);
+ return true;
+ } else if (m_set_chunk) {
+ stringstream oid;
+ int _oid = m_op % m_objects +1;
+ oid << _oid;
+ if ((_oid) % 2) {
+ oid << " " << string(300, 'o');
+ }
+ if (context.oid_in_use.count(oid.str())) {
+ /* previous set-chunk is not finished */
+ op = NULL;
+ m_op--;
+ cout << m_op << " retry set_chunk !" << std::endl;
+ return true;
+ }
+ stringstream oid2;
+ int _oid2 = _oid + m_objects;
+ oid2 << _oid2;
+ if ((_oid2) % 2) {
+ oid2 << " " << string(300, 'o');
+ }
+
+ /* make a chunk like source object (random offset, random length) -->
+ * target object (random offset)
+ */
+ ObjectDesc contents, contents2;
+ context.find_object(oid.str(), &contents);
+ context.find_object(oid2.str(), &contents2);
+ uint32_t max_len = contents.most_recent_gen()->get_length(contents.most_recent());
+ if (max_len > contents2.most_recent_gen()->get_length(contents2.most_recent())) {
+ max_len = contents2.most_recent_gen()->get_length(contents2.most_recent());
+ }
+ uint32_t rand_offset = rand() % max_len;
+ uint32_t rand_length = rand() % max_len;
+
+ while (rand_offset + rand_length > max_len || rand_length == 0) {
+ rand_offset = rand() % max_len;
+ rand_length = rand() % max_len;
+ }
+ uint32_t rand_tgt_offset = rand_offset;
+ cout << m_op << ": " << "set_chunk oid " << oid.str() << " offset: " << rand_offset
+ << " length: " << rand_length << " target oid " << oid2.str()
+ << " tgt_offset: " << rand_tgt_offset << std::endl;
+ op = new SetChunkOp(m_op, &context, oid.str(), rand_offset, rand_length, oid2.str(),
+ context.pool_name, rand_tgt_offset, m_stats);
+ return true;
}
- cout << m_op << ": " << "set_redirect oid " << oid.str() << " target oid "
- << oid2.str() << std::endl;
- op = new SetRedirectOp(m_op, &context, oid.str(), oid2.str(), context.pool_name);
- return true;
}
if (!context.oid_redirect_not_in_use.size() && m_op > set_redirect_end) {
int set_size = context.oid_not_in_use.size();
+ /* wait until redirect or set_chunk completion */
if (set_size < m_objects + m_redirect_objects) {
op = NULL;
+ m_op--;
+ cout << m_op << " wait completion " << std::endl;
return true;
}
for (int t_op = m_objects+1; t_op <= create_objects_end; t_op++) {
<< context.current_snap << std::endl;
return new WriteOp(m_op, &context, oid, true, true, m_stats);
+ case TEST_OP_CHUNK_READ:
+ oid = *(rand_choose(context.oid_not_in_use));
+ cout << m_op << ": " << "chunk read oid " << oid << " target oid " << oid2 << std::endl;
+ return new ChunkReadOp(m_op, &context, oid, context.pool_name, false, m_stats);
+
case TEST_OP_SET_REDIRECT:
oid = *(rand_choose(context.oid_not_in_use));
oid2 = *(rand_choose(context.oid_redirect_not_in_use));
bool m_ec_pool;
bool m_balance_reads;
bool m_set_redirect;
+ bool m_set_chunk;
int m_redirect_objects{0};
int m_initial_redirected_objects{0};
};
{ TEST_OP_APPEND_EXCL, "append_excl", true },
{ TEST_OP_SET_REDIRECT, "set_redirect", true },
{ TEST_OP_UNSET_REDIRECT, "unset_redirect", true },
+ { TEST_OP_CHUNK_READ, "chunk_read", true },
{ TEST_OP_READ /* grr */, NULL },
};
bool no_sparse = false;
bool balance_reads = false;
bool set_redirect = false;
+ bool set_chunk = false;
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--max-ops") == 0)
}
} else if (strcmp(argv[i], "--set_redirect") == 0) {
set_redirect = true;
+ } else if (strcmp(argv[i], "--set_chunk") == 0) {
+ set_chunk = true;
} else {
cerr << "unknown arg " << argv[i] << std::endl;
exit(1);
WeightedTestGenerator gen = WeightedTestGenerator(
ops, objects,
op_weights, &stats, max_seconds,
- ec_pool, balance_reads, set_redirect);
+ ec_pool, balance_reads, set_redirect, set_chunk);
int r = context.init();
if (r < 0) {
cerr << "Error initializing rados test context: "