return 0;
}
+namespace trash {
+
+static const std::string IMAGE_KEY_PREFIX("id_");
+
+std::string image_key(const std::string &image_id) {
+ return IMAGE_KEY_PREFIX + image_id;
+}
+
+std::string image_id_from_key(const std::string &key) {
+ return key.substr(IMAGE_KEY_PREFIX.size());
+}
+
+} // namespace trash
+
+/**
+ * Add an image entry to the rbd trash. Creates the trash object if
+ * needed, and stores the trash spec information of the deleted image.
+ *
+ * Input:
+ * @param id the id of the image
+ * @param trash_spec the spec info of the deleted image
+ *
+ * Output:
+ * @returns -EEXIST if the image id is already in the trash
+ * @returns 0 on success, negative error code on failure
+ */
+int trash_add(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ int r = cls_cxx_create(hctx, false);
+ if (r < 0) {
+ CLS_ERR("could not create trash: %s", cpp_strerror(r).c_str());
+ return r;
+ }
+
+ string id;
+ cls::rbd::TrashImageSpec trash_spec;
+ try {
+ bufferlist::iterator iter = in->begin();
+ ::decode(id, iter);
+ ::decode(trash_spec, iter);
+ } catch (const buffer::error &err) {
+ return -EINVAL;
+ }
+
+ if (!is_valid_id(id)) {
+ CLS_ERR("trash_add: invalid id '%s'", id.c_str());
+ return -EINVAL;
+ }
+
+ CLS_LOG(20, "trash_add id=%s", id.c_str());
+
+ string key = trash::image_key(id);
+ cls::rbd::TrashImageSpec tmp;
+ r = read_key(hctx, key, &tmp);
+ if (r < 0 && r != -ENOENT) {
+ CLS_ERR("could not read key %s entry from trash: %s", key.c_str(),
+ cpp_strerror(r).c_str());
+ return r;
+ } else if (r == 0) {
+ CLS_LOG(10, "id already exists");
+ return -EEXIST;
+ }
+
+ map<string, bufferlist> omap_vals;
+ ::encode(trash_spec, omap_vals[key]);
+ return cls_cxx_map_set_vals(hctx, &omap_vals);
+}
+
+/**
+ * Removes an image entry from the rbd trash object.
+ * image.
+ *
+ * Input:
+ * @param id the id of the image
+ *
+ * Output:
+ * @returns -ENOENT if the image id does not exist in the trash
+ * @returns 0 on success, negative error code on failure
+ */
+int trash_remove(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ string id;
+ try {
+ bufferlist::iterator iter = in->begin();
+ ::decode(id, iter);
+ } catch (const buffer::error &err) {
+ return -EINVAL;
+ }
+
+ CLS_LOG(20, "trash_remove id=%s", id.c_str());
+
+ string key = trash::image_key(id);
+ bufferlist tmp;
+ int r = cls_cxx_map_get_val(hctx, key, &tmp);
+ if (r < 0) {
+ if (r != -ENOENT) {
+ CLS_ERR("error reading entry key %s: %s", key.c_str(), cpp_strerror(r).c_str());
+ }
+ return r;
+ }
+
+ r = cls_cxx_map_remove_key(hctx, key);
+ if (r < 0) {
+ CLS_ERR("error removing entry: %s", cpp_strerror(r).c_str());
+ return r;
+ }
+
+ return 0;
+}
+
+/**
+ * Returns the list of trash spec entries registered in the rbd_trash
+ * object.
+ *
+ * Output:
+ * @param data the map between image id and trash spec info
+ *
+ * @returns 0 on success, negative error code on failure
+ */
+int trash_list(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ map<string, cls::rbd::TrashImageSpec> data;
+ string last_read = trash::image_key("");
+ int max_read = RBD_MAX_KEYS_READ;
+
+ CLS_LOG(20, "trash_get_images");
+
+ do {
+ map<string, bufferlist> raw_data;
+ int r = cls_cxx_map_get_vals(hctx, last_read, trash::IMAGE_KEY_PREFIX,
+ max_read, &raw_data);
+ if (r < 0) {
+ CLS_ERR("failed to read the vals off of disk: %s", cpp_strerror(r).c_str());
+ return r;
+ }
+ if (raw_data.empty()) {
+ break;
+ }
+
+ map<string, bufferlist>::iterator it = raw_data.begin();
+ for (; it != raw_data.end(); ++it) {
+ ::decode(data[trash::image_id_from_key(it->first)], it->second);
+ }
+
+ if (r < max_read) {
+ break;
+ }
+
+ last_read = raw_data.rbegin()->first;
+ } while (max_read);
+
+ ::encode(data, *out);
+
+ return 0;
+}
+
+/**
+ * Returns the trash spec entry of an image registered in the rbd_trash
+ * object.
+ *
+ * Input:
+ * @param id the id of the image
+ *
+ * Output:
+ * @param out the trash spec entry
+ *
+ * @returns 0 on success, negative error code on failure
+ */
+int trash_get(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ string id;
+ try {
+ bufferlist::iterator iter = in->begin();
+ ::decode(id, iter);
+ } catch (const buffer::error &err) {
+ return -EINVAL;
+ }
+
+ CLS_LOG(20, "trash_get_image id=%s", id.c_str());
+
+
+ string key = trash::image_key(id);
+ bufferlist bl;
+ int r = cls_cxx_map_get_val(hctx, key, out);
+ if (r != -ENOENT) {
+ CLS_ERR("error reading image from trash '%s': '%s'", id.c_str(),
+ cpp_strerror(r).c_str());
+ }
+ return r;
+}
+
CLS_INIT(rbd)
{
CLS_LOG(20, "Loaded rbd class!");
cls_method_handle_t h_image_add_group;
cls_method_handle_t h_image_remove_group;
cls_method_handle_t h_image_get_group;
+ cls_method_handle_t h_trash_add;
+ cls_method_handle_t h_trash_remove;
+ cls_method_handle_t h_trash_list;
+ cls_method_handle_t h_trash_get;
cls_register("rbd", &h_class);
cls_register_cxx_method(h_class, "create",
cls_register_cxx_method(h_class, "image_get_group",
CLS_METHOD_RD,
image_get_group, &h_image_get_group);
+
+ /* rbd_trash object methods */
+ cls_register_cxx_method(h_class, "trash_add",
+ CLS_METHOD_RD | CLS_METHOD_WR,
+ trash_add, &h_trash_add);
+ cls_register_cxx_method(h_class, "trash_remove",
+ CLS_METHOD_RD | CLS_METHOD_WR,
+ trash_remove, &h_trash_remove);
+ cls_register_cxx_method(h_class, "trash_list",
+ CLS_METHOD_RD,
+ trash_list, &h_trash_list);
+ cls_register_cxx_method(h_class, "trash_get",
+ CLS_METHOD_RD,
+ trash_get, &h_trash_get);
+
return;
}
return image_get_group_finish(&iter, group_spec);
}
+ // rbd_trash functions
+ void trash_add(librados::ObjectWriteOperation *op,
+ const std::string &id,
+ const cls::rbd::TrashImageSpec &trash_spec)
+ {
+ bufferlist bl;
+ ::encode(id, bl);
+ ::encode(trash_spec, bl);
+ op->exec("rbd", "trash_add", bl);
+ }
+
+ int trash_add(librados::IoCtx *ioctx, const std::string &id,
+ const cls::rbd::TrashImageSpec &trash_spec)
+ {
+ librados::ObjectWriteOperation op;
+ trash_add(&op, id, trash_spec);
+
+ return ioctx->operate(RBD_TRASH, &op);
+ }
+
+ void trash_remove(librados::ObjectWriteOperation *op,
+ const std::string &id)
+ {
+ bufferlist bl;
+ ::encode(id, bl);
+ op->exec("rbd", "trash_remove", bl);
+ }
+
+ int trash_remove(librados::IoCtx *ioctx, const std::string &id)
+ {
+ librados::ObjectWriteOperation op;
+ trash_remove(&op, id);
+
+ return ioctx->operate(RBD_TRASH, &op);
+ }
+
+ void trash_list_start(librados::ObjectReadOperation *op)
+ {
+ bufferlist bl;
+ op->exec("rbd", "trash_list", bl);
+ }
+
+ int trash_list_finish(bufferlist::iterator *it,
+ map<string, cls::rbd::TrashImageSpec> *entries)
+ {
+ assert(entries);
+
+ try {
+ ::decode(*entries, *it);
+ } catch (const buffer::error &err) {
+ return -EBADMSG;
+ }
+
+ return 0;
+ }
+
+ int trash_list(librados::IoCtx *ioctx,
+ map<string, cls::rbd::TrashImageSpec> *entries)
+ {
+ librados::ObjectReadOperation op;
+ trash_list_start(&op);
+
+ bufferlist out_bl;
+ int r = ioctx->operate(RBD_TRASH, &op, &out_bl);
+ if (r < 0) {
+ return r;
+ }
+
+ bufferlist::iterator iter = out_bl.begin();
+ return trash_list_finish(&iter, entries);
+ }
+
+ void trash_get_start(librados::ObjectReadOperation *op,
+ const std::string &id)
+ {
+ bufferlist bl;
+ ::encode(id, bl);
+ op->exec("rbd", "trash_get", bl);
+ }
+
+ int trash_get_finish(bufferlist::iterator *it,
+ cls::rbd::TrashImageSpec *trash_spec) {
+ assert(trash_spec);
+ try {
+ ::decode(*trash_spec, *it);
+ } catch (const buffer::error &err) {
+ return -EBADMSG;
+ }
+
+ return 0;
+ }
+
+
+ int trash_get(librados::IoCtx *ioctx, const std::string &id,
+ cls::rbd::TrashImageSpec *trash_spec)
+ {
+ librados::ObjectReadOperation op;
+ trash_get_start(&op, id);
+
+ bufferlist out_bl;
+ int r = ioctx->operate(RBD_TRASH, &op, &out_bl);
+ if (r < 0) {
+ return r;
+ }
+
+ bufferlist::iterator it = out_bl.begin();
+ return trash_get_finish(&it, trash_spec);
+ }
+
} // namespace cls_client
} // namespace librbd
int image_get_group(librados::IoCtx *ioctx, const std::string &oid,
cls::rbd::GroupSpec *group_spec);
+ // operations on rbd_trash object
+ void trash_add(librados::ObjectWriteOperation *op,
+ const std::string &id,
+ const cls::rbd::TrashImageSpec &trash_spec);
+ int trash_add(librados::IoCtx *ioctx, const std::string &id,
+ const cls::rbd::TrashImageSpec &trash_spec);
+ void trash_remove(librados::ObjectWriteOperation *op,
+ const std::string &id);
+ int trash_remove(librados::IoCtx *ioctx, const std::string &id);
+ void trash_list_start(librados::ObjectReadOperation *op);
+ int trash_list_finish(bufferlist::iterator *it,
+ map<string, cls::rbd::TrashImageSpec> *entries);
+ int trash_list(librados::IoCtx *ioctx,
+ map<string, cls::rbd::TrashImageSpec> *entries);
+ void trash_get_start(librados::ObjectReadOperation *op,
+ const std::string &id);
+ int trash_get_finish(bufferlist::iterator *it,
+ cls::rbd::TrashImageSpec *trash_spec);
+ int trash_get(librados::IoCtx *ioctx, const std::string &id,
+ cls::rbd::TrashImageSpec *trash_spec);
+
} // namespace cls_client
} // namespace librbd
#endif // CEPH_LIBRBD_CLS_RBD_CLIENT_H
return os;
}
+void TrashImageSpec::encode(bufferlist& bl) const {
+ ENCODE_START(1, 1, bl);
+ ::encode(source, bl);
+ ::encode(name, bl);
+ ::encode(deletion_time, bl);
+ ::encode(deferment_end_time, bl);
+ ENCODE_FINISH(bl);
+}
+
+void TrashImageSpec::decode(bufferlist::iterator &it) {
+ DECODE_START(1, it);
+ ::decode(source, it);
+ ::decode(name, it);
+ ::decode(deletion_time, it);
+ ::decode(deferment_end_time, it);
+ DECODE_FINISH(it);
+}
+
+void TrashImageSpec::dump(Formatter *f) const {
+ switch(source) {
+ case TRASH_IMAGE_SOURCE_USER:
+ f->dump_string("source", "user");
+ break;
+ case TRASH_IMAGE_SOURCE_MIRRORING:
+ f->dump_string("source", "rbd_mirror");
+ }
+ f->dump_string("name", name);
+ f->dump_unsigned("deletion_time", deletion_time);
+ f->dump_unsigned("deferment_end_time", deferment_end_time);
+}
+
} // namespace rbd
} // namespace cls
};
WRITE_CLASS_ENCODER(SnapshotNamespaceOnDisk);
+enum TrashImageSource {
+ TRASH_IMAGE_SOURCE_USER = 0,
+ TRASH_IMAGE_SOURCE_MIRRORING = 1
+};
+
+inline void encode(const TrashImageSource &source, bufferlist& bl,
+ uint64_t features=0)
+{
+ ::encode(static_cast<uint8_t>(source), bl);
+}
+
+inline void decode(TrashImageSource &source, bufferlist::iterator& it)
+{
+ uint8_t int_source;
+ ::decode(int_source, it);
+ source = static_cast<TrashImageSource>(int_source);
+}
+
+struct TrashImageSpec {
+ TrashImageSource source = TRASH_IMAGE_SOURCE_USER;
+ std::string name;
+ utime_t deletion_time; // time of deletion
+ utime_t deferment_end_time;
+
+ TrashImageSpec() {}
+ TrashImageSpec(TrashImageSource source, const std::string &name,
+ utime_t deletion_time, utime_t deferment_end_time) :
+ source(source), name(name), deletion_time(deletion_time),
+ deferment_end_time(deferment_end_time) {}
+
+ void encode(bufferlist &bl) const;
+ void decode(bufferlist::iterator& it);
+ void dump(Formatter *f) const;
+};
+WRITE_CLASS_ENCODER(TrashImageSpec);
+
} // namespace rbd
} // namespace cls
#define RBD_GROUP_DIRECTORY "rbd_group_directory"
+#define RBD_TRASH "rbd_trash"
+
struct rbd_info {
__le64 max_id;
} __attribute__ ((packed));
#include "common/ceph_context.h"
#include "common/config.h"
#include "common/snap_types.h"
+#include "common/Clock.h"
#include "include/encoding.h"
#include "include/types.h"
#include "include/rados/librados.h"
ASSERT_EQ(group_id, spec.group_id);
ASSERT_EQ(pool_id, spec.pool_id);
}
+
+TEST_F(TestClsRbd, trash_methods)
+{
+ librados::IoCtx ioctx;
+ ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx));
+
+ string id = "123456789";
+ string id2 = "123456780";
+
+ std::map<string, cls::rbd::TrashImageSpec> entries;
+ ASSERT_EQ(-ENOENT, trash_list(&ioctx, &entries));
+
+ utime_t now1 = ceph_clock_now();
+ utime_t now1_delay = now1;
+ now1_delay += 380;
+ cls::rbd::TrashImageSpec trash_spec(cls::rbd::TRASH_IMAGE_SOURCE_USER, "name",
+ now1, now1_delay);
+ ASSERT_EQ(0, trash_add(&ioctx, id, trash_spec));
+
+ utime_t now2 = ceph_clock_now();
+ utime_t now2_delay = now2;
+ now2_delay += 480;
+ cls::rbd::TrashImageSpec trash_spec2(cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING,
+ "name2", now2, now2_delay);
+ ASSERT_EQ(-EEXIST, trash_add(&ioctx, id, trash_spec2));
+
+ ASSERT_EQ(0, trash_remove(&ioctx, id));
+ ASSERT_EQ(-ENOENT, trash_remove(&ioctx, id));
+
+ ASSERT_EQ(0, trash_list(&ioctx, &entries));
+ ASSERT_TRUE(entries.empty());
+
+ ASSERT_EQ(0, trash_add(&ioctx, id, trash_spec2));
+ ASSERT_EQ(0, trash_add(&ioctx, id2, trash_spec));
+
+ ASSERT_EQ(0, trash_list(&ioctx, &entries));
+
+ for (auto& entry : entries) {
+ if (entry.first == id) {
+ ASSERT_EQ(entry.second.source, cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING);
+ ASSERT_EQ(entry.second.name, "name2");
+ ASSERT_EQ(entry.second.deletion_time, now2);
+ ASSERT_EQ(entry.second.deferment_end_time, now2_delay);
+ } else if (entry.first == id2) {
+ ASSERT_EQ(entry.second.source, cls::rbd::TRASH_IMAGE_SOURCE_USER);
+ ASSERT_EQ(entry.second.name, "name");
+ ASSERT_EQ(entry.second.deletion_time, now1);
+ ASSERT_EQ(entry.second.deferment_end_time, now1_delay);
+ }
+ }
+
+ cls::rbd::TrashImageSpec spec_res1;
+ ASSERT_EQ(0, trash_get(&ioctx, id, &spec_res1));
+ cls::rbd::TrashImageSpec spec_res2;
+ ASSERT_EQ(0, trash_get(&ioctx, id2, &spec_res2));
+
+ ASSERT_EQ(spec_res1.name, "name2");
+ ASSERT_EQ(spec_res1.deletion_time, now2);
+ ASSERT_EQ(spec_res1.deferment_end_time, now2_delay);
+
+ ASSERT_EQ(spec_res2.name, "name");
+ ASSERT_EQ(spec_res2.deletion_time, now1);
+ ASSERT_EQ(spec_res2.deferment_end_time, now1_delay);
+
+ ioctx.close();
+}
+