From 2279ae08f1039a54161f65c951a914199f898ecf Mon Sep 17 00:00:00 2001 From: Ilya Dryomov Date: Thu, 20 Oct 2022 17:25:46 +0200 Subject: [PATCH] librbd: remap resize target size if encryption is loaded When encryption is loaded, rbd_get_size() and Image::size() return "effective" size, but rbd_resize() and Image::resize() continue to take raw size. The user has to constantly keep these domains in mind. Saying that resize must be done without encryption loaded is not an answer because shrinking a clone that has snapshots involves copying up objects in the affected range (identical to flattening). In addition, even if a clone doesn't have snapshots, shrinking it to a size that isn't an object boundary is going to involve a copyup for the victim object as well. To avoid subtle data corruption on shrink, treat resize operation the same as flatten operation (including on the CLI). Signed-off-by: Ilya Dryomov --- doc/man/8/rbd.rst | 2 +- src/librbd/Operations.cc | 27 ++++++---- src/test/cli/rbd/help.t | 23 ++++---- src/test/librbd/test_librbd.cc | 99 ++++++++++++++++++++++++++++++++++ src/tools/rbd/action/Resize.cc | 17 ++++++ 5 files changed, 148 insertions(+), 20 deletions(-) diff --git a/doc/man/8/rbd.rst b/doc/man/8/rbd.rst index 2427df67f66..59e34620975 100644 --- a/doc/man/8/rbd.rst +++ b/doc/man/8/rbd.rst @@ -606,7 +606,7 @@ Commands Initialize pool for use by RBD. Newly created pools must initialized prior to use. -:command:`resize` (-s | --size *size-in-M/G/T*) [--allow-shrink] *image-spec* +:command:`resize` (-s | --size *size-in-M/G/T*) [--allow-shrink] [--encryption-format *encryption-format* --encryption-passphrase-file *passphrase-file*]... *image-spec* Resize rbd image. The size parameter also needs to be specified. The --allow-shrink option lets the size be reduced. diff --git a/src/librbd/Operations.cc b/src/librbd/Operations.cc index 30bb7efb3a6..bab2ef4d18c 100644 --- a/src/librbd/Operations.cc +++ b/src/librbd/Operations.cc @@ -17,6 +17,7 @@ #include "librbd/Utils.h" #include "librbd/api/Config.h" #include "librbd/asio/ContextWQ.h" +#include "librbd/io/Utils.h" #include "librbd/journal/DisabledPolicy.h" #include "librbd/journal/StandardPolicy.h" #include "librbd/operation/DisableFeaturesRequest.h" @@ -742,9 +743,12 @@ int Operations::resize(uint64_t size, bool allow_shrink, ProgressContext& pro CephContext *cct = m_image_ctx.cct; m_image_ctx.image_lock.lock_shared(); - ldout(cct, 5) << this << " " << __func__ << ": " - << "size=" << m_image_ctx.size << ", " - << "new_size=" << size << dendl; + uint64_t raw_size = io::util::area_to_raw_offset(m_image_ctx, size, + io::ImageArea::DATA); + ldout(cct, 5) << this << " " << __func__ + << ": size=" << size + << " raw_size=" << m_image_ctx.size + << " new_raw_size=" << raw_size << dendl; m_image_ctx.image_lock.unlock_shared(); int r = m_image_ctx.state->refresh_if_required(); @@ -753,7 +757,7 @@ int Operations::resize(uint64_t size, bool allow_shrink, ProgressContext& pro } if (m_image_ctx.test_features(RBD_FEATURE_OBJECT_MAP) && - !ObjectMap<>::is_compatible(m_image_ctx.layout, size)) { + !ObjectMap<>::is_compatible(m_image_ctx.layout, raw_size)) { lderr(cct) << "New size not compatible with object map" << dendl; return -EINVAL; } @@ -783,9 +787,12 @@ void Operations::execute_resize(uint64_t size, bool allow_shrink, ProgressCon CephContext *cct = m_image_ctx.cct; m_image_ctx.image_lock.lock_shared(); - ldout(cct, 5) << this << " " << __func__ << ": " - << "size=" << m_image_ctx.size << ", " - << "new_size=" << size << dendl; + uint64_t raw_size = io::util::area_to_raw_offset(m_image_ctx, size, + io::ImageArea::DATA); + ldout(cct, 5) << this << " " << __func__ + << ": size=" << size + << " raw_size=" << m_image_ctx.size + << " new_raw_size=" << raw_size << dendl; if (m_image_ctx.snap_id != CEPH_NOSNAP || m_image_ctx.read_only || m_image_ctx.operations_disabled) { @@ -794,7 +801,7 @@ void Operations::execute_resize(uint64_t size, bool allow_shrink, ProgressCon return; } else if (m_image_ctx.test_features(RBD_FEATURE_OBJECT_MAP, m_image_ctx.image_lock) && - !ObjectMap<>::is_compatible(m_image_ctx.layout, size)) { + !ObjectMap<>::is_compatible(m_image_ctx.layout, raw_size)) { m_image_ctx.image_lock.unlock_shared(); on_finish->complete(-EINVAL); return; @@ -802,8 +809,8 @@ void Operations::execute_resize(uint64_t size, bool allow_shrink, ProgressCon m_image_ctx.image_lock.unlock_shared(); operation::ResizeRequest *req = new operation::ResizeRequest( - m_image_ctx, new C_NotifyUpdate(m_image_ctx, on_finish), size, allow_shrink, - prog_ctx, journal_op_tid, false); + m_image_ctx, new C_NotifyUpdate(m_image_ctx, on_finish), raw_size, + allow_shrink, prog_ctx, journal_op_tid, false); req->send(); } diff --git a/src/test/cli/rbd/help.t b/src/test/cli/rbd/help.t index 34f35aed2ff..5c2a38031a5 100644 --- a/src/test/cli/rbd/help.t +++ b/src/test/cli/rbd/help.t @@ -2235,22 +2235,27 @@ rbd help resize usage: rbd resize [--pool ] [--namespace ] [--image ] --size [--allow-shrink] - [--no-progress] + [--no-progress] [--encryption-format ] + [--encryption-passphrase-file ] Resize (expand or shrink) image. Positional arguments - image specification - (example: [/[/]]) + image specification + (example: + [/[/]]) Optional arguments - -p [ --pool ] arg pool name - --namespace arg namespace name - --image arg image name - -s [ --size ] arg image size (in M/G/T) [default: M] - --allow-shrink permit shrinking - --no-progress disable progress output + -p [ --pool ] arg pool name + --namespace arg namespace name + --image arg image name + -s [ --size ] arg image size (in M/G/T) [default: M] + --allow-shrink permit shrinking + --no-progress disable progress output + --encryption-format arg encryption formats [possible values: luks] + --encryption-passphrase-file arg path to file containing passphrase for + unlocking the image rbd help snap create usage: rbd snap create [--pool ] [--namespace ] diff --git a/src/test/librbd/test_librbd.cc b/src/test/librbd/test_librbd.cc index be9ffabc4c5..543a580568d 100644 --- a/src/test/librbd/test_librbd.cc +++ b/src/test/librbd/test_librbd.cc @@ -2705,6 +2705,105 @@ TEST_F(TestLibRBD, EncryptionLoadBadStripePattern) } } +TEST_F(TestLibRBD, EncryptedResize) +{ + REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING)); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + auto name = get_temp_image_name(); + uint64_t luks2_meta_size = 16 << 20; + uint64_t data_size = 10 << 20; + std::string passphrase = "some passphrase"; + + { + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), + luks2_meta_size + data_size, &order)); + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr)); + + uint64_t size; + ASSERT_EQ(0, image.size(&size)); + ASSERT_EQ(luks2_meta_size + data_size, size); + + librbd::encryption_luks2_format_options_t opts = { + RBD_ENCRYPTION_ALGORITHM_AES256, passphrase}; + ASSERT_EQ(0, image.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS2, &opts, + sizeof(opts))); + ASSERT_EQ(0, image.size(&size)); + ASSERT_EQ(data_size, size); + ASSERT_EQ(0, image.resize(data_size * 3)); + ASSERT_EQ(0, image.size(&size)); + ASSERT_EQ(data_size * 3, size); + } + + { + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr)); + + uint64_t size; + ASSERT_EQ(0, image.size(&size)); + ASSERT_EQ(luks2_meta_size + data_size * 3, size); + + librbd::encryption_luks_format_options_t opts = {passphrase}; + ASSERT_EQ(0, image.encryption_load(RBD_ENCRYPTION_FORMAT_LUKS, &opts, + sizeof(opts))); + ASSERT_EQ(0, image.size(&size)); + ASSERT_EQ(data_size * 3, size); + ASSERT_EQ(0, image.resize(data_size / 2)); + ASSERT_EQ(0, image.size(&size)); + ASSERT_EQ(data_size / 2, size); + } + + { + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr)); + + uint64_t size; + ASSERT_EQ(0, image.size(&size)); + ASSERT_EQ(luks2_meta_size + data_size / 2, size); + + librbd::encryption_luks_format_options_t opts = {passphrase}; + ASSERT_EQ(0, image.encryption_load(RBD_ENCRYPTION_FORMAT_LUKS, &opts, + sizeof(opts))); + ASSERT_EQ(0, image.size(&size)); + ASSERT_EQ(data_size / 2, size); + ASSERT_EQ(0, image.resize(0)); + ASSERT_EQ(0, image.size(&size)); + ASSERT_EQ(0, size); + } + + { + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr)); + + uint64_t size; + ASSERT_EQ(0, image.size(&size)); + ASSERT_EQ(luks2_meta_size, size); + + librbd::encryption_luks_format_options_t opts = {passphrase}; + ASSERT_EQ(0, image.encryption_load(RBD_ENCRYPTION_FORMAT_LUKS, &opts, + sizeof(opts))); + ASSERT_EQ(0, image.size(&size)); + ASSERT_EQ(0, size); + ASSERT_EQ(0, image.resize(data_size)); + ASSERT_EQ(0, image.size(&size)); + ASSERT_EQ(data_size, size); + } + + { + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr)); + + uint64_t size; + ASSERT_EQ(0, image.size(&size)); + ASSERT_EQ(luks2_meta_size + data_size, size); + } +} + #endif TEST_F(TestLibRBD, TestIOWithIOHint) diff --git a/src/tools/rbd/action/Resize.cc b/src/tools/rbd/action/Resize.cc index 60c16429b79..79fbbd127d7 100644 --- a/src/tools/rbd/action/Resize.cc +++ b/src/tools/rbd/action/Resize.cc @@ -34,6 +34,7 @@ void get_arguments(po::options_description *positional, options->add_options() ("allow-shrink", po::bool_switch(), "permit shrinking"); at::add_no_progress_option(options); + at::add_encryption_options(options); } int execute(const po::variables_map &vm, @@ -57,6 +58,12 @@ int execute(const po::variables_map &vm, return r; } + utils::EncryptionOptions encryption_options; + r = utils::get_encryption_options(vm, &encryption_options); + if (r < 0) { + return r; + } + librados::Rados rados; librados::IoCtx io_ctx; librbd::Image image; @@ -66,6 +73,16 @@ int execute(const po::variables_map &vm, return r; } + if (!encryption_options.specs.empty()) { + r = image.encryption_load2(encryption_options.specs.data(), + encryption_options.specs.size()); + if (r < 0) { + std::cerr << "rbd: encryption load failed: " << cpp_strerror(r) + << std::endl; + return r; + } + } + librbd::image_info_t info; r = image.stat(info, sizeof(info)); if (r < 0) { -- 2.39.5