]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
cls_rbd: Add methods for manipulating an image object map
authorJason Dillaman <dillaman@redhat.com>
Thu, 9 Oct 2014 06:51:44 +0000 (02:51 -0400)
committerJason Dillaman <dillaman@redhat.com>
Thu, 29 Jan 2015 02:12:50 +0000 (21:12 -0500)
The object map will track the current state of each object
within an RBD image.

Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/cls/rbd/cls_rbd.cc
src/cls/rbd/cls_rbd_client.cc
src/cls/rbd/cls_rbd_client.h
src/common/Makefile.am
src/common/bit_vector.hpp [new file with mode: 0644]
src/test/Makefile.am
src/test/cls_rbd/test_cls_rbd.cc
src/test/common/test_bit_vector.cc [new file with mode: 0644]
src/test/encoding/types.h

index 866823f024ba9cfd310a2e7e250de617cf645d86..50323e273f4c81ef8c127dfd468d5a5b9b230caa 100644 (file)
@@ -37,6 +37,7 @@
 #include <sstream>
 #include <vector>
 
+#include "common/bit_vector.hpp"
 #include "common/errno.h"
 #include "objclass/objclass.h"
 #include "include/rbd_types.h"
@@ -90,6 +91,9 @@ cls_method_handle_t h_dir_list;
 cls_method_handle_t h_dir_add_image;
 cls_method_handle_t h_dir_remove_image;
 cls_method_handle_t h_dir_rename_image;
+cls_method_handle_t h_object_map_load;
+cls_method_handle_t h_object_map_resize;
+cls_method_handle_t h_object_map_update;
 cls_method_handle_t h_old_snapshots_list;
 cls_method_handle_t h_old_snapshot_add;
 cls_method_handle_t h_old_snapshot_remove;
@@ -1795,6 +1799,186 @@ int dir_remove_image(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
   return dir_remove_image_helper(hctx, name, id);
 }
 
