This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new ce306f3689e branch-4.2: [fix](cloud) Skip empty rowsets before 
resolving fs when checking tablet encryption #69694 (#70115)
ce306f3689e is described below

commit ce306f3689ed86833e8f3046d2b5bbe43243c7c7
Author: Xin Liao <[email protected]>
AuthorDate: Thu Nov 5 09:23:12 2026 +0800

    branch-4.2: [fix](cloud) Skip empty rowsets before resolving fs when 
checking tablet encryption #69694 (#70115)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: #69694
    
    Problem Summary:
    
    Backport #69694 to `branch-4.2`.
    
    An encrypted tablet containing an empty version-hole rowset could be
    reported as unencrypted when that rowset has no resolvable storage
    resource. Skip empty rowsets before filesystem resolution and return
    failures as `ResultError`, so errors are not implicitly converted into
    `false`.
    
    The conflict resolution retains this branch's `Rowset::segment_path(0)`
    API. Includes tests for empty rowsets and error propagation.
    
    ### Release note
    
    Fix tablet encryption checks misreporting encrypted tablets with empty
    rowsets and correctly report filesystem-resolution errors.
    
    ### Check List (For Author)
    
    - Test: clang-format 16 and `git diff --check` passed. Test constructor
    and rowset APIs were checked against `branch-4.2`. BE unit tests were
    not run locally because the local Xcode/toolchain environment blocked
    the test entry point. CI `run buildall` is requested separately after PR
    creation.
    - Behavior changed: Yes. Empty rowsets are skipped and inspection errors
    are surfaced as errors.
    - Does this need documentation: No.
---
 .../http/action/check_encryption_action.cpp        | 12 ++-
 .../service/http/action/check_encryption_action.h  |  6 ++
 .../service/http/check_encryption_action_test.cpp  | 92 ++++++++++++++++++++++
 3 files changed, 106 insertions(+), 4 deletions(-)

diff --git a/be/src/service/http/action/check_encryption_action.cpp 
b/be/src/service/http/action/check_encryption_action.cpp
index 1c11fc25b64..3b66f104e2c 100644
--- a/be/src/service/http/action/check_encryption_action.cpp
+++ b/be/src/service/http/action/check_encryption_action.cpp
@@ -74,6 +74,11 @@ Result<bool> is_tablet_encrypted(const BaseTabletSPtr& 
tablet) {
             rs_meta->end_version() == 1) {
             return;
         }
+        // Must be checked before resolving the fs: an empty rowset has no 
file to check, and a
+        // version hole rowset may carry a resource id that resolves to no 
storage resource.
+        if (rs->num_segments() == 0) {
+            return;
+        }
         // Must not be `physical_fs()`: packed rowsets keep their segments as 
slices inside a
         // shared object, and only the `PackedFileSystem` wrapper can resolve 
a segment path to
         // that slice. `fs()` is not an option either, since it decrypts and 
would hide the
@@ -85,9 +90,6 @@ Result<bool> is_tablet_encrypted(const BaseTabletSPtr& 
tablet) {
             return;
         }
 
