]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
cls_rbd: Add snapshot namespaces data structures
authorVictor Denisov <denisovenator@gmail.com>
Wed, 21 Sep 2016 04:30:56 +0000 (21:30 -0700)
committerVictor Denisov <denisovenator@gmail.com>
Sat, 22 Oct 2016 03:53:24 +0000 (20:53 -0700)
Signed-off-by: Victor Denisov <denisovenator@gmail.com>
src/cls/rbd/cls_rbd.cc
src/cls/rbd/cls_rbd.h
src/cls/rbd/cls_rbd_client.cc
src/cls/rbd/cls_rbd_client.h
src/cls/rbd/cls_rbd_types.cc
src/cls/rbd/cls_rbd_types.h
src/test/cls_rbd/test_cls_rbd.cc

index 316165fd37173b32a307add1f603291d632375c5..9cff72f885cb3560037426fb3636636b832b0c9c 100644 (file)
@@ -86,6 +86,7 @@ cls_method_handle_t h_get_snapcontext;
 cls_method_handle_t h_get_object_prefix;
 cls_method_handle_t h_get_data_pool;
 cls_method_handle_t h_get_snapshot_name;
+cls_method_handle_t h_get_snapshot_namespace;
 cls_method_handle_t h_snapshot_add;
 cls_method_handle_t h_snapshot_remove;
 cls_method_handle_t h_snapshot_rename;
@@ -1537,12 +1538,53 @@ int get_snapshot_name(cls_method_context_t hctx, bufferlist *in, bufferlist *out
   return 0;
 }
 