+int object_map_read(cls_method_context_t hctx, BitVector<2> &object_map)
+{
+  uint64_t size;
+  int r = cls_cxx_stat(hctx, &size, NULL);
+  if (r < 0) {
+    return r;
+  }
+
+  bufferlist bl;
+  r = cls_cxx_read(hctx, 0, size, &bl);
+  if (r < 0) {
+   return r;
+  }
+
+  try {
+    bufferlist::iterator iter = bl.begin();
+    ::decode(object_map, iter);
+  } catch (const buffer::error &err) {
+    return -EINVAL;
+  }
+  return 0;
+}
+
+/**
+ * Load an rbd image's object map
+ *
+ * Input:
+ * none
+ *
+ * Output:
+ * @param object map bit vector
+ * @returns 0 on success, negative error code on failure
+ */
+int object_map_load(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+  BitVector<2> object_map;
+  int r = object_map_read(hctx, object_map);
+  if (r < 0) {
+    return r;
+  }
+
+  ::encode(object_map, *out);
+  return 0;
+}
+
+/**
+ * Resize an rbd image's object map
+ *
+ * Input:
+ * @param object_count the max number of objects in the image
+ * @param default_state the default state of newly created objects
+ *
+ * Output:
+ * @returns 0 on success, negative error code on failure
+ */
+int object_map_resize(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+  uint64_t object_count;
+  uint8_t default_state;
+  try {
+    bufferlist::iterator iter = in->begin();
+    ::decode(object_count, iter);
+    ::decode(default_state, iter);
+  } catch (const buffer::error &err) {
+    return -EINVAL;
+  }
+
+  BitVector<2> object_map;
+  int r = object_map_read(hctx, object_map);
+  if ((r < 0) && (r != -ENOENT)) {
+    return r;
+  }
+
+  size_t orig_object_map_size = object_map.size();
+  if (orig_object_map_size != object_count) {
+    object_map.resize(object_count);
+    for (uint64_t i = orig_object_map_size; i < object_count; ++i) {
+      object_map[i] = default_state;
+    }
+  }
+
+  bufferlist map;
+  ::encode(object_map, map);
+  CLS_LOG(20, "object_map_resize: object size=%llu, byte size=%llu",
+         static_cast<unsigned long long>(object_count),
+         static_cast<unsigned long long>(map.length()));
+  return cls_cxx_write_full(hctx, &map);
+}
+
+/**
+ * Update an rbd image's object map
+ *
+ * Input:
+ * @param start_object_no the start object iterator
+ * @param end_object_no the end object iterator
+ * @param new_object_state the new object state
+ * @param current_object_state optional current object state filter
+ *
+ * Output:
+ * @returns 0 on success, negative error code on failure
+ */
+int object_map_update(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+  uint64_t start_object_no;
+  uint64_t end_object_no;
+  uint8_t new_object_state;
+  boost::optional<uint8_t> current_object_state;
+  try {
+    bufferlist::iterator iter = in->begin();
+    ::decode(start_object_no, iter);
+    ::decode(end_object_no, iter);
+    ::decode(new_object_state, iter);
+    ::decode(current_object_state, iter);
+  } catch (const buffer::error &err) {
+    return -EINVAL;
+  }
+
+  BitVector<2> object_map;
+  bufferlist header_bl;
+  int r = cls_cxx_read(hctx, 0, object_map.get_header_length(), &header_bl);
+  if (r < 0) {
+    return r;
+  }
+
+  try {
+    bufferlist::iterator it = header_bl.begin();
+    object_map.decode_header(it);
+  } catch (const buffer::error &err) {
+    return -EINVAL;
+  }
+
+  if (start_object_no >= end_object_no || end_object_no > object_map.size()) {
+    return -ERANGE;
+  }
+
+  uint64_t byte_offset;
+  uint64_t byte_length;
+  object_map.get_data_extents(start_object_no,
+                             end_object_no - start_object_no,
+                             &byte_offset, &byte_length);
+
+  bufferlist data_bl;
+  r = cls_cxx_read(hctx, object_map.get_header_length() + byte_offset,
+                  byte_length, &data_bl); 
+  if (r < 0) {
+    return r;
+  }
+
+  try {
+    bufferlist::iterator it = data_bl.begin();
+    object_map.decode_data(it, byte_offset);
+  } catch (const buffer::error &err) {
+    return -EINVAL;
+  } 
+
+  bool updated = false;
+  for (uint64_t object_no = start_object_no; object_no < end_object_no;
+       ++object_no) {
+    if ((!current_object_state || object_map[object_no] == *current_object_state) &&
+       object_map[object_no] != new_object_state) {
+      object_map[object_no] = new_object_state;
+      updated = true;
+    }
+  }
+
+  if (updated) {
+    CLS_LOG(20, "object_map_update: %llu~%llu -> %llu",
+           static_cast<unsigned long long>(byte_offset),
+           static_cast<unsigned long long>(byte_length),
+           static_cast<unsigned long long>(object_map.get_header_length() +
+                                           byte_offset));
+
+    bufferlist update;
+    object_map.encode_data(update, byte_offset, byte_length); 
+    r = cls_cxx_write(hctx, object_map.get_header_length() + byte_offset,
+                     update.length(), &update);
+  }
+  return r;
+}
+
 /****************************** Old format *******************************/
 
 int old_snapshots_list(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
@@ -2090,6 +2274,17 @@ void __cls_init()
                          CLS_METHOD_RD | CLS_METHOD_WR,
                          dir_rename_image, &h_dir_rename_image);
 
