diff --git a/src/cls/rbd/cls_rbd.cc b/src/cls/rbd/cls_rbd.cc index 836782f5f73..49855bc3552 100644 --- a/src/cls/rbd/cls_rbd.cc +++ b/src/cls/rbd/cls_rbd.cc @@ -278,6 +278,184 @@ int snapshot_iterate(cls_method_context_t hctx, L& lambda) { return 0; } +int set_migration(cls_method_context_t hctx, + const cls::rbd::MigrationSpec &migration_spec, bool init) { + if (init) { + bufferlist bl; + int r = cls_cxx_map_get_val(hctx, "migration", &bl); + if (r != -ENOENT) { + if (r == 0) { + CLS_LOG(10, "migration already set"); + return -EEXIST; + } + CLS_ERR("failed to read migration off disk: %s", cpp_strerror(r).c_str()); + return r; + } + + uint64_t features = 0; + r = read_key(hctx, "features", &features); + if (r == -ENOENT) { + CLS_LOG(20, "no features, assuming v1 format"); + bufferlist header; + r = cls_cxx_read(hctx, 0, sizeof(RBD_HEADER_TEXT), &header); + if (r < 0) { + CLS_ERR("failed to read v1 header: %s", cpp_strerror(r).c_str()); + return r; + } + if (header.length() != sizeof(RBD_HEADER_TEXT)) { + CLS_ERR("unrecognized v1 header format"); + return -ENXIO; + } + if (memcmp(RBD_HEADER_TEXT, header.c_str(), header.length()) != 0) { + if (memcmp(RBD_MIGRATE_HEADER_TEXT, header.c_str(), + header.length()) == 0) { + CLS_LOG(10, "migration already set"); + return -EEXIST; + } else { + CLS_ERR("unrecognized v1 header format"); + return -ENXIO; + } + } + if (migration_spec.header_type != cls::rbd::MIGRATION_HEADER_TYPE_SRC) { + CLS_LOG(10, "v1 format image can only be migration source"); + return -EINVAL; + } + + header.clear(); + header.append(RBD_MIGRATE_HEADER_TEXT); + r = cls_cxx_write(hctx, 0, header.length(), &header); + if (r < 0) { + CLS_ERR("error updating v1 header: %s", cpp_strerror(r).c_str()); + return r; + } + } else if (r < 0) { + CLS_ERR("failed to read features off disk: %s", cpp_strerror(r).c_str()); + return r; + } else if ((features & RBD_FEATURE_MIGRATING) != 0ULL) { + if (migration_spec.header_type != cls::rbd::MIGRATION_HEADER_TYPE_DST) { + CLS_LOG(10, "migrating feature already set"); + return -EEXIST; + } + } else { + features |= RBD_FEATURE_MIGRATING; + bl.clear(); + encode(features, bl); + r = cls_cxx_map_set_val(hctx, "features", &bl); + if (r < 0) { + CLS_ERR("error updating features: %s", cpp_strerror(r).c_str()); + return r; + } + } + } + + bufferlist bl; + encode(migration_spec, bl); + int r = cls_cxx_map_set_val(hctx, "migration", &bl); + if (r < 0) { + CLS_ERR("error setting migration: %s", cpp_strerror(r).c_str()); + return r; + } + + return 0; +} + +int read_migration(cls_method_context_t hctx, + cls::rbd::MigrationSpec *migration_spec) { + uint64_t features = 0; + int r = read_key(hctx, "features", &features); + if (r == -ENOENT) { + CLS_LOG(20, "no features, assuming v1 format"); + bufferlist header; + r = cls_cxx_read(hctx, 0, sizeof(RBD_HEADER_TEXT), &header); + if (r < 0) { + CLS_ERR("failed to read v1 header: %s", cpp_strerror(r).c_str()); + return r; + } + if (header.length() != sizeof(RBD_HEADER_TEXT)) { + CLS_ERR("unrecognized v1 header format"); + return -ENXIO; + } + if (memcmp(RBD_MIGRATE_HEADER_TEXT, header.c_str(), header.length()) != 0) { + if (memcmp(RBD_HEADER_TEXT, header.c_str(), header.length()) == 0) { + CLS_LOG(10, "migration feature not set"); + return -EINVAL; + } else { + CLS_ERR("unrecognized v1 header format"); + return -ENXIO; + } + } + if (migration_spec->header_type != cls::rbd::MIGRATION_HEADER_TYPE_SRC) { + CLS_LOG(10, "v1 format image can only be migration source"); + return -EINVAL; + } + } else if (r < 0) { + CLS_ERR("failed to read features off disk: %s", cpp_strerror(r).c_str()); + return r; + } else if ((features & RBD_FEATURE_MIGRATING) == 0ULL) { + CLS_LOG(10, "migration feature not set"); + return -EINVAL; + } + + r = read_key(hctx, "migration", migration_spec); + if (r < 0) { + CLS_ERR("failed to read migration off disk: %s", cpp_strerror(r).c_str()); + return r; + } + + return 0; +} + +int remove_migration(cls_method_context_t hctx) { + int r = remove_key(hctx, "migration"); + if (r < 0) { + return r; + } + + uint64_t features = 0; + r = read_key(hctx, "features", &features); + if (r == -ENOENT) { + CLS_LOG(20, "no features, assuming v1 format"); + bufferlist header; + r = cls_cxx_read(hctx, 0, sizeof(RBD_MIGRATE_HEADER_TEXT), &header); + if (header.length() != sizeof(RBD_MIGRATE_HEADER_TEXT)) { + CLS_ERR("unrecognized v1 header format"); + return -ENXIO; + } + if (memcmp(RBD_MIGRATE_HEADER_TEXT, header.c_str(), header.length()) != 0) { + if (memcmp(RBD_HEADER_TEXT, header.c_str(), header.length()) == 0) { + CLS_LOG(10, "migration feature not set"); + return -EINVAL; + } else { + CLS_ERR("unrecognized v1 header format"); + return -ENXIO; + } + } + header.clear(); + header.append(RBD_HEADER_TEXT); + r = cls_cxx_write(hctx, 0, header.length(), &header); + if (r < 0) { + CLS_ERR("error updating v1 header: %s", cpp_strerror(r).c_str()); + return r; + } + } else if (r < 0) { + CLS_ERR("failed to read features off disk: %s", cpp_strerror(r).c_str()); + return r; + } else if ((features & RBD_FEATURE_MIGRATING) == 0ULL) { + CLS_LOG(10, "migrating feature not set"); + } else { + features &= ~RBD_FEATURE_MIGRATING; + bufferlist bl; + encode(features, bl); + r = cls_cxx_map_set_val(hctx, "features", &bl); + if (r < 0) { + CLS_ERR("error updating features: %s", cpp_strerror(r).c_str()); + return r; + } + } + + return 0; +} + } // namespace image /** @@ -3449,6 +3627,114 @@ int children_list(cls_method_context_t hctx, bufferlist *in, bufferlist *out) return 0; } +/** + * Set image migration. + * + * Input: + * @param migration_spec (cls::rbd::MigrationSpec) image migration spec + * + * Output: + * + * @returns 0 on success, negative error code on failure + */ +int migration_set(cls_method_context_t hctx, bufferlist *in, bufferlist *out) { + cls::rbd::MigrationSpec migration_spec; + try { + auto it = in->cbegin(); + decode(migration_spec, it); + } catch (const buffer::error &err) { + return -EINVAL; + } + + int r = image::set_migration(hctx, migration_spec, true); + if (r < 0) { + return r; + } + + return 0; +} + +/** + * Set image migration state. + * + * Input: + * @param state (cls::rbd::MigrationState) migration state + * @param description (std::string) migration state description + * + * Output: + * + * @returns 0 on success, negative error code on failure + */ +int migration_set_state(cls_method_context_t hctx, bufferlist *in, + bufferlist *out) { + cls::rbd::MigrationState state; + std::string description; + try { + auto it = in->cbegin(); + decode(state, it); + decode(description, it); + } catch (const buffer::error &err) { + return -EINVAL; + } + + cls::rbd::MigrationSpec migration_spec; + int r = image::read_migration(hctx, &migration_spec); + if (r < 0) { + return r; + } + + migration_spec.state = state; + migration_spec.state_description = description; + + r = image::set_migration(hctx, migration_spec, false); + if (r < 0) { + return r; + } + + return 0; +} + +/** + * Get image migration spec. + * + * Input: + * + * Output: + * @param migration_spec (cls::rbd::MigrationSpec) image migration spec + * + * @returns 0 on success, negative error code on failure + */ +int migration_get(cls_method_context_t hctx, bufferlist *in, bufferlist *out) { + cls::rbd::MigrationSpec migration_spec; + int r = image::read_migration(hctx, &migration_spec); + if (r < 0) { + return r; + } + + encode(migration_spec, *out); + + return 0; +} + +/** + * Remove image migration spec. + * + * Input: + * + * Output: + * + * @returns 0 on success, negative error code on failure + */ +int migration_remove(cls_method_context_t hctx, bufferlist *in, + bufferlist *out) { + int r = image::remove_migration(hctx); + if (r < 0) { + return r; + } + + return 0; +} + /****************************** Old format *******************************/ int old_snapshots_list(cls_method_context_t hctx, bufferlist *in, bufferlist *out) @@ -6371,6 +6657,10 @@ CLS_INIT(rbd) cls_method_handle_t h_child_attach; cls_method_handle_t h_child_detach; cls_method_handle_t h_children_list; + cls_method_handle_t h_migration_set; + cls_method_handle_t h_migration_set_state; + cls_method_handle_t h_migration_get; + cls_method_handle_t h_migration_remove; cls_method_handle_t h_old_snapshots_list; cls_method_handle_t h_old_snapshot_add; cls_method_handle_t h_old_snapshot_remove; @@ -6536,6 +6826,18 @@ CLS_INIT(rbd) cls_register_cxx_method(h_class, "children_list", CLS_METHOD_RD, children_list, &h_children_list); + cls_register_cxx_method(h_class, "migration_set", + CLS_METHOD_RD | CLS_METHOD_WR, + migration_set, &h_migration_set); + cls_register_cxx_method(h_class, "migration_set_state", + CLS_METHOD_RD | CLS_METHOD_WR, + migration_set_state, &h_migration_set_state); + cls_register_cxx_method(h_class, "migration_get", + CLS_METHOD_RD, + migration_get, &h_migration_get); + cls_register_cxx_method(h_class, "migration_remove", + CLS_METHOD_RD | CLS_METHOD_WR, + migration_remove, &h_migration_remove); /* methods for the rbd_children object */ cls_register_cxx_method(h_class, "add_child", diff --git a/src/cls/rbd/cls_rbd_client.cc b/src/cls/rbd/cls_rbd_client.cc index fabf0e2e853..98e9cea9967 100644 --- a/src/cls/rbd/cls_rbd_client.cc +++ b/src/cls/rbd/cls_rbd_client.cc @@ -1591,6 +1591,82 @@ namespace librbd { return 0; } + int migration_set(librados::IoCtx *ioctx, const std::string &oid, + const cls::rbd::MigrationSpec &migration_spec) { + librados::ObjectWriteOperation op; + migration_set(&op, migration_spec); + return ioctx->operate(oid, &op); + } + + void migration_set(librados::ObjectWriteOperation *op, + const cls::rbd::MigrationSpec &migration_spec) { + bufferlist bl; + encode(migration_spec, bl); + op->exec("rbd", "migration_set", bl); + } + + int migration_set_state(librados::IoCtx *ioctx, const std::string &oid, + cls::rbd::MigrationState state, + const std::string &description) { + librados::ObjectWriteOperation op; + migration_set_state(&op, state, description); + return ioctx->operate(oid, &op); + } + + void migration_set_state(librados::ObjectWriteOperation *op, + cls::rbd::MigrationState state, + const std::string &description) { + bufferlist bl; + encode(state, bl); + encode(description, bl); + op->exec("rbd", "migration_set_state", bl); + } + + void migration_get_start(librados::ObjectReadOperation *op) { + bufferlist bl; + op->exec("rbd", "migration_get", bl); + } + + int migration_get_finish(bufferlist::const_iterator *it, + cls::rbd::MigrationSpec *migration_spec) { + try { + decode(*migration_spec, *it); + } catch (const buffer::error &err) { + return -EBADMSG; + } + return 0; + } + + int migration_get(librados::IoCtx *ioctx, const std::string &oid, + cls::rbd::MigrationSpec *migration_spec) { + librados::ObjectReadOperation op; + migration_get_start(&op); + + bufferlist out_bl; + int r = ioctx->operate(oid, &op, &out_bl); + if (r < 0) { + return r; + } + + auto iter = out_bl.cbegin(); + r = migration_get_finish(&iter, migration_spec); + if (r < 0) { + return r; + } + return 0; + } + + int migration_remove(librados::IoCtx *ioctx, const std::string &oid) { + librados::ObjectWriteOperation op; + migration_remove(&op); + return ioctx->operate(oid, &op); + } + + void migration_remove(librados::ObjectWriteOperation *op) { + bufferlist bl; + op->exec("rbd", "migration_remove", bl); + } + void mirror_uuid_get_start(librados::ObjectReadOperation *op) { bufferlist bl; op->exec("rbd", "mirror_uuid_get", bl); diff --git a/src/cls/rbd/cls_rbd_client.h b/src/cls/rbd/cls_rbd_client.h index e89c6e90643..c96900e9864 100644 --- a/src/cls/rbd/cls_rbd_client.h +++ b/src/cls/rbd/cls_rbd_client.h @@ -258,6 +258,24 @@ namespace librbd { snapid_t snap_id, cls::rbd::ChildImageSpecs *child_images); + int migration_set(librados::IoCtx *ioctx, const std::string &oid, + const cls::rbd::MigrationSpec &migration_spec); + void migration_set(librados::ObjectWriteOperation *op, + const cls::rbd::MigrationSpec &migration_spec); + int migration_set_state(librados::IoCtx *ioctx, const std::string &oid, + cls::rbd::MigrationState state, + const std::string &description); + void migration_set_state(librados::ObjectWriteOperation *op, + cls::rbd::MigrationState state, + const std::string &description); + void migration_get_start(librados::ObjectReadOperation *op); + int migration_get_finish(bufferlist::const_iterator *it, + cls::rbd::MigrationSpec *migration_spec); + int migration_get(librados::IoCtx *ioctx, const std::string &oid, + cls::rbd::MigrationSpec *migration_spec); + int migration_remove(librados::IoCtx *ioctx, const std::string &oid); + void migration_remove(librados::ObjectWriteOperation *op); + // operations on rbd_id objects void get_id_start(librados::ObjectReadOperation *op); int get_id_finish(bufferlist::const_iterator *it, std::string *id); diff --git a/src/cls/rbd/cls_rbd_types.cc b/src/cls/rbd/cls_rbd_types.cc index 2897fe5b0bf..00538666ee1 100644 --- a/src/cls/rbd/cls_rbd_types.cc +++ b/src/cls/rbd/cls_rbd_types.cc @@ -732,5 +732,121 @@ std::ostream& operator<<(std::ostream& os, << image_map.mapped_time << "]"; } +std::ostream& operator<<(std::ostream& os, + const MigrationHeaderType& type) { + switch (type) { + case MIGRATION_HEADER_TYPE_SRC: + os << "source"; + break; + case MIGRATION_HEADER_TYPE_DST: + os << "destination"; + break; + default: + os << "unknown (" << static_cast(type) << ")"; + break; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, + const MigrationState& migration_state) { + switch (migration_state) { + case MIGRATION_STATE_ERROR: + os << "error"; + break; + case MIGRATION_STATE_PREPARING: + os << "preparing"; + break; + case MIGRATION_STATE_PREPARED: + os << "prepared"; + break; + case MIGRATION_STATE_EXECUTING: + os << "executing"; + break; + case MIGRATION_STATE_EXECUTED: + os << "executed"; + break; + default: + os << "unknown (" << static_cast(migration_state) << ")"; + break; + } + return os; +} + +void MigrationSpec::encode(bufferlist& bl) const { + ENCODE_START(1, 1, bl); + encode(header_type, bl); + encode(pool_id, bl); + encode(image_name, bl); + encode(image_id, bl); + encode(snap_seqs, bl); + encode(overlap, bl); + encode(flatten, bl); + encode(mirroring, bl); + encode(state, bl); + encode(state_description, bl); + ENCODE_FINISH(bl); +} + +void MigrationSpec::decode(bufferlist::const_iterator& bl) { + DECODE_START(1, bl); + decode(header_type, bl); + decode(pool_id, bl); + decode(image_name, bl); + decode(image_id, bl); + decode(snap_seqs, bl); + decode(overlap, bl); + decode(flatten, bl); + decode(mirroring, bl); + decode(state, bl); + decode(state_description, bl); + DECODE_FINISH(bl); +} + +std::ostream& operator<<(std::ostream& os, + const std::map& snap_seqs) { + os << "{"; + size_t count = 0; + for (auto &it : snap_seqs) { + os << (count++ > 0 ? ", " : "") << "(" << it.first << ", " << it.second + << ")"; + } + os << "}"; + return os; +} + +void MigrationSpec::dump(Formatter *f) const { + f->dump_stream("header_type") << header_type; + f->dump_int("pool_id", pool_id); + f->dump_string("image_name", image_name); + f->dump_string("image_id", image_id); + f->dump_stream("snap_seqs") << snap_seqs; + f->dump_unsigned("overlap", overlap); + f->dump_bool("mirroring", mirroring); +} + +void MigrationSpec::generate_test_instances(std::list &o) { + o.push_back(new MigrationSpec()); + o.push_back(new MigrationSpec(MIGRATION_HEADER_TYPE_SRC, 1, "image_name", + "image_id", {{1, 2}}, 123, true, true, + MIGRATION_STATE_PREPARED, "description")); +} + +std::ostream& operator<<(std::ostream& os, + const MigrationSpec& migration_spec) { + os << "[" + << "header_type=" << migration_spec.header_type << ", " + << "pool_id=" << migration_spec.pool_id << ", " + << "image_name=" << migration_spec.image_name << ", " + << "image_id=" << migration_spec.image_id << ", " + << "snap_seqs=" << migration_spec.snap_seqs << ", " + << "overlap=" << migration_spec.overlap << ", " + << "flatten=" << migration_spec.flatten << ", " + << "mirroring=" << migration_spec.mirroring << ", " + << "state=" << migration_spec.state << ", " + << "state_description=" << migration_spec.state_description << "]"; + return os; +} + } // namespace rbd } // namespace cls diff --git a/src/cls/rbd/cls_rbd_types.h b/src/cls/rbd/cls_rbd_types.h index 4007f59c599..7be7d5171a8 100644 --- a/src/cls/rbd/cls_rbd_types.h +++ b/src/cls/rbd/cls_rbd_types.h @@ -587,6 +587,90 @@ std::ostream& operator<<(std::ostream& os, const MirrorImageMap &image_map); WRITE_CLASS_ENCODER(MirrorImageMap); +enum MigrationHeaderType { + MIGRATION_HEADER_TYPE_SRC = 1, + MIGRATION_HEADER_TYPE_DST = 2, +}; + +inline void encode(const MigrationHeaderType &type, bufferlist& bl) { + using ceph::encode; + encode(static_cast(type), bl); +} + +inline void decode(MigrationHeaderType &type, bufferlist::const_iterator& it) { + uint8_t int_type; + using ceph::decode; + decode(int_type, it); + type = static_cast(int_type); +} + +enum MigrationState { + MIGRATION_STATE_ERROR = 0, + MIGRATION_STATE_PREPARING = 1, + MIGRATION_STATE_PREPARED = 2, + MIGRATION_STATE_EXECUTING = 3, + MIGRATION_STATE_EXECUTED = 4, +}; + +inline void encode(const MigrationState &state, bufferlist& bl) { + using ceph::encode; + encode(static_cast(state), bl); +} + +inline void decode(MigrationState &state, bufferlist::const_iterator& it) { + uint8_t int_state; + using ceph::decode; + decode(int_state, it); + state = static_cast(int_state); +} + +std::ostream& operator<<(std::ostream& os, + const MigrationState& migration_state); + +struct MigrationSpec { + MigrationHeaderType header_type = MIGRATION_HEADER_TYPE_SRC; + int64_t pool_id = -1; + std::string image_name; + std::string image_id; + std::map snap_seqs; + uint64_t overlap = 0; + bool flatten = false; + bool mirroring = false; + MigrationState state = MIGRATION_STATE_ERROR; + std::string state_description; + + MigrationSpec() { + } + MigrationSpec(MigrationHeaderType header_type, int64_t pool_id, + const std::string &image_name, const std::string &image_id, + const std::map &snap_seqs, uint64_t overlap, + bool mirroring, bool flatten, MigrationState state, + const std::string &state_description) + : header_type(header_type), pool_id(pool_id), image_name(image_name), + image_id(image_id), snap_seqs(snap_seqs), overlap(overlap), + flatten(flatten), mirroring(mirroring), state(state), + state_description(state_description) { + } + + void encode(bufferlist &bl) const; + void decode(bufferlist::const_iterator& it); + void dump(Formatter *f) const; + + static void generate_test_instances(std::list &o); + + inline bool operator==(const MigrationSpec& ms) const { + return header_type == ms.header_type && pool_id == ms.pool_id && + image_name == ms.image_name && image_id == ms.image_id && + snap_seqs == ms.snap_seqs && overlap == ms.overlap && + flatten == ms.flatten && mirroring == ms.mirroring && state == ms.state && + state_description == ms.state_description; + } +}; + +std::ostream& operator<<(std::ostream& os, const MigrationSpec& migration_spec); + +WRITE_CLASS_ENCODER(MigrationSpec); + } // namespace rbd } // namespace cls diff --git a/src/include/rbd/features.h b/src/include/rbd/features.h index 24ae5fcfc10..89c54a36b21 100644 --- a/src/include/rbd/features.h +++ b/src/include/rbd/features.h @@ -10,6 +10,7 @@ #define RBD_FEATURE_JOURNALING (1ULL<<6) #define RBD_FEATURE_DATA_POOL (1ULL<<7) #define RBD_FEATURE_OPERATIONS (1ULL<<8) +#define RBD_FEATURE_MIGRATING (1ULL<<9) #define RBD_FEATURES_DEFAULT (RBD_FEATURE_LAYERING | \ RBD_FEATURE_EXCLUSIVE_LOCK | \ @@ -26,6 +27,7 @@ #define RBD_FEATURE_NAME_JOURNALING "journaling" #define RBD_FEATURE_NAME_DATA_POOL "data-pool" #define RBD_FEATURE_NAME_OPERATIONS "operations" +#define RBD_FEATURE_NAME_MIGRATING "migrating" /// features that make an image inaccessible for read or write by /// clients that don't understand them @@ -40,7 +42,8 @@ RBD_FEATURE_FAST_DIFF | \ RBD_FEATURE_DEEP_FLATTEN | \ RBD_FEATURE_JOURNALING | \ - RBD_FEATURE_OPERATIONS) + RBD_FEATURE_OPERATIONS | \ + RBD_FEATURE_MIGRATING) #define RBD_FEATURES_ALL (RBD_FEATURE_LAYERING | \ RBD_FEATURE_STRIPINGV2 | \ @@ -50,7 +53,8 @@ RBD_FEATURE_DEEP_FLATTEN | \ RBD_FEATURE_JOURNALING | \ RBD_FEATURE_DATA_POOL | \ - RBD_FEATURE_OPERATIONS) + RBD_FEATURE_OPERATIONS | \ + RBD_FEATURE_MIGRATING) /// features that may be dynamically enabled or disabled #define RBD_FEATURES_MUTABLE (RBD_FEATURE_EXCLUSIVE_LOCK | \ @@ -72,10 +76,12 @@ #define RBD_FEATURES_IMPLICIT_ENABLE (RBD_FEATURE_STRIPINGV2 | \ RBD_FEATURE_DATA_POOL | \ RBD_FEATURE_FAST_DIFF | \ - RBD_FEATURE_OPERATIONS) + RBD_FEATURE_OPERATIONS | \ + RBD_FEATURE_MIGRATING) /// features that cannot be controlled by the user -#define RBD_FEATURES_INTERNAL (RBD_FEATURE_OPERATIONS) +#define RBD_FEATURES_INTERNAL (RBD_FEATURE_OPERATIONS | \ + RBD_FEATURE_MIGRATING) #define RBD_OPERATION_FEATURE_CLONE_PARENT (1ULL<<0) #define RBD_OPERATION_FEATURE_CLONE_CHILD (1ULL<<1) diff --git a/src/librbd/Features.cc b/src/librbd/Features.cc index 5c8c085839c..15b14931359 100644 --- a/src/librbd/Features.cc +++ b/src/librbd/Features.cc @@ -20,7 +20,7 @@ static const std::map RBD_FEATURE_MAP = { {RBD_FEATURE_NAME_JOURNALING, RBD_FEATURE_JOURNALING}, {RBD_FEATURE_NAME_DATA_POOL, RBD_FEATURE_DATA_POOL}, }; -static_assert((RBD_FEATURE_OPERATIONS << 1) > RBD_FEATURES_ALL, +static_assert((RBD_FEATURE_MIGRATING << 1) > RBD_FEATURES_ALL, "new RBD feature added"); diff --git a/src/test/cls_rbd/test_cls_rbd.cc b/src/test/cls_rbd/test_cls_rbd.cc index 9c888c9e0e7..f8a386d5162 100644 --- a/src/test/cls_rbd/test_cls_rbd.cc +++ b/src/test/cls_rbd/test_cls_rbd.cc @@ -2764,3 +2764,118 @@ TEST_F(TestClsRbd, namespace_methods) ASSERT_EQ(0, namespace_list(&ioctx, name1, 1, &entries)); ASSERT_TRUE(entries.empty()); } + +TEST_F(TestClsRbd, migration) +{ + 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, 0, 22, 0, oid, -1)); + + cls::rbd::MigrationSpec migration_spec(cls::rbd::MIGRATION_HEADER_TYPE_DST, 1, + "name", "id", {}, 0, false, false, + cls::rbd::MIGRATION_STATE_PREPARING, + "123"); + cls::rbd::MigrationSpec read_migration_spec; + + ASSERT_EQ(-EINVAL, migration_get(&ioctx, oid, &read_migration_spec)); + + uint64_t features; + ASSERT_EQ(0, get_features(&ioctx, oid, CEPH_NOSNAP, &features)); + ASSERT_EQ(0U, features); + + ASSERT_EQ(0, migration_set(&ioctx, oid, migration_spec)); + ASSERT_EQ(0, migration_get(&ioctx, oid, &read_migration_spec)); + ASSERT_EQ(migration_spec, read_migration_spec); + + ASSERT_EQ(0, get_features(&ioctx, oid, CEPH_NOSNAP, &features)); + ASSERT_EQ(RBD_FEATURE_MIGRATING, features); + + ASSERT_EQ(-EEXIST, migration_set(&ioctx, oid, migration_spec)); + + migration_spec.state = cls::rbd::MIGRATION_STATE_PREPARED; + migration_spec.state_description = "456"; + ASSERT_EQ(0, migration_set_state(&ioctx, oid, migration_spec.state, + migration_spec.state_description)); + ASSERT_EQ(0, migration_get(&ioctx, oid, &read_migration_spec)); + ASSERT_EQ(migration_spec, read_migration_spec); + + ASSERT_EQ(0, migration_remove(&ioctx, oid)); + + ASSERT_EQ(0, get_features(&ioctx, oid, CEPH_NOSNAP, &features)); + ASSERT_EQ(0U, features); + + ASSERT_EQ(-EINVAL, migration_get(&ioctx, oid, &read_migration_spec)); + ASSERT_EQ(-EINVAL, migration_set_state(&ioctx, oid, migration_spec.state, + migration_spec.state_description)); + + migration_spec.header_type = cls::rbd::MIGRATION_HEADER_TYPE_SRC; + + ASSERT_EQ(0, migration_set(&ioctx, oid, migration_spec)); + + ASSERT_EQ(0, get_features(&ioctx, oid, CEPH_NOSNAP, &features)); + ASSERT_EQ(RBD_FEATURE_MIGRATING, features); + + ASSERT_EQ(0, migration_remove(&ioctx, oid)); + + ASSERT_EQ(-EINVAL, migration_get(&ioctx, oid, &read_migration_spec)); + ASSERT_EQ(0, get_features(&ioctx, oid, CEPH_NOSNAP, &features)); + ASSERT_EQ(0U, features); + + ioctx.close(); +} + +TEST_F(TestClsRbd, migration_v1) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx)); + + bufferlist header; + header.append(RBD_HEADER_TEXT, sizeof(RBD_HEADER_TEXT)); + string oid = get_temp_image_name(); + ASSERT_EQ(0, ioctx.write(oid, header, header.length(), 0)); + + cls::rbd::MigrationSpec migration_spec(cls::rbd::MIGRATION_HEADER_TYPE_DST, 1, + "name", "id", {}, 0, false, false, + cls::rbd::MIGRATION_STATE_PREPARING, + "123"); + cls::rbd::MigrationSpec read_migration_spec; + + ASSERT_EQ(-EINVAL, migration_get(&ioctx, oid, &read_migration_spec)); + + // v1 format image can only be migration source + ASSERT_EQ(-EINVAL, migration_set(&ioctx, oid, migration_spec)); + + migration_spec.header_type = cls::rbd::MIGRATION_HEADER_TYPE_SRC; + ASSERT_EQ(0, migration_set(&ioctx, oid, migration_spec)); + + ASSERT_EQ(0, migration_get(&ioctx, oid, &read_migration_spec)); + ASSERT_EQ(migration_spec, read_migration_spec); + + header.clear(); + ASSERT_EQ(static_cast(sizeof(RBD_MIGRATE_HEADER_TEXT)), + ioctx.read(oid, header, sizeof(RBD_MIGRATE_HEADER_TEXT), 0)); + ASSERT_STREQ(RBD_MIGRATE_HEADER_TEXT, header.c_str()); + + ASSERT_EQ(-EEXIST, migration_set(&ioctx, oid, migration_spec)); + + migration_spec.state = cls::rbd::MIGRATION_STATE_PREPARED; + migration_spec.state_description = "456"; + ASSERT_EQ(0, migration_set_state(&ioctx, oid, migration_spec.state, + migration_spec.state_description)); + ASSERT_EQ(0, migration_get(&ioctx, oid, &read_migration_spec)); + ASSERT_EQ(migration_spec, read_migration_spec); + + ASSERT_EQ(0, migration_remove(&ioctx, oid)); + + ASSERT_EQ(-EINVAL, migration_get(&ioctx, oid, &read_migration_spec)); + ASSERT_EQ(-EINVAL, migration_set_state(&ioctx, oid, migration_spec.state, + migration_spec.state_description)); + header.clear(); + ASSERT_EQ(static_cast(sizeof(RBD_HEADER_TEXT)), + ioctx.read(oid, header, sizeof(RBD_HEADER_TEXT), 0)); + ASSERT_STREQ(RBD_HEADER_TEXT, header.c_str()); + + ioctx.close(); +} diff --git a/src/tools/ceph-dencoder/types.h b/src/tools/ceph-dencoder/types.h index 5bd6e70bcf0..2542c6bb21b 100644 --- a/src/tools/ceph-dencoder/types.h +++ b/src/tools/ceph-dencoder/types.h @@ -465,6 +465,7 @@ TYPE(cls_rbd_snap) #include "cls/rbd/cls_rbd_types.h" TYPE(cls::rbd::ChildImageSpec) +TYPE(cls::rbd::MigrationSpec) TYPE(cls::rbd::MirrorPeer) TYPE(cls::rbd::MirrorImage) TYPE(cls::rbd::MirrorImageMap)