-        if (rs->num_segments() == 0) {
-            return;
-        }
         auto maybe_seg_path = rs->segment_path(0);
         if (!maybe_seg_path) {
             st = std::move(maybe_seg_path.error());
@@ -141,7 +143,9 @@ Result<bool> is_tablet_encrypted(const BaseTabletSPtr& 
tablet) {
     if (st) {
         return is_encrypted;
     }
-    return st;
+    // Must be wrapped: `Status` converts to `bool`, so a bare `return st;` 
would report the
+    // error as "not encrypted".
+    return ResultError(std::move(st));
 }
 
 Result<std::string> get_last_encrypt_footer(const BaseTabletSPtr& tablet) {
diff --git a/be/src/service/http/action/check_encryption_action.h 
b/be/src/service/http/action/check_encryption_action.h
index ae4ce978b16..32912ce07c4 100644
--- a/be/src/service/http/action/check_encryption_action.h
+++ b/be/src/service/http/action/check_encryption_action.h
@@ -18,12 +18,18 @@
 #include <gen_cpp/FrontendService_types.h>
 
 #include "cloud/cloud_tablet_mgr.h"
+#include "common/status.h"
 #include "runtime/exec_env.h"
 #include "service/http/http_handler_with_auth.h"
 #include "service/http/http_request.h"
+#include "storage/tablet/tablet_fwd.h"
 #include "storage/tablet/tablet_manager.h"
 namespace doris {
 
+// Returns whether every non-empty rowset of `tablet` ends with the encryption 
magic code, or an
+// error if any file could not be checked.
+Result<bool> is_tablet_encrypted(const BaseTabletSPtr& tablet);
+
 class CheckEncryptionAction : public HttpHandlerWithAuth {
 public:
     explicit CheckEncryptionAction(ExecEnv* exec_env, TPrivilegeHier::type 
hier,
diff --git a/be/test/service/http/check_encryption_action_test.cpp 
b/be/test/service/http/check_encryption_action_test.cpp
new file mode 100644
index 00000000000..2767b27ce63
--- /dev/null
+++ b/be/test/service/http/check_encryption_action_test.cpp
@@ -0,0 +1,92 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "service/http/action/check_encryption_action.h"
+
+#include <gen_cpp/olap_file.pb.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include "cloud/cloud_storage_engine.h"
+#include "cloud/cloud_tablet.h"
+#include "storage/rowset/rowset.h"
+#include "storage/rowset/rowset_factory.h"
+#include "storage/rowset/rowset_meta.h"
+#include "storage/tablet/tablet_meta.h"
+
+namespace doris {
+
+class CheckEncryptionActionTest : public testing::Test {
+public:
+    CheckEncryptionActionTest() : _engine(CloudStorageEngine(EngineOptions 
{})) {}
+
+    void SetUp() override {
+        auto tablet_meta = std::make_shared<TabletMeta>(
+                1, 2, 15673, 15674, 4, 5, TTabletSchema(), 6,
+                std::unordered_map<uint32_t, uint32_t> {{7, 8}}, UniqueId(9, 
10),
+                TTabletType::TABLET_TYPE_DISK, TCompressionType::LZ4F, 0, 
false, std::nullopt,
+                "size_based", 1024, 1000, 3600, 5, 1, 
TInvertedIndexFileStorageFormat::V2,
+                TEncryptionAlgorithm::AES256);
+        ASSERT_EQ(tablet_meta->encryption_algorithm(), 
EncryptionAlgorithmPB::AES_256_CTR);
+        _tablet = std::make_shared<CloudTablet>(_engine, 
std::move(tablet_meta));
+    }
+
+protected:
+    void add_rowset(Version version, int64_t num_segments, const std::string& 
resource_id) {
+        auto rs_meta = std::make_shared<RowsetMeta>();
+        rs_meta->set_rowset_type(BETA_ROWSET);
+        rs_meta->set_version(version);
+        rs_meta->set_rowset_id(_engine.next_rowset_id());
+        rs_meta->set_num_segments(num_segments);
+        // A set resource id makes the rowset remote, so its fs comes from the 
storage resource.
+        rs_meta->set_resource_id(resource_id);
+        RowsetSharedPtr rowset;
+        ASSERT_TRUE(RowsetFactory::create_rowset(nullptr, "", rs_meta, 
&rowset).ok());
+        std::unique_lock wlock(_tablet->get_header_lock());
+        _tablet->add_rowsets({rowset}, false, wlock, false);
+    }
+
+    CloudStorageEngine _engine;
+    std::shared_ptr<CloudTablet> _tablet;
+};
+
+// A version hole is filled with an empty rowset copied from the previous 
rowset's meta. In cloud
+// mode that may be the initial [0-1] rowset, whose resource id is empty, so 
no fs can be resolved
+// for it. It has no file, so it must be skipped rather than fail the whole 
check.
+TEST_F(CheckEncryptionActionTest, EmptyRowsetWithoutStorageResourceIsSkipped) {
+    add_rowset(Version(2, 2), 0, "");
+
+    auto res = is_tablet_encrypted(_tablet);
+    ASSERT_TRUE(res.has_value()) << res.error();
+    EXPECT_TRUE(res.value());
+}
+
+// A rowset whose files cannot be checked must surface the error, not be 
reported as unencrypted.
+TEST_F(CheckEncryptionActionTest, UncheckableRowsetReportsError) {
+    add_rowset(Version(2, 2), 1, "not_exist_resource");
+
+    auto res = is_tablet_encrypted(_tablet);
+    ASSERT_FALSE(res.has_value()) << "unexpected value: " << res.value();
+    EXPECT_NE(res.error().to_string().find("failed to get fs for rowset"), 
std::string::npos)
+            << res.error();
+}
+
+} // namespace doris


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to