+  /* methods for the rbd_object_map.$image_id object */
+  cls_register_cxx_method(h_class, "object_map_load",
+                          CLS_METHOD_RD,
+                         object_map_load, &h_object_map_load);
+  cls_register_cxx_method(h_class, "object_map_resize",
+                          CLS_METHOD_RD | CLS_METHOD_WR,
+                         object_map_resize, &h_object_map_resize);
+  cls_register_cxx_method(h_class, "object_map_update",
+                          CLS_METHOD_RD | CLS_METHOD_WR,
+                         object_map_update, &h_object_map_update);
+
   /* methods for the old format */
   cls_register_cxx_method(h_class, "snap_list",
                          CLS_METHOD_RD,
index 66dc03b3fdfedccd2fe8a5af8dbdcb65ca421c71..aa91178fbf47161d16f0df2baaa791fa0ad9162d 100644 (file)
@@ -655,5 +655,47 @@ namespace librbd {
       ::encode(id, in);
       return ioctx->exec(oid, "rbd", "dir_rename_image", in, out);
     }
+
+    int object_map_load(librados::IoCtx *ioctx, const std::string &oid,
+                       ceph::BitVector<2> *object_map)
+    {
+      bufferlist in;
+      bufferlist out;
+      int r = ioctx->exec(oid, "rbd", "object_map_load", in, out);
+      if (r < 0) {
+       return r;
+      }
+
+      try {
+        bufferlist::iterator iter = out.begin();
+        ::decode(*object_map, iter);
+      } catch (const buffer::error &err) {
+        return -EBADMSG;
+      }
+      return 0;
+    }
+
+    void object_map_resize(librados::ObjectWriteOperation *rados_op,
+                           uint64_t object_count, uint8_t default_state)
+    {
+      bufferlist in;
+      ::encode(object_count, in);
+      ::encode(default_state, in);
+      rados_op->exec("rbd", "object_map_resize", in);
+    }
+
+    void object_map_update(librados::ObjectWriteOperation *rados_op,
+                          uint64_t start_object_no, uint64_t end_object_no,
+                           uint8_t new_object_state,
+                          const boost::optional<uint8_t> &current_object_state)
+    {
+      bufferlist in;
+      ::encode(start_object_no, in);
+      ::encode(end_object_no, in);
+      ::encode(new_object_state, in);
+      ::encode(current_object_state, in);
+      rados_op->exec("rbd", "object_map_update", in);
+    }
+
   } // namespace cls_client
 } // namespace librbd
index 19f0b2dabce269b0651f6af819fc5e02082a443a..131b03099469b661d32c8a1efe849d3d7d04df95 100644 (file)
@@ -5,6 +5,7 @@
 #define CEPH_LIBRBD_CLS_RBD_CLIENT_H
 
 #include "cls/lock/cls_lock_types.h"
+#include "common/bit_vector.hpp"
 #include "common/snap_types.h"
 #include "include/rados/librados.hpp"
 #include "include/types.h"
@@ -101,6 +102,16 @@ namespace librbd {
                         const std::string &src, const std::string &dest,
                         const std::string &id);
 