+/**
+ * Retrieve namespace of a snapshot.
+ *
+ * Input:
+ * @param snap_id id of the snapshot (uint64_t)
+ *
+ * Output:
+ * @param SnapshotNamespace
+ * @returns 0 on success, negative error code on failure.
+ */
+int get_snapshot_namespace(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+  uint64_t snap_id;
+
+  bufferlist::iterator iter = in->begin();
+  try {
+    ::decode(snap_id, iter);
+  } catch (const buffer::error &err) {
+    return -EINVAL;
+  }
+
+  CLS_LOG(20, "get_snapshot_namespace snap_id=%" PRIu64, snap_id);
+
+  if (snap_id == CEPH_NOSNAP) {
+    return -EINVAL;
+  }
+
+  cls_rbd_snap snap;
+  string snapshot_key;
+  key_from_snap_id(snap_id, &snapshot_key);
+  int r = read_key(hctx, snapshot_key, &snap);
+  if (r < 0) {
+    return r;
+  }
+
+  ::encode(snap.snapshot_namespace, *out);
+
+  return 0;
+}
+
 /**
  * Adds a snapshot to an rbd header. Ensures the id and name are unique.
  *
  * Input:
  * @param snap_name name of the snapshot (string)
  * @param snap_id id of the snapshot (uint64_t)
+ * @param snap_namespace namespace of the snapshot (cls::rbd::SnapshotNamespaceOnDisk)
  *
  * Output:
  * @returns 0 on success, negative error code on failure.
@@ -1559,6 +1601,9 @@ int snapshot_add(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
     bufferlist::iterator iter = in->begin();
     ::decode(snap_meta.name, iter);
     ::decode(snap_meta.id, iter);
+    if (!iter.end()) {
+      ::decode(snap_meta.snapshot_namespace, iter);
+    }
   } catch (const buffer::error &err) {
     return -EINVAL;
   }
@@ -4781,6 +4826,9 @@ void __cls_init()
   cls_register_cxx_method(h_class, "get_snapshot_name",
                          CLS_METHOD_RD,
                          get_snapshot_name, &h_get_snapshot_name);
+  cls_register_cxx_method(h_class, "get_snapshot_namespace",
+                         CLS_METHOD_RD,
+                         get_snapshot_namespace, &h_get_snapshot_namespace);
   cls_register_cxx_method(h_class, "snapshot_add",
                          CLS_METHOD_RD | CLS_METHOD_WR,
                          snapshot_add, &h_snapshot_add);
index 710b54213ff85302817a23cb99cf26e31cbcb543..c8bd6dab6d7206fe9d0bad9d02f7d0b54dcefa25 100644 (file)
@@ -7,6 +7,7 @@
 #include "include/buffer_fwd.h"
 #include "common/Formatter.h"
 #include "librbd/parent_types.h"
+#include "cls/rbd/cls_rbd_types.h"
 
 /// information about our parent image, if any
 struct cls_rbd_parent {
@@ -64,6 +65,7 @@ struct cls_rbd_snap {
   uint8_t protection_status;
   cls_rbd_parent parent;
   uint64_t flags;
+  cls::rbd::SnapshotNamespaceOnDisk snapshot_namespace;
 
   /// true if we have a parent
   bool has_parent() const {
@@ -75,7 +77,7 @@ struct cls_rbd_snap {
                    flags(0)
     {}
   void encode(bufferlist& bl) const {
-    ENCODE_START(4, 1, bl);
+    ENCODE_START(5, 1, bl);
     ::encode(id, bl);
     ::encode(name, bl);
     ::encode(image_size, bl);
@@ -83,10 +85,11 @@ struct cls_rbd_snap {
     ::encode(parent, bl);
     ::encode(protection_status, bl);
     ::encode(flags, bl);
+    ::encode(snapshot_namespace, bl);
     ENCODE_FINISH(bl);
   }
   void decode(bufferlist::iterator& p) {
-    DECODE_START(4, p);
+    DECODE_START(5, p);
     ::decode(id, p);
     ::decode(name, p);
     ::decode(image_size, p);
@@ -100,6 +103,11 @@ struct cls_rbd_snap {
     if (struct_v >= 4) {
       ::decode(flags, p);
     }
+    if (struct_v >= 5) {
+      ::decode(snapshot_namespace, p);
+    } else {
+      snapshot_namespace = cls::rbd::SnapshotNamespaceOnDisk(cls::rbd::UserSnapshotNamespace());
+    }
     DECODE_FINISH(p);
   }
   void dump(Formatter *f) const {
index f9835701ffa917580f3a98440fa6b7e8390b50fb..44d3572f693f05af46b12e2570c4629b41fa9432 100644 (file)
@@ -481,11 +481,12 @@ namespace librbd {
     }
 
     void snapshot_add(librados::ObjectWriteOperation *op, snapid_t snap_id,
-                     const std::string &snap_name)
+                     const std::string &snap_name, const cls::rbd::SnapshotNamespace &snap_namespace)
     {
       bufferlist bl;
       ::encode(snap_name, bl);
       ::encode(snap_id, bl);
+      ::encode(cls::rbd::SnapshotNamespaceOnDisk(snap_namespace), bl);
       op->exec("rbd", "snapshot_add", bl);
     }
 
@@ -550,7 +551,8 @@ namespace librbd {
                              std::vector<string> *names,
                              std::vector<uint64_t> *sizes,
                              std::vector<parent_info> *parents,
-                             std::vector<uint8_t> *protection_statuses) {
+                             std::vector<uint8_t> *protection_statuses)
+    {
       names->resize(ids.size());
       sizes->resize(ids.size());
       parents->resize(ids.size());
@@ -598,6 +600,52 @@ namespace librbd {
                                   protection_statuses);
     }
 
+    void snap_namespace_list_start(librados::ObjectReadOperation *op,
+                             const std::vector<snapid_t> &ids)
+    {
+      for (vector<snapid_t>::const_iterator it = ids.begin();
+           it != ids.end(); ++it) {
+        snapid_t snap_id = it->val;
+        bufferlist bl;
+        ::encode(snap_id, bl);
+       op->exec("rbd", "get_snapshot_namespace", bl);
+      }
+    }
+
+    int snap_namespace_list_finish(bufferlist::iterator *it,
+                                  const std::vector<snapid_t> &ids,
+                                  std::vector<cls::rbd::SnapshotNamespace> *namespaces)
+    {
+      namespaces->resize(ids.size());
+      try {
+       for (size_t i = 0; i < namespaces->size(); ++i) {
+         cls::rbd::SnapshotNamespaceOnDisk e;
+         ::decode(e, *it);
+         (*namespaces)[i] = e.snapshot_namespace;
+       }
+      } catch (const buffer::error &err) {
+        return -EBADMSG;
+      }
+      return 0;
+    }
+
+    int snap_namespace_list(librados::IoCtx *ioctx, const std::string &oid,
+                           const std::vector<snapid_t> &ids,
+                           std::vector<cls::rbd::SnapshotNamespace> *namespaces)
+    {
+      librados::ObjectReadOperation op;
+      snap_namespace_list_start(&op, ids);
+
+      bufferlist out_bl;
+      int r = ioctx->operate(oid, &op, &out_bl);
+      if (r < 0) {
+        return r;
+      }
+
+      bufferlist::iterator it = out_bl.begin();
+      return snap_namespace_list_finish(&it, ids, namespaces);
+    }
+
     void old_snapshot_add(librados::ObjectWriteOperation *op,
                          snapid_t snap_id, const std::string &snap_name)
     {
index 5c5ba3f4c599b2685a095d8dffb53d87ec8326f1..d16033d1935612a7e65dbcb33c603029ee3acc8e 100644 (file)
@@ -104,7 +104,8 @@ namespace librbd {
     int get_children(librados::IoCtx *ioctx, const std::string &oid,
                       parent_spec pspec, set<string>& children);
     void snapshot_add(librados::ObjectWriteOperation *op, snapid_t snap_id,
-                     const std::string &snap_name);
+                     const std::string &snap_name,
+                     const cls::rbd::SnapshotNamespace &snap_namespace);
     void snapshot_remove(librados::ObjectWriteOperation *op, snapid_t snap_id);
     void snapshot_rename(librados::ObjectWriteOperation *op,
                        snapid_t src_snap_id,
@@ -127,6 +128,15 @@ namespace librbd {
                      std::vector<parent_info> *parents,
                      std::vector<uint8_t> *protection_statuses);
 
+    void snap_namespace_list_start(librados::ObjectReadOperation *op,
+                                  const std::vector<snapid_t> &ids);
+    int snap_namespace_list_finish(bufferlist::iterator *it,
+                                  const std::vector<snapid_t> &ids,
+                                  std::vector<cls::rbd::SnapshotNamespace> *namespaces);
+    int snap_namespace_list(librados::IoCtx *ioctx, const std::string &oid,
+                           const std::vector<snapid_t> &ids,
+                           std::vector<cls::rbd::SnapshotNamespace> *namespaces);
+
     int copyup(librados::IoCtx *ioctx, const std::string &oid,
               bufferlist data);
     int get_protection_status(librados::IoCtx *ioctx, const std::string &oid,
index efbe47fd92ba60dca39ba5f6f51d3437d785eac2..ace0abc15ac4fab0b5decb01ec5ded7e7db2c8cd 100644 (file)
@@ -1,6 +1,7 @@
 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
 // vim: ts=8 sw=2 smarttab
 
+#include <boost/variant.hpp>
 #include "cls/rbd/cls_rbd_types.h"
 #include "common/Formatter.h"
 
@@ -316,5 +317,136 @@ bool GroupSpec::is_valid() const {
   return (!group_id.empty()) && (pool_id != -1);
 }
 
+void GroupSnapshotNamespace::encode(bufferlist& bl) const {
+  ::encode(group_pool, bl);
+  ::encode(group_id, bl);
+  ::encode(snapshot_id, bl);
+}
+
+void GroupSnapshotNamespace::decode(bufferlist::iterator& it) {
+  ::decode(group_pool, it);
+  ::decode(group_id, it);
+  ::decode(snapshot_id, it);
+}
+
+void GroupSnapshotNamespace::dump(Formatter *f) const {
+  f->dump_int("group_pool", group_pool);
+  f->dump_string("group_id", group_id);
+  f->dump_int("snapshot_id", snapshot_id);
+}
+
+class EncodeSnapshotNamespaceVisitor : public boost::static_visitor<void> {
+public:
+  explicit EncodeSnapshotNamespaceVisitor(bufferlist &bl) : m_bl(bl) {
+  }
+
+  template <typename T>
+  inline void operator()(const T& t) const {
+    ::encode(static_cast<uint32_t>(T::SNAPSHOT_NAMESPACE_TYPE), m_bl);
+    t.encode(m_bl);
+  }
+
+private:
+  bufferlist &m_bl;
+};
+
+class DecodeSnapshotNamespaceVisitor : public boost::static_visitor<void> {
+public:
+  DecodeSnapshotNamespaceVisitor(bufferlist::iterator &iter)
+    : m_iter(iter) {
+  }
+
+  template <typename T>
+  inline void operator()(T& t) const {
+    t.decode(m_iter);
+  }
+private:
+  bufferlist::iterator &m_iter;
+};
+
+class DumpSnapshotNamespaceVisitor : public boost::static_visitor<void> {
+public:
+  explicit DumpSnapshotNamespaceVisitor(Formatter *formatter, const std::string &key)
+    : m_formatter(formatter), m_key(key) {}
+
+  template <typename T>
+  inline void operator()(const T& t) const {
+    auto type = T::SNAPSHOT_NAMESPACE_TYPE;
+    m_formatter->dump_string(m_key.c_str(), stringify(type));
+    t.dump(m_formatter);
+  }
+private:
+  ceph::Formatter *m_formatter;
+  std::string m_key;
+};
+
+class GetTypeVisitor : public boost::static_visitor<SnapshotNamespaceType> {
+public:
+  template <typename T>
+  inline SnapshotNamespaceType operator()(const T&) const {
+    return static_cast<SnapshotNamespaceType>(T::SNAPSHOT_NAMESPACE_TYPE);
+  }
+};
+
+
+SnapshotNamespaceType SnapshotNamespaceOnDisk::get_namespace_type() const {
+  return static_cast<SnapshotNamespaceType>(boost::apply_visitor(GetTypeVisitor(),
+                                                                snapshot_namespace));
+}
+
+void SnapshotNamespaceOnDisk::encode(bufferlist& bl) const {
+  ENCODE_START(1, 1, bl);
+  boost::apply_visitor(EncodeSnapshotNamespaceVisitor(bl), snapshot_namespace);
+  ENCODE_FINISH(bl);
+}
+
+void SnapshotNamespaceOnDisk::decode(bufferlist::iterator &p)
+{
+  DECODE_START(1, p);
+  uint32_t snap_type;
+  ::decode(snap_type, p);
+  switch (snap_type) {
+    case cls::rbd::SNAPSHOT_NAMESPACE_TYPE_USER:
+      snapshot_namespace = UserSnapshotNamespace();
+      break;
+    case cls::rbd::SNAPSHOT_NAMESPACE_TYPE_GROUP:
+      snapshot_namespace = GroupSnapshotNamespace();
+      break;
+    default:
+      snapshot_namespace = UnknownSnapshotNamespace();
+      break;
+  }
+  boost::apply_visitor(DecodeSnapshotNamespaceVisitor(p), snapshot_namespace);
+  DECODE_FINISH(p);
+}
+
+void SnapshotNamespaceOnDisk::dump(Formatter *f) const {
+  boost::apply_visitor(DumpSnapshotNamespaceVisitor(f, "snapshot_namespace_type"), snapshot_namespace);
+}
+
+void SnapshotNamespaceOnDisk::generate_test_instances(std::list<SnapshotNamespaceOnDisk *> &o) {
+  o.push_back(new SnapshotNamespaceOnDisk(UserSnapshotNamespace()));
+  o.push_back(new SnapshotNamespaceOnDisk(GroupSnapshotNamespace(0, "10152ae8944a", 1)));
+  o.push_back(new SnapshotNamespaceOnDisk(GroupSnapshotNamespace(5, "1018643c9869", 3)));
+}
+
+std::ostream& operator<<(std::ostream& os, const UserSnapshotNamespace& ns) {
+  os << "[user]";
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const GroupSnapshotNamespace& ns) {
+  os << "[group"
+     << " group_pool=" << ns.group_pool
+     << " group_id=" << ns.group_id
+     << " snapshot_id=" << ns.snapshot_id << "]";
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const UnknownSnapshotNamespace& ns) {
+  os << "[unknown]";
+  return os;
+}
+
 } // namespace rbd
 } // namespace cls
index 547e9963250546f7a81cb3eb5967f24165f92225..d2a48a2d0c197dde2cb3acbd735a79b323b5421d 100644 (file)
@@ -4,6 +4,7 @@
 #ifndef CEPH_CLS_RBD_TYPES_H
 #define CEPH_CLS_RBD_TYPES_H
 
+#include <boost/variant.hpp>
 #include "include/int_types.h"
 #include "include/buffer.h"
 #include "include/encoding.h"
@@ -217,6 +218,98 @@ struct GroupSpec {
 
 WRITE_CLASS_ENCODER(GroupSpec);
 
+enum SnapshotNamespaceType {
+  SNAPSHOT_NAMESPACE_TYPE_USER = 0,
+  SNAPSHOT_NAMESPACE_TYPE_GROUP = 1
+};
+
+struct UserSnapshotNamespace {
+  static const uint32_t SNAPSHOT_NAMESPACE_TYPE = SNAPSHOT_NAMESPACE_TYPE_USER;
+
+  UserSnapshotNamespace() {}
+
+  void encode(bufferlist& bl) const {}
+  void decode(bufferlist::iterator& it) {}
+
+  void dump(Formatter *f) const {}
+
+  inline bool operator==(const UserSnapshotNamespace& usn) const {
+    return true;
+  }
+
+};
+
+std::ostream& operator<<(std::ostream& os, const UserSnapshotNamespace& ns);
+
+struct GroupSnapshotNamespace {
+  static const uint32_t SNAPSHOT_NAMESPACE_TYPE = SNAPSHOT_NAMESPACE_TYPE_GROUP;
+
+  GroupSnapshotNamespace() {}
+
+  GroupSnapshotNamespace(int64_t _group_pool,
+                        const string &_group_id,
+                        const snapid_t &_snapshot_id) :group_pool(_group_pool),
+                                                       group_id(_group_id),
+                                                       snapshot_id(_snapshot_id) {}
+
+  int64_t group_pool;
+  string group_id;
+  snapid_t snapshot_id;
+
+  void encode(bufferlist& bl) const;
+  void decode(bufferlist::iterator& it);
+
+  void dump(Formatter *f) const;
+
+  inline bool operator==(const GroupSnapshotNamespace& gsn) const {
+    return group_pool == gsn.group_pool &&
+          group_id == gsn.group_id &&
+          snapshot_id == gsn.snapshot_id;
+  }
+
+};
+
+std::ostream& operator<<(std::ostream& os, const GroupSnapshotNamespace& ns);
+
+struct UnknownSnapshotNamespace {
+  static const uint32_t SNAPSHOT_NAMESPACE_TYPE = static_cast<uint32_t>(-1);
+
+  UnknownSnapshotNamespace() {}
+
+  void encode(bufferlist& bl) const {}
+  void decode(bufferlist::iterator& it) {}
+  void dump(Formatter *f) const {}
+  inline bool operator==(const UnknownSnapshotNamespace& gsn) const {
+    return true;
+  }
+};
+
+std::ostream& operator<<(std::ostream& os, const UnknownSnapshotNamespace& ns);
+
+typedef boost::variant<UserSnapshotNamespace, GroupSnapshotNamespace, UnknownSnapshotNamespace> SnapshotNamespace;
+
+
+struct SnapshotNamespaceOnDisk {
+
+  SnapshotNamespaceOnDisk() : snapshot_namespace(UnknownSnapshotNamespace()) {}
+  SnapshotNamespaceOnDisk(const SnapshotNamespace &sn) : snapshot_namespace(sn) {}
+
+  SnapshotNamespace snapshot_namespace;
+
+  SnapshotNamespaceType get_namespace_type() const;
+
+  void encode(bufferlist& bl) const;
+  void decode(bufferlist::iterator& it);
+  void dump(Formatter *f) const;
+
+  static void generate_test_instances(std::list<SnapshotNamespaceOnDisk *> &o);
+
+  inline bool operator==(const SnapshotNamespaceOnDisk& gsn) const {
+    return snapshot_namespace == gsn.snapshot_namespace;
+  }
+};
+WRITE_CLASS_ENCODER(SnapshotNamespaceOnDisk);
+
 } // namespace rbd
 } // namespace cls
 
index 7de1c4298e492539d348416a9229b4c631f98bcd..1e0e3ceb9ea549321af4b5333b513b7dbfa82465 100644 (file)
@@ -29,7 +29,7 @@ using ::librbd::parent_spec;
 static int snapshot_add(librados::IoCtx *ioctx, const std::string &oid,
                         uint64_t snap_id, const std::string &snap_name) {
   librados::ObjectWriteOperation op;
-  ::librbd::cls_client::snapshot_add(&op, snap_id, snap_name);
+  ::librbd::cls_client::snapshot_add(&op, snap_id, snap_name, cls::rbd::UserSnapshotNamespace());
   return ioctx->operate(oid, &op);
 }
 
@@ -753,6 +753,7 @@ TEST_F(TestClsRbd, parents)
 
 TEST_F(TestClsRbd, snapshots)
 {
+  cls::rbd::SnapshotNamespace userSnapNamespace = cls::rbd::UserSnapshotNamespace();
   librados::IoCtx ioctx;
   ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx));
 
@@ -762,6 +763,7 @@ TEST_F(TestClsRbd, snapshots)
   ASSERT_EQ(0, create_image(&ioctx, oid, 10, 22, 0, oid, -1));
 
   vector<string> snap_names;
+  vector<cls::rbd::SnapshotNamespace> snap_namespaces;
   vector<uint64_t> snap_sizes;
   SnapContext snapc;
   vector<parent_info> parents;
@@ -772,7 +774,9 @@ TEST_F(TestClsRbd, snapshots)
   ASSERT_EQ(0u, snapc.seq);
   ASSERT_EQ(0, snapshot_list(&ioctx, oid, snapc.snaps, &snap_names,
                             &snap_sizes, &parents, &protection_status));
+  ASSERT_EQ(0, snap_namespace_list(&ioctx, oid, snapc.snaps, &snap_namespaces));
   ASSERT_EQ(0u, snap_names.size());
+  ASSERT_EQ(0u, snap_namespaces.size());
   ASSERT_EQ(0u, snap_sizes.size());
 
   ASSERT_EQ(0, snapshot_add(&ioctx, oid, 0, "snap1"));
@@ -782,8 +786,11 @@ TEST_F(TestClsRbd, snapshots)
   ASSERT_EQ(0u, snapc.seq);
   ASSERT_EQ(0, snapshot_list(&ioctx, oid, snapc.snaps, &snap_names,
                             &snap_sizes, &parents, &protection_status));
+  ASSERT_EQ(0, snap_namespace_list(&ioctx, oid, snapc.snaps, &snap_namespaces));
   ASSERT_EQ(1u, snap_names.size());
   ASSERT_EQ("snap1", snap_names[0]);
+  ASSERT_EQ(1u, snap_namespaces.size());
+  ASSERT_EQ(userSnapNamespace, snap_namespaces[0]);
   ASSERT_EQ(10u, snap_sizes[0]);
 
   // snap with same id and name
@@ -794,8 +801,11 @@ TEST_F(TestClsRbd, snapshots)
   ASSERT_EQ(0u, snapc.seq);
   ASSERT_EQ(0, snapshot_list(&ioctx, oid, snapc.snaps, &snap_names,
                             &snap_sizes, &parents, &protection_status));
+  ASSERT_EQ(0, snap_namespace_list(&ioctx, oid, snapc.snaps, &snap_namespaces));
   ASSERT_EQ(1u, snap_names.size());
   ASSERT_EQ("snap1", snap_names[0]);
+  ASSERT_EQ(1u, snap_namespaces.size());
+  ASSERT_EQ(userSnapNamespace, snap_namespaces[0]);
   ASSERT_EQ(10u, snap_sizes[0]);
 
   // snap with same id, different name
@@ -806,8 +816,11 @@ TEST_F(TestClsRbd, snapshots)
   ASSERT_EQ(0u, snapc.seq);
   ASSERT_EQ(0, snapshot_list(&ioctx, oid, snapc.snaps, &snap_names,
                             &snap_sizes, &parents, &protection_status));
+  ASSERT_EQ(0, snap_namespace_list(&ioctx, oid, snapc.snaps, &snap_namespaces));
   ASSERT_EQ(1u, snap_names.size());
   ASSERT_EQ("snap1", snap_names[0]);
+  ASSERT_EQ(1u, snap_namespaces.size());
+  ASSERT_EQ(userSnapNamespace, snap_namespaces[0]);
   ASSERT_EQ(10u, snap_sizes[0]);
 
   // snap with different id, same name
@@ -818,8 +831,11 @@ TEST_F(TestClsRbd, snapshots)
   ASSERT_EQ(0u, snapc.seq);
   ASSERT_EQ(0, snapshot_list(&ioctx, oid, snapc.snaps, &snap_names,
                             &snap_sizes, &parents, &protection_status));
+  ASSERT_EQ(0, snap_namespace_list(&ioctx, oid, snapc.snaps, &snap_namespaces));
   ASSERT_EQ(snap_names.size(), 1u);
   ASSERT_EQ(snap_names[0], "snap1");
+  ASSERT_EQ(1u, snap_namespaces.size());
+  ASSERT_EQ(userSnapNamespace, snap_namespaces[0]);
   ASSERT_EQ(snap_sizes[0], 10u);
 
   // snap with different id, different name
@@ -831,19 +847,27 @@ TEST_F(TestClsRbd, snapshots)
   ASSERT_EQ(1u, snapc.seq);
   ASSERT_EQ(0, snapshot_list(&ioctx, oid, snapc.snaps, &snap_names,
                             &snap_sizes, &parents, &protection_status));
+  ASSERT_EQ(0, snap_namespace_list(&ioctx, oid, snapc.snaps, &snap_namespaces));
   ASSERT_EQ(2u, snap_names.size());
+  ASSERT_EQ(2u, snap_namespaces.size());
   ASSERT_EQ("snap2", snap_names[0]);
+  ASSERT_EQ(userSnapNamespace, snap_namespaces[0]);
   ASSERT_EQ(10u, snap_sizes[0]);
   ASSERT_EQ("snap1", snap_names[1]);
+  ASSERT_EQ(userSnapNamespace, snap_namespaces[1]);
   ASSERT_EQ(10u, snap_sizes[1]);
 
   ASSERT_EQ(0, snapshot_rename(&ioctx, oid, 0, "snap1-rename"));
   ASSERT_EQ(0, snapshot_list(&ioctx, oid, snapc.snaps, &snap_names,
                             &snap_sizes, &parents, &protection_status));
+  ASSERT_EQ(0, snap_namespace_list(&ioctx, oid, snapc.snaps, &snap_namespaces));
   ASSERT_EQ(2u, snap_names.size());
+  ASSERT_EQ(2u, snap_namespaces.size());
   ASSERT_EQ("snap2", snap_names[0]);
+  ASSERT_EQ(userSnapNamespace, snap_namespaces[0]);
   ASSERT_EQ(10u, snap_sizes[0]);
   ASSERT_EQ("snap1-rename", snap_names[1]);
+  ASSERT_EQ(userSnapNamespace, snap_namespaces[1]);
   ASSERT_EQ(10u, snap_sizes[1]);
   ASSERT_EQ(0, snapshot_remove(&ioctx, oid, 0));
   ASSERT_EQ(0, get_snapcontext(&ioctx, oid, &snapc));
@@ -852,8 +876,11 @@ TEST_F(TestClsRbd, snapshots)
   ASSERT_EQ(1u, snapc.seq);
   ASSERT_EQ(0, snapshot_list(&ioctx, oid, snapc.snaps, &snap_names,
                             &snap_sizes, &parents, &protection_status));
+  ASSERT_EQ(0, snap_namespace_list(&ioctx, oid, snapc.snaps, &snap_namespaces));
   ASSERT_EQ(1u, snap_names.size());
+  ASSERT_EQ(1u, snap_namespaces.size());
   ASSERT_EQ("snap2", snap_names[0]);
+  ASSERT_EQ(userSnapNamespace, snap_namespaces[0]);
   ASSERT_EQ(10u, snap_sizes[0]);
 
   uint64_t size;
@@ -872,10 +899,14 @@ TEST_F(TestClsRbd, snapshots)
   ASSERT_EQ(large_snap_id, snapc.seq);
   ASSERT_EQ(0, snapshot_list(&ioctx, oid, snapc.snaps, &snap_names,
                             &snap_sizes, &parents, &protection_status));
+  ASSERT_EQ(0, snap_namespace_list(&ioctx, oid, snapc.snaps, &snap_namespaces));
   ASSERT_EQ(2u, snap_names.size());
+  ASSERT_EQ(2u, snap_namespaces.size());
   ASSERT_EQ("snap3", snap_names[0]);
+  ASSERT_EQ(userSnapNamespace, snap_namespaces[0]);
   ASSERT_EQ(0u, snap_sizes[0]);
   ASSERT_EQ("snap2", snap_names[1]);
+  ASSERT_EQ(userSnapNamespace, snap_namespaces[1]);
   ASSERT_EQ(10u, snap_sizes[1]);
 
   ASSERT_EQ(0, get_size(&ioctx, oid, large_snap_id, &size, &order));
@@ -893,8 +924,11 @@ TEST_F(TestClsRbd, snapshots)
   ASSERT_EQ(large_snap_id, snapc.seq);
   ASSERT_EQ(0, snapshot_list(&ioctx, oid, snapc.snaps, &snap_names,
                             &snap_sizes, &parents, &protection_status));
+  ASSERT_EQ(0, snap_namespace_list(&ioctx, oid, snapc.snaps, &snap_namespaces));
   ASSERT_EQ(1u, snap_names.size());
+  ASSERT_EQ(1u, snap_namespaces.size());
   ASSERT_EQ("snap2", snap_names[0]);
+  ASSERT_EQ(userSnapNamespace, snap_namespaces[0]);
   ASSERT_EQ(10u, snap_sizes[0]);
 
   ASSERT_EQ(-ENOENT, snapshot_remove(&ioctx, oid, large_snap_id));
@@ -904,12 +938,49 @@ TEST_F(TestClsRbd, snapshots)
   ASSERT_EQ(large_snap_id, snapc.seq);
   ASSERT_EQ(0, snapshot_list(&ioctx, oid, snapc.snaps, &snap_names,
                             &snap_sizes, &parents, &protection_status));
+  ASSERT_EQ(0, snap_namespace_list(&ioctx, oid, snapc.snaps, &snap_namespaces));
   ASSERT_EQ(0u, snap_names.size());
+  ASSERT_EQ(0u, snap_namespaces.size());
   ASSERT_EQ(0u, snap_sizes.size());
 
   ioctx.close();
 }
 
+TEST_F(TestClsRbd, snapshots_namespaces)
+{
+  cls::rbd::SnapshotNamespace groupSnapNamespace = cls::rbd::GroupSnapshotNamespace(5, "1018643c9869", 3);
+  librados::IoCtx ioctx;
+  ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx));
+
+  string oid = get_temp_image_name();
+
+  ASSERT_EQ(0, create_image(&ioctx, oid, 10, 22, 0, oid, -1));
+
+  vector<string> snap_names;
+  vector<cls::rbd::SnapshotNamespace> snap_namespaces;
+  SnapContext snapc;
+
+  ASSERT_EQ(0, get_snapcontext(&ioctx, oid, &snapc));
+  ASSERT_EQ(0u, snapc.snaps.size());
+  ASSERT_EQ(0u, snapc.seq);
+  ASSERT_EQ(0, snap_namespace_list(&ioctx, oid, snapc.snaps, &snap_namespaces));
+  ASSERT_EQ(0u, snap_namespaces.size());
+
+  ASSERT_EQ(0, snapshot_add(&ioctx, oid, 0, "snap1"));
+
+  librados::ObjectWriteOperation op;
+  ::librbd::cls_client::snapshot_add(&op, 0, "snap1", groupSnapNamespace);
+  int r = ioctx.operate(oid, &op);
+  ASSERT_EQ(0, r);
+
+  ASSERT_EQ(0, get_snapcontext(&ioctx, oid, &snapc));
+  ASSERT_EQ(1u, snapc.snaps.size());
+  ASSERT_EQ(0u, snapc.snaps[0]);
+  ASSERT_EQ(0u, snapc.seq);
+  ASSERT_EQ(0, snap_namespace_list(&ioctx, oid, snapc.snaps, &snap_namespaces));
+  ASSERT_EQ(groupSnapNamespace, snap_namespaces[0]);
+}
+
 TEST_F(TestClsRbd, snapid_race)
 {
   librados::IoCtx ioctx;