]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
src/test: add ChunkRead, SetChunk test
authormyoungwon oh <omwmw@sk.com>
Thu, 7 Sep 2017 13:51:25 +0000 (22:51 +0900)
committermyoungwon oh <omwmw@sk.com>
Mon, 6 Nov 2017 06:53:28 +0000 (15:53 +0900)
Signed-off-by: Myoungwon Oh <omwmw@sk.com>
src/test/osd/Object.h
src/test/osd/RadosModel.h
src/test/osd/TestRados.cc

index 09a59a9ceb5cc8ac32b016909c5e65f09ffcfe13..9edfcb9856d15a53b26ae5a0dee299437c544cda 100644 (file)
@@ -51,6 +51,13 @@ WRITE_CLASS_ENCODER(ContDesc)
 
 std::ostream &operator<<(std::ostream &out, const ContDesc &rhs);
 
+class ChunkDesc {
+public:
+  uint32_t offset;
+  uint32_t length;
+  std::string oid;
+};
+
 class ContentsGenerator {
 public:
 
@@ -507,6 +514,7 @@ public:
 
   uint64_t version;
   std::string redirect_target;
+  std::map<uint64_t, ChunkDesc> chunk_info;
 private:
   std::list<std::pair<ceph::shared_ptr<ContentsGenerator>, ContDesc> > layers;
 };
index b4851c6d36933d620a11a3e005556fe4055c417e..932209d77a319be62726e6a371e76bcd04c2c943 100644 (file)
@@ -66,7 +66,8 @@ enum TestOpType {
   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 {
@@ -487,6 +488,22 @@ public:
     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;
@@ -1956,6 +1973,371 @@ public:
   }
 };
 
+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;
index cb6ff12d50d0d8fbed5d59a3baa15b3f08e31669..c87cdcb0dad5f2d8ed58ca2f9c8c1924a96f03f8 100644 (file)
@@ -30,13 +30,15 @@ public:
                        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();
@@ -46,11 +48,17 @@ public:
       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;
+      }
     }
   }
 
@@ -77,11 +85,11 @@ public:
       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;
@@ -108,15 +116,20 @@ public:
     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;
@@ -131,33 +144,83 @@ public:
       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++) {
@@ -319,6 +382,11 @@ private:
           << 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));
@@ -349,6 +417,7 @@ private:
   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}; 
 };
@@ -391,6 +460,7 @@ int main(int argc, char **argv)
     { 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 },
   };
 
@@ -401,6 +471,7 @@ int main(int argc, char **argv)
   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)
@@ -473,6 +544,8 @@ int main(int argc, char **argv)
       }
     } 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);
@@ -533,7 +606,7 @@ int main(int argc, char **argv)
   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: "