+    // operations on the rbd_object_map.$image_id object
+    int object_map_load(librados::IoCtx *ioctx, const std::string &oid,
+                       ceph::BitVector<2> *object_map);
+    void object_map_resize(librados::ObjectWriteOperation *rados_op,
+                          uint64_t object_count, uint8_t default_state);
+    void object_map_update(librados::ObjectWriteOperation *rados_op,
+                          uint64_t start_object_no, uint64_t end_object_no,
+                          uint8_t new_object_state,
+                          const boost::optional<uint8_t> &current_object_state);
+
     // class operations on the old format, kept for
     // backwards compatability
     int old_snapshot_add(librados::IoCtx *ioctx, const std::string &oid,
index 07a7f1c77ee9c182ce77d13a4893cb1360c0ff97..621a10a4f625a80bcceb94ce9212f72ebe2efc84 100644 (file)
@@ -227,7 +227,8 @@ noinst_HEADERS += \
        common/Readahead.h \
        common/Cycles.h \
        common/Initialize.h \
-       common/ContextCompletion.h
+       common/ContextCompletion.h \
+       common/bit_vector.hpp
 
 if ENABLE_XIO
 noinst_HEADERS += \
diff --git a/src/common/bit_vector.hpp b/src/common/bit_vector.hpp
new file mode 100644 (file)
index 0000000..5c8e20f
--- /dev/null
@@ -0,0 +1,311 @@
+// -*- 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) 2014 Red Hat <contact@redhat.com>
+ *
+ * LGPL2.1 (see COPYING-LGPL2.1) or later
+ */
+
+#ifndef BIT_VECTOR_HPP
+#define BIT_VECTOR_HPP
+
+#include "common/Formatter.h"
+#include "include/assert.h"
+#include "include/buffer.h"
+#include "include/encoding.h"
+#include <stdint.h>
+#include <cmath>
+#include <list>
+#include <boost/static_assert.hpp>
+
+namespace ceph {
+
+template <uint8_t _bit_count>
+class BitVector
+{
+private:
+  static const uint8_t BITS_PER_BYTE = 8;
+  static const uint32_t ELEMENTS_PER_BLOCK = BITS_PER_BYTE / _bit_count;
+  static const uint8_t MASK = static_cast<uint8_t>((1 << _bit_count) - 1);
+
+  // must be power of 2
+  BOOST_STATIC_ASSERT((_bit_count != 0) && !(_bit_count & (_bit_count - 1)));
+  BOOST_STATIC_ASSERT(_bit_count <= BITS_PER_BYTE);
+public:
+
+  class ConstReference {
+  public:
+    operator uint8_t() const;
+  private:
+    friend class BitVector;
+    const BitVector &m_bit_vector;
+    uint64_t m_offset;
+
+    ConstReference(const BitVector &bit_vector, uint64_t offset);
+  };
+
+  class Reference {
+  public:
+    operator uint8_t() const;
+    Reference& operator=(uint8_t v);
+  private:
+    friend class BitVector;
+    BitVector &m_bit_vector;
+    uint64_t m_offset;
+
+    Reference(BitVector &bit_vector, uint64_t offset);
+  };
+
+  static const uint8_t BIT_COUNT = _bit_count;
+
+  BitVector();
+
+  void clear();
+
+  void resize(uint64_t elements);
+  uint64_t size() const;
+
+  const bufferlist& get_data() const;
+
+  Reference operator[](uint64_t offset);
+  ConstReference operator[](uint64_t offset) const;
+
+  void encode_header(bufferlist& bl) const;
+  void decode_header(bufferlist::iterator& it);
+  uint64_t get_header_length() const;
+
+  void encode_data(bufferlist& bl, uint64_t byte_offset,
+                  uint64_t byte_length) const;
+  void decode_data(bufferlist::iterator& it, uint64_t byte_offset);
+  void get_data_extents(uint64_t offset, uint64_t length,
+                       uint64_t *byte_offset, uint64_t *byte_length) const;
+
+  void encode(bufferlist& bl) const;
+  void decode(bufferlist::iterator& it);
+  void dump(Formatter *f) const;
+
+  bool operator==(const BitVector &b) const;
+
+  static void generate_test_instances(std::list<BitVector *> &o);
+private:
+
+  bufferlist m_data;
+  uint64_t m_size;
+
+  static void compute_index(uint64_t offset, uint64_t *index, uint64_t *shift);
+
+};
+
+template <uint8_t _b>
+BitVector<_b>::BitVector() : m_size(0)
+{
+}
+
+template <uint8_t _b>
+void BitVector<_b>::clear() {
+  m_data.clear();
+  m_size = 0;
+}
+
+template <uint8_t _b>
+void BitVector<_b>::resize(uint64_t size) {
+  uint64_t buffer_size = static_cast<uint64_t>(std::ceil(static_cast<double>(size) /
+                                          ELEMENTS_PER_BLOCK));
+  if (buffer_size > m_data.length()) {
+    m_data.append_zero(buffer_size - m_data.length());
+  } else if (buffer_size < m_data.length()) {
+    bufferlist bl;
+    bl.substr_of(m_data, 0, buffer_size);
+    bl.swap(m_data);
+  }
+  m_size = size;
+}
+
+template <uint8_t _b>
+uint64_t BitVector<_b>::size() const {
+  return m_size;
+}
+
+template <uint8_t _b>
+const bufferlist& BitVector<_b>::get_data() const {
+  return m_data;
+}
+
+template <uint8_t _b>
+void BitVector<_b>::compute_index(uint64_t offset, uint64_t *index, uint64_t *shift) {
+  *index = offset / ELEMENTS_PER_BLOCK;
+  *shift = ((ELEMENTS_PER_BLOCK - 1) - (offset % ELEMENTS_PER_BLOCK)) * _b;
+}
+
+template <uint8_t _b>
+void BitVector<_b>::encode_header(bufferlist& bl) const {
+  ENCODE_START(1, 1, bl);
+  ::encode(m_size, bl);
+  ENCODE_FINISH(bl);
+}
+
+template <uint8_t _b>
+void BitVector<_b>::decode_header(bufferlist::iterator& it) {
+  uint64_t size;
+  DECODE_START(1, it);
+  ::decode(size, it);
+  DECODE_FINISH(it);
+
+  resize(size);
+}
+
+template <uint8_t _b>
+uint64_t BitVector<_b>::get_header_length() const {
+  // 6 byte encoding header, 8 byte size
+  return 14;
+}
+
+template <uint8_t _b>
+void BitVector<_b>::encode_data(bufferlist& bl, uint64_t byte_offset,
+                               uint64_t byte_length) const {
+  bufferlist bit;
+  bit.substr_of(m_data, byte_offset, byte_length);
+  bl.append(bit);
+}
+
+template <uint8_t _b>
+void BitVector<_b>::decode_data(bufferlist::iterator& it, uint64_t byte_offset) {
+  if (byte_offset + it.get_remaining() > m_data.length()) {
+    throw buffer::malformed_input("attempting to decode past end of buffer");
+  }
+
+  char* packed_data = m_data.c_str();
+  for (; !it.end(); ++it) {
+    packed_data[byte_offset++] = *it;
+  }
+}
+
+template <uint8_t _b>
+void BitVector<_b>::get_data_extents(uint64_t offset, uint64_t length,
+                                    uint64_t *byte_offset,
+                                    uint64_t *byte_length) const {
+  assert(length > 0);
+  uint64_t shift;
+  compute_index(offset, byte_offset, &shift);
+
+  uint64_t end_offset;
+  compute_index(offset + length - 1, &end_offset, &shift);
+  assert(*byte_offset <= end_offset);
+
+  *byte_length = end_offset - *byte_offset + 1;
+}
+
+template <uint8_t _b>
+void BitVector<_b>::encode(bufferlist& bl) const {
+  encode_header(bl);
+  if (size() > 0) {
+    encode_data(bl, 0, m_data.length()); 
+  }
+}
+
+template <uint8_t _b>
+void BitVector<_b>::decode(bufferlist::iterator& it) {
+  decode_header(it);
+  decode_data(it, 0);
+}
+
+template <uint8_t _b>
+void BitVector<_b>::dump(Formatter *f) const {
+  f->dump_unsigned("size", m_size);
+  f->open_array_section("bit_table");
+  for (unsigned i = 0; i < m_data.length(); ++i) {
+    f->dump_format("byte", "0x%02hhX", m_data[i]);
+  }
+  f->close_section();
+}
+
+template <uint8_t _b>
+bool BitVector<_b>::operator==(const BitVector &b) const {
+  return (this->m_size == b.m_size && this->m_data == b.m_data);
+}
+
+template <uint8_t _b>
+typename BitVector<_b>::Reference BitVector<_b>::operator[](uint64_t offset) {
+  return Reference(*this, offset);
+}
+
+template <uint8_t _b>
+typename BitVector<_b>::ConstReference BitVector<_b>::operator[](uint64_t offset) const {
+  return ConstReference(*this, offset);
+}
+
+template <uint8_t _b>
+BitVector<_b>::ConstReference::ConstReference(const BitVector<_b> &bit_vector,
+                                             uint64_t offset)
+  : m_bit_vector(bit_vector), m_offset(offset)
+{
+}
+
+template <uint8_t _b>
+BitVector<_b>::ConstReference::operator uint8_t() const {
+  uint64_t index;
+  uint64_t shift;
+  this->m_bit_vector.compute_index(this->m_offset, &index, &shift);
+
+  return (this->m_bit_vector.m_data[index] >> shift) & MASK;
+}
+
+template <uint8_t _b>
+BitVector<_b>::Reference::Reference(BitVector<_b> &bit_vector, uint64_t offset)
+  : m_bit_vector(bit_vector), m_offset(offset)
+{
+}
+
+template <uint8_t _b>
+BitVector<_b>::Reference::operator uint8_t() const {
+  uint64_t index;
+  uint64_t shift;
+  this->m_bit_vector.compute_index(this->m_offset, &index, &shift);
+
+  return (this->m_bit_vector.m_data[index] >> shift) & MASK;
+}
+
+template <uint8_t _b>
+typename BitVector<_b>::Reference& BitVector<_b>::Reference::operator=(uint8_t v) {
+  uint64_t index;
+  uint64_t shift;
+  this->m_bit_vector.compute_index(this->m_offset, &index, &shift);
+
+  // TODO: find out why bufferlist doesn't support char& operator[]()
+  uint8_t mask = MASK << shift;
+  char* packed_data = this->m_bit_vector.m_data.c_str();
+  uint8_t packed_value = (packed_data[index] & ~mask) | ((v << shift) & mask);
+  packed_data[index] = packed_value;
+  return *this;
+}
+
+template <uint8_t _b>
+void BitVector<_b>::generate_test_instances(std::list<BitVector *> &o) {
+  o.push_back(new BitVector());
+
+  BitVector *b = new BitVector();
+  const uint64_t radix = 1 << b->BIT_COUNT;
+  const uint64_t size = 1024;
+
+  b->resize(size);
+  for (uint64_t i = 0; i < size; ++i) {
+    (*b)[i] = rand() % radix;
+  }
+  o.push_back(b);
+}
+
+}
+
+WRITE_CLASS_ENCODER(ceph::BitVector<2>)
+
+template <uint8_t _b>
+inline std::ostream& operator<<(std::ostream& out, const ceph::BitVector<_b> &b)
+{
+  out << "ceph::BitVector<" << _b << ">(size=" << b.size() << ", data="
+      << b.get_data() << ")";
+  return out;
+}
+
+#endif // BIT_VECTOR_HPP
index 78aca98e1cb32bade574ce4a1088bb81fdfbb5cc..3da028fd29e662a54d3613069b11767e00b8d19c 100644 (file)
@@ -669,6 +669,11 @@ unittest_tableformatter_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 unittest_tableformatter_LDADD = $(UNITTEST_LDADD) $(CEPH_GLOBAL)
 check_PROGRAMS += unittest_tableformatter
 
+unittest_bit_vector_SOURCES = test/common/test_bit_vector.cc
+unittest_bit_vector_CXXFLAGS = $(UNITTEST_CXXFLAGS)
+unittest_bit_vector_LDADD = $(UNITTEST_LDADD) $(CEPH_GLOBAL)
+check_PROGRAMS += unittest_bit_vector
+
 check_SCRIPTS += test/pybind/test_ceph_argparse.py
 
 if WITH_RADOSGW
index bef5fe141b5460720e0f559da8435d6c355e573b..663c9332bc2fbfd6a5a311319507cfbb4f284318 100644 (file)
@@ -49,6 +49,9 @@ using ::librbd::cls_client::get_stripe_unit_count;
 using ::librbd::cls_client::set_stripe_unit_count;
 using ::librbd::cls_client::old_snapshot_add;
 using ::librbd::cls_client::get_mutable_metadata;
+using ::librbd::cls_client::object_map_load;
+using ::librbd::cls_client::object_map_resize;
+using ::librbd::cls_client::object_map_update;
 
 static char *random_buf(size_t len)
 {
@@ -923,3 +926,98 @@ TEST_F(TestClsRbd, get_mutable_metadata_features)
 
   ioctx.close();
 }
+
+TEST_F(TestClsRbd, object_map_resize)
+{
+  librados::IoCtx ioctx;
+  ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx));
+
+  string oid = get_temp_image_name();
+  BitVector<2> ref_bit_vector;
+  ref_bit_vector.resize(32);
+  for (uint64_t i = 0; i < ref_bit_vector.size(); ++i) {
+    ref_bit_vector[i] = 1;
+  }
+
+  librados::ObjectWriteOperation op1;
+  object_map_resize(&op1, ref_bit_vector.size(), 1);
+  ASSERT_EQ(0, ioctx.operate(oid, &op1));
+
+  BitVector<2> osd_bit_vector;
+  ASSERT_EQ(0, object_map_load(&ioctx, oid, &osd_bit_vector));
+  ASSERT_EQ(ref_bit_vector, osd_bit_vector);
+
+  ref_bit_vector.resize(64);
+  for (uint64_t i = 32; i < ref_bit_vector.size(); ++i) {
+    ref_bit_vector[i] = 2;
+  }
+
+  librados::ObjectWriteOperation op2;
+  object_map_resize(&op2, ref_bit_vector.size(), 2);
+  ASSERT_EQ(0, ioctx.operate(oid, &op2));
+  ASSERT_EQ(0, object_map_load(&ioctx, oid, &osd_bit_vector));
+  ASSERT_EQ(ref_bit_vector, osd_bit_vector);
+
+  ref_bit_vector.resize(16);
+
+  librados::ObjectWriteOperation op3;
+  object_map_resize(&op3, ref_bit_vector.size(), 1);
+  ASSERT_EQ(0, ioctx.operate(oid, &op3));
+  ASSERT_EQ(0, object_map_load(&ioctx, oid, &osd_bit_vector));
+  ASSERT_EQ(ref_bit_vector, osd_bit_vector);
+
+  ioctx.close();
+}
+
+TEST_F(TestClsRbd, object_map_update)
+{
+  librados::IoCtx ioctx;
+  ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx));
+
+  string oid = get_temp_image_name();
+  BitVector<2> ref_bit_vector;
+  ref_bit_vector.resize(16);
+  for (uint64_t i = 0; i < ref_bit_vector.size(); ++i) {
+    ref_bit_vector[i] = 2;
+  }
+
+  BitVector<2> osd_bit_vector;
+
+  librados::ObjectWriteOperation op1;
+  object_map_resize(&op1, ref_bit_vector.size(), 2);
+  ASSERT_EQ(0, ioctx.operate(oid, &op1));
+  ASSERT_EQ(0, object_map_load(&ioctx, oid, &osd_bit_vector));
+  ASSERT_EQ(ref_bit_vector, osd_bit_vector);
+
+  ref_bit_vector[7] = 1;
+  ref_bit_vector[8] = 1;
+
+  librados::ObjectWriteOperation op2;
+  object_map_update(&op2, 7, 9, 1, boost::optional<uint8_t>());
+  ASSERT_EQ(0, ioctx.operate(oid, &op2));
+  ASSERT_EQ(0, object_map_load(&ioctx, oid, &osd_bit_vector));
+  ASSERT_EQ(ref_bit_vector, osd_bit_vector);
+
+  ref_bit_vector[7] = 3;
+  ref_bit_vector[8] = 3;
+
+  librados::ObjectWriteOperation op3;
+  object_map_update(&op3, 6, 10, 3, 1);
+  ASSERT_EQ(0, ioctx.operate(oid, &op3));
+  ASSERT_EQ(0, object_map_load(&ioctx, oid, &osd_bit_vector));
+  ASSERT_EQ(ref_bit_vector, osd_bit_vector);
+
+  ioctx.close();
+}
+
+TEST_F(TestClsRbd, object_map_load_enoent)
+{
+  librados::IoCtx ioctx;
+  ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx));
+
+  string oid = get_temp_image_name();
+  BitVector<2> osd_bit_vector;
+  ASSERT_EQ(-ENOENT, object_map_load(&ioctx, oid, &osd_bit_vector));
+
+  ioctx.close();
+}
diff --git a/src/test/common/test_bit_vector.cc b/src/test/common/test_bit_vector.cc
new file mode 100644 (file)
index 0000000..ad6c243
--- /dev/null
@@ -0,0 +1,110 @@
+// -*- 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) 2014 Red Hat <contact@redhat.com>
+ *
+ * LGPL2.1 (see COPYING-LGPL2.1) or later
+ */
+
+#include <gtest/gtest.h>
+#include <cmath>
+#include "common/bit_vector.hpp"
+
+using namespace ceph;
+
+template <uint8_t _bit_count>
+class TestParams {
+public:
+  static const uint8_t BIT_COUNT = _bit_count;
+};
+
+template <typename T>
+class BitVectorTest : public ::testing::Test {
+public:
+  typedef BitVector<T::BIT_COUNT> bit_vector_t;
+};
+
+typedef ::testing::Types<TestParams<2> > BitVectorTypes;
+TYPED_TEST_CASE(BitVectorTest, BitVectorTypes);
+
+TYPED_TEST(BitVectorTest, resize) {
+  typename TestFixture::bit_vector_t bit_vector;
+
+  size_t size = 2357;
+
+  double elements_per_byte = 8 / bit_vector.BIT_COUNT;
+
+  bit_vector.resize(size);
+  ASSERT_EQ(bit_vector.size(), size);
+  ASSERT_EQ(bit_vector.get_data().length(), static_cast<uint64_t>(std::ceil(
+    size / elements_per_byte)));
+}
+
+TYPED_TEST(BitVectorTest, clear) {
+  typename TestFixture::bit_vector_t bit_vector;
+
+  bit_vector.resize(123);
+  bit_vector.clear();
+  ASSERT_EQ(0ull, bit_vector.size());
+  ASSERT_EQ(0ull, bit_vector.get_data().length());
+}
+
+TYPED_TEST(BitVectorTest, bit_order) {
+  typename TestFixture::bit_vector_t bit_vector;
+  bit_vector.resize(1);
+
+  uint8_t value = 1;
+  bit_vector[0] = value;
+
+  value <<= (8 - bit_vector.BIT_COUNT);
+  ASSERT_EQ(value, bit_vector.get_data()[0]);
+}
+
+TYPED_TEST(BitVectorTest, get_set) {
+  typename TestFixture::bit_vector_t bit_vector;
+  std::vector<uint64_t> ref;
+
+  uint64_t radix = 1 << bit_vector.BIT_COUNT;
+
+  size_t size = 1024;
+  bit_vector.resize(size);
+  ref.resize(size);
+  for (size_t i = 0; i < size; ++i) {
+    uint64_t v = rand() % radix;
+    ref[i] = v;
+    bit_vector[i] = v;
+  }
+
+  const typename TestFixture::bit_vector_t &const_bit_vector(bit_vector);
+  for (size_t i = 0; i < size; ++i) {
+    ASSERT_EQ(ref[i], bit_vector[i]);
+    ASSERT_EQ(ref[i], const_bit_vector[i]);
+  }
+}
+
+TYPED_TEST(BitVectorTest, get_buffer_extents) {
+  typename TestFixture::bit_vector_t bit_vector;
+
+  uint64_t offset = 5381;
+  uint64_t length = 4111;
+  uint64_t byte_offset;
+  uint64_t byte_length;
+  bit_vector.get_data_extents(offset, length, &byte_offset, &byte_length);
+
+  uint64_t elements_per_byte = 8 / bit_vector.BIT_COUNT;
+  uint64_t start_byte = offset / elements_per_byte;
+  ASSERT_EQ(start_byte, byte_offset);
+
+  uint64_t end_byte = (offset + length - 1) / elements_per_byte;
+  ASSERT_EQ(end_byte - start_byte + 1, byte_length);
+}
+
+TYPED_TEST(BitVectorTest, get_header_length) {
+  typename TestFixture::bit_vector_t bit_vector;
+
+  bufferlist bl;
+  bit_vector.encode_header(bl);
+  ASSERT_EQ(bl.length(), bit_vector.get_header_length());
+}
index 684364e1841a33c32972242f2ed2fe7e05a83b1e..bc4fe33e4424bcc64108c90d4b2ad1c88c58ca5e 100644 (file)
@@ -4,6 +4,9 @@ TYPE(CompatSet)
 #include "include/filepath.h"
 TYPE(filepath)
 
+#include "common/bit_vector.hpp"
+TYPE(BitVector<2>)
+
 #include "common/bloom_filter.hpp"
 TYPE(bloom_filter)
 TYPE(compressible_bloom_filter)