| // 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. | |
| //#define private public | |
| #include "meta-service/meta_service.h" | |
| //#undef private | |
| #include <brpc/controller.h> | |
| #include <bvar/window.h> | |
| #include <gen_cpp/cloud.pb.h> | |
| #include <gen_cpp/olap_file.pb.h> | |
| #include <gtest/gtest.h> | |
| #include <atomic> | |
| #include <condition_variable> | |
| #include <cstdint> | |
| #include <memory> | |
| #include <optional> | |
| #include <random> | |
| #include <thread> | |
| #include "common/config.h" | |
| #include "common/logging.h" | |
| #include "common/util.h" | |
| #include "cpp/sync_point.h" | |
| #include "meta-store/keys.h" | |
| #include "meta-store/mem_txn_kv.h" | |
| #include "meta-store/txn_kv_error.h" | |
| #include "rate-limiter/rate_limiter.h" | |
| #include "resource-manager/resource_manager.h" | |
| int main(int argc, char** argv) { | |
| const std::string conf_file = "doris_cloud.conf"; | |
| if (!doris::cloud::config::init(conf_file.c_str(), true)) { | |
| std::cerr << "failed to init config file, conf=" << conf_file << std::endl; | |
| return -1; | |
| } | |
| if (!doris::cloud::init_glog("resource_test")) { | |
| std::cerr << "failed to init glog" << std::endl; | |
| return -1; | |
| } | |
| ::testing::InitGoogleTest(&argc, argv); | |
| return RUN_ALL_TESTS(); | |
| } | |
| namespace doris::cloud { | |
| extern std::string get_instance_id(const std::shared_ptr<ResourceManager>& rc_mgr, | |
| const std::string& cloud_unique_id); | |
| static std::shared_ptr<TxnKv> create_txn_kv() { | |
| // MemKv | |
| int ret = 0; | |
| auto txn_kv = std::dynamic_pointer_cast<TxnKv>(std::make_shared<MemTxnKv>()); | |
| if (txn_kv != nullptr) { | |
| ret = txn_kv->init(); | |
| [&] { ASSERT_EQ(ret, 0); }(); | |
| } | |
| [&] { ASSERT_NE(txn_kv.get(), nullptr); }(); | |
| std::unique_ptr<Transaction> txn; | |
| EXPECT_EQ(txn_kv->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| txn->remove("\x00", "\xfe"); // This is dangerous if the fdb is not correctly set | |
| EXPECT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| return txn_kv; | |
| } | |
| std::unique_ptr<MetaServiceProxy> get_meta_service(std::shared_ptr<TxnKv> txn_kv = {}) { | |
| if (!txn_kv) { | |
| txn_kv = create_txn_kv(); | |
| } | |
| auto rs = std::make_shared<ResourceManager>(txn_kv); | |
| EXPECT_EQ(rs->init(), 0); | |
| auto rl = std::make_shared<RateLimiter>(); | |
| auto snapshot = std::make_shared<SnapshotManager>(txn_kv); | |
| auto meta_service = std::make_unique<MetaServiceImpl>(txn_kv, rs, rl, snapshot); | |
| return std::make_unique<MetaServiceProxy>(std::move(meta_service)); | |
| } | |
| static void create_args_to_add(std::vector<NodeInfo>* to_add, std::vector<NodeInfo>* to_del, | |
| bool is_host = false) { | |
| to_add->clear(); | |
| to_del->clear(); | |
| auto ni_1 = NodeInfo {.role = Role::COMPUTE_NODE, | |
| .instance_id = "test-resource-instance", | |
| .cluster_name = "cluster_name_1", | |
| .cluster_id = "cluster_id_1", | |
| .node_info = NodeInfoPB {}}; | |
| is_host ? ni_1.node_info.set_host("host1") : ni_1.node_info.set_ip("127.0.0.1"); | |
| ni_1.node_info.set_cloud_unique_id("test_cloud_unique_id_1"); | |
| ni_1.node_info.set_heartbeat_port(9999); | |
| to_add->push_back(ni_1); | |
| auto ni_2 = NodeInfo {.role = Role::COMPUTE_NODE, | |
| .instance_id = "test-resource-instance", | |
| .cluster_name = "cluster_name_1", | |
| .cluster_id = "cluster_id_1", | |
| .node_info = NodeInfoPB {}}; | |
| is_host ? ni_2.node_info.set_host("host2") : ni_2.node_info.set_ip("127.0.0.2"); | |
| ni_2.node_info.set_cloud_unique_id("test_cloud_unique_id_1"); | |
| ni_2.node_info.set_heartbeat_port(9999); | |
| to_add->push_back(ni_2); | |
| auto ni_3 = NodeInfo {.role = Role::COMPUTE_NODE, | |
| .instance_id = "test-resource-instance", | |
| .cluster_name = "cluster_name_2", | |
| .cluster_id = "cluster_id_2", | |
| .node_info = NodeInfoPB {}}; | |
| is_host ? ni_3.node_info.set_host("host3") : ni_3.node_info.set_ip("127.0.0.3"); | |
| ni_3.node_info.set_cloud_unique_id("test_cloud_unique_id_2"); | |
| ni_3.node_info.set_heartbeat_port(9999); | |
| to_add->push_back(ni_3); | |
| } | |
| static void create_args_to_del(std::vector<NodeInfo>* to_add, std::vector<NodeInfo>* to_del, | |
| bool is_host = false) { | |
| to_add->clear(); | |
| to_del->clear(); | |
| auto ni_1 = NodeInfo {.role = Role::COMPUTE_NODE, | |
| .instance_id = "test-resource-instance", | |
| .cluster_name = "cluster_name_1", | |
| .cluster_id = "cluster_id_1", | |
| .node_info = NodeInfoPB {}}; | |
| is_host ? ni_1.node_info.set_host("host2") : ni_1.node_info.set_ip("127.0.0.2"); | |
| ni_1.node_info.set_cloud_unique_id("test_cloud_unique_id_1"); | |
| ni_1.node_info.set_heartbeat_port(9999); | |
| to_del->push_back(ni_1); | |
| } | |
| static void get_instance_info(MetaServiceProxy* ms, InstanceInfoPB* instance, | |
| std::string_view instance_id = "test-resource-instance") { | |
| InstanceKeyInfo key_info {instance_id}; | |
| std::string key; | |
| std::string val; | |
| instance_key(key_info, &key); | |
| std::unique_ptr<Transaction> txn; | |
| EXPECT_EQ(ms->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| EXPECT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK); | |
| instance->ParseFromString(val); | |
| } | |
| static void create_instance(MetaServiceProxy* ms, const std::string& instance_id) { | |
| brpc::Controller ctrl; | |
| CreateInstanceRequest req; | |
| req.set_instance_id(instance_id); | |
| req.set_user_id("test_user"); | |
| req.set_name(instance_id + "name"); | |
| ObjectStoreInfoPB obj; | |
| obj.set_ak("123"); | |
| obj.set_sk("321"); | |
| obj.set_bucket("456"); | |
| obj.set_prefix("654"); | |
| obj.set_endpoint("789"); | |
| obj.set_region("987"); | |
| obj.set_external_endpoint("888"); | |
| obj.set_provider(ObjectStoreInfoPB::BOS); | |
| req.mutable_obj_info()->CopyFrom(obj); | |
| CreateInstanceResponse resp; | |
| ms->create_instance(&ctrl, &req, &resp, brpc::DoNothing()); | |
| ASSERT_FALSE(ctrl.Failed()); | |
| ASSERT_EQ(resp.status().code(), MetaServiceCode::OK); | |
| } | |
| static void create_cluster(MetaServiceProxy* ms, const std::string& instance_id, | |
| const std::string& cluster_id, const std::string& cluster_name, | |
| ClusterPB_Type type) { | |
| brpc::Controller cntl; | |
| AlterClusterRequest req; | |
| req.set_instance_id(instance_id); | |
| req.mutable_cluster()->set_cluster_id(cluster_id); | |
| req.mutable_cluster()->set_cluster_name(cluster_name); | |
| req.mutable_cluster()->set_type(type); | |
| if (type == ClusterPB::SQL) { | |
| auto* node = req.mutable_cluster()->add_nodes(); | |
| node->set_node_type(NodeInfoPB::FE_MASTER); | |
| node->set_ip("127.0.0.1"); | |
| node->set_edit_log_port(10000); | |
| node->set_name("sql_node"); | |
| } else { | |
| auto* node = req.mutable_cluster()->add_nodes(); | |
| node->set_ip("127.0.0.1"); | |
| node->set_heartbeat_port(10000); | |
| node->set_name("sql_node"); | |
| } | |
| req.set_op(AlterClusterRequest::ADD_CLUSTER); | |
| AlterClusterResponse res; | |
| ms->alter_cluster(&cntl, &req, &res, brpc::DoNothing()); | |
| ASSERT_EQ(res.status().code(), MetaServiceCode::OK); | |
| } | |
| static void drop_cluster(MetaServiceProxy* ms, const std::string& instance_id, | |
| const std::string& cluster_id) { | |
| brpc::Controller cntl; | |
| AlterClusterRequest req; | |
| req.set_instance_id(instance_id); | |
| req.mutable_cluster()->set_cluster_id(cluster_id); | |
| req.set_op(AlterClusterRequest::DROP_CLUSTER); | |
| AlterClusterResponse res; | |
| ms->alter_cluster(&cntl, &req, &res, brpc::DoNothing()); | |
| ASSERT_EQ(res.status().code(), MetaServiceCode::OK); | |
| } | |
| // test cluster's node addr use ip | |
| TEST(ResourceTest, ModifyNodesIpTest) { | |
| auto meta_service = get_meta_service(); | |
| std::vector<NodeInfo> to_add = {}; | |
| std::vector<NodeInfo> to_del = {}; | |
| auto ins = InstanceInfoPB {}; | |
| create_args_to_add(&to_add, &to_del); | |
| auto sp = SyncPoint::get_instance(); | |
| sp->set_call_back("modify_nodes:get_instance", [&](auto&& args) { | |
| *try_any_cast<TxnErrorCode*>(args[0]) = TxnErrorCode::TXN_OK; | |
| ins.set_instance_id("test-resource-instance"); | |
| ins.set_status(InstanceInfoPB::NORMAL); | |
| auto* c = ins.mutable_clusters()->Add(); | |
| c->set_cluster_name("cluster_name_1"); | |
| c->set_cluster_id("cluster_id_1"); | |
| c->set_type(ClusterPB::COMPUTE); | |
| auto* c1 = ins.mutable_clusters()->Add(); | |
| c1->set_cluster_name("cluster_name_2"); | |
| c1->set_cluster_id("cluster_id_2"); | |
| c1->set_type(ClusterPB::COMPUTE); | |
| *try_any_cast<InstanceInfoPB*>(args[1]) = ins; | |
| }); | |
| sp->enable_processing(); | |
| // test cluster add nodes | |
| auto r = meta_service->resource_mgr()->modify_nodes("test-resource-instance", to_add, to_del); | |
| ASSERT_EQ(r, ""); | |
| InstanceInfoPB instance; | |
| get_instance_info(meta_service.get(), &instance); | |
| std::cout << "after to add = " << proto_to_json(instance) << std::endl; | |
| ASSERT_EQ(instance.clusters().size(), 2); | |
| // after add assert cluster_name_1 has 2 nodes | |
| ASSERT_EQ(instance.clusters(0).nodes().size(), 2); | |
| // after add assert cluster_name_2 has 1 nodes | |
| ASSERT_EQ(instance.clusters(1).nodes().size(), 1); | |
| sp->clear_all_call_backs(); | |
| sp->clear_trace(); | |
| sp->disable_processing(); | |
| sp->set_call_back("modify_nodes:get_instance", [&](auto&& args) { | |
| *try_any_cast<TxnErrorCode*>(args[0]) = TxnErrorCode::TXN_OK; | |
| *try_any_cast<InstanceInfoPB*>(args[1]) = instance; | |
| }); | |
| sp->enable_processing(); | |
| create_args_to_del(&to_add, &to_del); | |
| // test cluster del node | |
| r = meta_service->resource_mgr()->modify_nodes("test-resource-instance", to_add, to_del); | |
| InstanceInfoPB instance1; | |
| get_instance_info(meta_service.get(), &instance1); | |
| ASSERT_EQ(r, ""); | |
| std::cout << "after to del = " << proto_to_json(instance1) << std::endl; | |
| ASSERT_EQ(instance1.clusters().size(), 2); | |
| // after del assert cluster_name_1 has 1 nodes | |
| ASSERT_EQ(instance1.clusters(0).nodes().size(), 1); | |
| // after del assert cluster_name_2 has 1 nodes | |
| ASSERT_EQ(instance1.clusters(1).nodes().size(), 1); | |
| sp->clear_all_call_backs(); | |
| sp->clear_trace(); | |
| sp->disable_processing(); | |
| } | |
| // test cluster's node addr use host | |
| TEST(ResourceTest, ModifyNodesHostTest) { | |
| auto meta_service = get_meta_service(); | |
| std::vector<NodeInfo> to_add = {}; | |
| std::vector<NodeInfo> to_del = {}; | |
| auto ins = InstanceInfoPB {}; | |
| create_args_to_add(&to_add, &to_del, true); | |
| auto sp = SyncPoint::get_instance(); | |
| sp->set_call_back("modify_nodes:get_instance", [&](auto&& args) { | |
| *try_any_cast<TxnErrorCode*>(args[0]) = TxnErrorCode::TXN_OK; | |
| ins.set_instance_id("test-resource-instance"); | |
| ins.set_status(InstanceInfoPB::NORMAL); | |
| auto* c = ins.mutable_clusters()->Add(); | |
| c->set_cluster_name("cluster_name_1"); | |
| c->set_cluster_id("cluster_id_1"); | |
| c->set_type(ClusterPB::COMPUTE); | |
| auto* c1 = ins.mutable_clusters()->Add(); | |
| c1->set_cluster_name("cluster_name_2"); | |
| c1->set_cluster_id("cluster_id_2"); | |
| c1->set_type(ClusterPB::COMPUTE); | |
| *try_any_cast<InstanceInfoPB*>(args[1]) = ins; | |
| }); | |
| sp->enable_processing(); | |
| // test cluster add nodes | |
| auto r = meta_service->resource_mgr()->modify_nodes("test-resource-instance", to_add, to_del); | |
| ASSERT_EQ(r, ""); | |
| InstanceInfoPB instance; | |
| get_instance_info(meta_service.get(), &instance); | |
| std::cout << "after to add = " << proto_to_json(instance) << std::endl; | |
| ASSERT_EQ(instance.clusters().size(), 2); | |
| // after add assert cluster_name_1 has 2 nodes | |
| ASSERT_EQ(instance.clusters(0).nodes().size(), 2); | |
| // after add assert cluster_name_2 has 1 nodes | |
| ASSERT_EQ(instance.clusters(1).nodes().size(), 1); | |
| sp->clear_all_call_backs(); | |
| sp->clear_trace(); | |
| sp->disable_processing(); | |
| sp->set_call_back("modify_nodes:get_instance", [&](auto&& args) { | |
| *try_any_cast<TxnErrorCode*>(args[0]) = TxnErrorCode::TXN_OK; | |
| *try_any_cast<InstanceInfoPB*>(args[1]) = instance; | |
| }); | |
| sp->enable_processing(); | |
| create_args_to_del(&to_add, &to_del, true); | |
| r = meta_service->resource_mgr()->modify_nodes("test-resource-instance", to_add, to_del); | |
| InstanceInfoPB instance1; | |
| get_instance_info(meta_service.get(), &instance1); | |
| ASSERT_EQ(r, ""); | |
| std::cout << "after to del = " << proto_to_json(instance1) << std::endl; | |
| ASSERT_EQ(instance1.clusters().size(), 2); | |
| // after del assert cluster_name_1 has 1 nodes | |
| ASSERT_EQ(instance1.clusters(0).nodes().size(), 1); | |
| // after del assert cluster_name_2 has 1 nodes | |
| ASSERT_EQ(instance1.clusters(1).nodes().size(), 1); | |
| sp->clear_all_call_backs(); | |
| sp->clear_trace(); | |
| sp->disable_processing(); | |
| } | |
| // test restart meta service | |
| TEST(ResourceTest, RestartResourceManager) { | |
| auto sp = SyncPoint::get_instance(); | |
| sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* ret = try_any_cast<int*>(args[0]); | |
| *ret = 0; | |
| auto* key = try_any_cast<std::string*>(args[1]); | |
| *key = "test"; | |
| auto* key_id = try_any_cast<int64_t*>(args[2]); | |
| *key_id = 1; | |
| }); | |
| sp->set_call_back("decrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* key = try_any_cast<std::string*>(args[0]); | |
| *key = "test"; | |
| auto* ret = try_any_cast<int*>(args[1]); | |
| *ret = 0; | |
| }); | |
| sp->enable_processing(); | |
| auto txn_kv = create_txn_kv(); | |
| { | |
| auto meta_service = get_meta_service(txn_kv); | |
| create_instance(meta_service.get(), "test_instance_id"); | |
| create_instance(meta_service.get(), "test_instance_id_2"); | |
| create_cluster(meta_service.get(), "test_instance_id", "cluster_id", "cluster_name", | |
| ClusterPB::SQL); | |
| ASSERT_FALSE(meta_service->resource_mgr()->is_version_read_enabled("test_instance_id_2")); | |
| // instance_2 enable multi version status | |
| std::unique_ptr<Transaction> txn; | |
| std::string instance_info_value; | |
| InstanceInfoPB instance_info; | |
| std::string key = instance_key("test_instance_id_2"); | |
| ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| ASSERT_EQ(txn->get(key, &instance_info_value), TxnErrorCode::TXN_OK); | |
| ASSERT_TRUE(instance_info.ParseFromString(instance_info_value)); | |
| instance_info.set_multi_version_status(MultiVersionStatus::MULTI_VERSION_ENABLED); | |
| txn->put(key, instance_info.SerializeAsString()); | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| auto [code, msg] = meta_service->resource_mgr()->refresh_instance("test_instance_id_2"); | |
| ASSERT_EQ(code, MetaServiceCode::OK) << msg; | |
| ASSERT_TRUE(meta_service->resource_mgr()->is_version_read_enabled("test_instance_id_2")); | |
| } | |
| { | |
| auto meta_service = get_meta_service(txn_kv); | |
| { | |
| InstanceInfoPB info; | |
| auto [code, msg] = | |
| meta_service->resource_mgr()->get_instance(nullptr, "test_instance_id", &info); | |
| ASSERT_EQ(code, TxnErrorCode::TXN_OK) << msg; | |
| ASSERT_EQ(info.name(), "test_instance_idname"); | |
| } | |
| { | |
| InstanceInfoPB info; | |
| auto [code, msg] = meta_service->resource_mgr()->get_instance( | |
| nullptr, "test_instance_id_2", &info); | |
| ASSERT_EQ(code, TxnErrorCode::TXN_OK) << msg; | |
| ASSERT_EQ(info.name(), "test_instance_id_2name"); | |
| } | |
| ASSERT_TRUE(meta_service->resource_mgr()->is_version_read_enabled("test_instance_id_2")); | |
| } | |
| sp->disable_processing(); | |
| sp->clear_all_call_backs(); | |
| } | |
| TEST(ResourceTest, RefreshDeletedInstanceClearsRuntimeCaches) { | |
| auto txn_kv = create_txn_kv(); | |
| ResourceManager resource_mgr(txn_kv); | |
| ASSERT_EQ(resource_mgr.init(), 0); | |
| const std::string instance_id = "deleted_cache_instance"; | |
| const std::string cloud_unique_id = "deleted_cache_cloud_unique_id"; | |
| InstanceInfoPB instance; | |
| instance.set_instance_id(instance_id); | |
| instance.set_status(InstanceInfoPB::NORMAL); | |
| instance.set_multi_version_status(MultiVersionStatus::MULTI_VERSION_ENABLED); | |
| instance.set_source_instance_id("source_instance"); | |
| instance.set_source_snapshot_id("00000000000000000000"); | |
| auto* cluster = instance.add_clusters(); | |
| cluster->set_type(ClusterPB::COMPUTE); | |
| cluster->set_cluster_id("cluster_id"); | |
| cluster->set_cluster_name("cluster_name"); | |
| auto* node = cluster->add_nodes(); | |
| node->set_cloud_unique_id(cloud_unique_id); | |
| node->set_ip("127.0.0.1"); | |
| node->set_heartbeat_port(10000); | |
| resource_mgr.refresh_instance(instance_id, instance); | |
| std::vector<NodeInfo> nodes; | |
| ASSERT_EQ(resource_mgr.get_node(cloud_unique_id, &nodes), ""); | |
| ASSERT_EQ(nodes.size(), 1); | |
| ASSERT_TRUE(resource_mgr.is_version_read_enabled(instance_id)); | |
| std::string source_instance_id; | |
| Versionstamp source_snapshot_version; | |
| ASSERT_TRUE(resource_mgr.get_source_snapshot_info(instance_id, &source_instance_id, | |
| &source_snapshot_version)); | |
| instance.set_status(InstanceInfoPB::DELETED); | |
| resource_mgr.refresh_instance(instance_id, instance); | |
| nodes.clear(); | |
| EXPECT_EQ(resource_mgr.get_node(cloud_unique_id, &nodes), ""); | |
| ASSERT_TRUE(!nodes.empty()); | |
| EXPECT_TRUE(resource_mgr.is_version_read_enabled(instance_id)); | |
| EXPECT_TRUE(resource_mgr.get_source_snapshot_info(instance_id, &source_instance_id, | |
| &source_snapshot_version)); | |
| } | |
| // test add/drop cluster | |
| TEST(ResourceTest, AddDropCluster) { | |
| auto sp = SyncPoint::get_instance(); | |
| sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* ret = try_any_cast<int*>(args[0]); | |
| *ret = 0; | |
| auto* key = try_any_cast<std::string*>(args[1]); | |
| *key = "selectdbselectdbselectdbselectdb"; | |
| auto* key_id = try_any_cast<int64_t*>(args[2]); | |
| *key_id = 1; | |
| }); | |
| sp->set_call_back("resource_manager::set_safe_drop_time", | |
| [](auto&& args) { *try_any_cast<int64_t*>(args[0]) = -1; }); | |
| sp->set_call_back("decrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* key = try_any_cast<std::string*>(args[0]); | |
| *key = "test"; | |
| auto* ret = try_any_cast<int*>(args[1]); | |
| *ret = 0; | |
| }); | |
| sp->set_call_back("resource_manager::set_safe_drop_time", | |
| [](auto&& args) { *try_any_cast<int64_t*>(args[0]) = -1; }); | |
| sp->enable_processing(); | |
| auto meta_service = get_meta_service(); | |
| create_instance(meta_service.get(), "test_instance_id"); | |
| create_cluster(meta_service.get(), "test_instance_id", "sql_id", "sql_cluster", ClusterPB::SQL); | |
| create_cluster(meta_service.get(), "test_instance_id", "compute_id", "compute_cluster", | |
| ClusterPB::COMPUTE); | |
| InstanceInfoPB info; | |
| get_instance_info(meta_service.get(), &info, "test_instance_id"); | |
| ASSERT_EQ(info.clusters_size(), 2); | |
| drop_cluster(meta_service.get(), "test_instance_id", "sql_id"); | |
| get_instance_info(meta_service.get(), &info, "test_instance_id"); | |
| ASSERT_EQ(info.clusters_size(), 1); | |
| ASSERT_EQ(info.clusters(0).cluster_name(), "compute_cluster"); | |
| ASSERT_EQ(info.clusters(0).cluster_id(), "compute_id"); | |
| sp->disable_processing(); | |
| sp->clear_all_call_backs(); | |
| } | |
| TEST(ResourceTest, InitScanRetry) { | |
| auto sp = SyncPoint::get_instance(); | |
| sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* ret = try_any_cast<int*>(args[0]); | |
| *ret = 0; | |
| auto* key = try_any_cast<std::string*>(args[1]); | |
| *key = "selectdbselectdbselectdbselectdb"; | |
| auto* key_id = try_any_cast<int64_t*>(args[2]); | |
| *key_id = 1; | |
| }); | |
| sp->set_call_back("decrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* key = try_any_cast<std::string*>(args[0]); | |
| *key = "test"; | |
| auto* ret = try_any_cast<int*>(args[1]); | |
| *ret = 0; | |
| }); | |
| sp->enable_processing(); | |
| constexpr size_t NUM_BATCH_SIZE = 100; | |
| sp->set_call_back("ResourceManager:init:limit", | |
| [&](auto&& args) { *try_any_cast<int*>(args[0]) = NUM_BATCH_SIZE; }); | |
| auto txn_kv = create_txn_kv(); | |
| { | |
| auto meta_service = get_meta_service(txn_kv); | |
| for (size_t i = 0; i < NUM_BATCH_SIZE * 2; i++) { | |
| std::string instance_id = "test_instance_id_" + std::to_string(i); | |
| create_instance(meta_service.get(), instance_id); | |
| } | |
| } | |
| { | |
| size_t count = 0; | |
| sp->set_call_back("ResourceManager:init:get_err", [&](auto&& args) { | |
| if (++count == 2) { | |
| *try_any_cast<TxnErrorCode*>(args[0]) = TxnErrorCode::TXN_TOO_OLD; | |
| } | |
| }); | |
| auto meta_service = get_meta_service(txn_kv); | |
| ASSERT_GT(count, 1) << count; | |
| for (size_t i = 0; i < NUM_BATCH_SIZE * 2; i++) { | |
| InstanceInfoPB info; | |
| std::string instance_id = "test_instance_id_" + std::to_string(i); | |
| get_instance_info(meta_service.get(), &info, instance_id); | |
| } | |
| } | |
| sp->disable_processing(); | |
| sp->clear_all_call_backs(); | |
| } | |
| // Helper to create instance with obj_info for cascade testing | |
| static Versionstamp next_test_snapshot_versionstamp() { | |
| static std::atomic<uint64_t> version_counter {1}; | |
| uint64_t version = version_counter.fetch_add(1); | |
| return Versionstamp(version, 0); | |
| } | |
| static void create_instance_with_obj_info(MetaServiceProxy* meta_service, | |
| const std::string& instance_id, | |
| const std::string& source_instance_id, | |
| const std::string& user_id, const std::string& ak, | |
| const std::string& sk, bool enable_snapshot = true) { | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| InstanceInfoPB instance; | |
| instance.set_instance_id(instance_id); | |
| std::optional<Versionstamp> snapshot_version; | |
| if (!source_instance_id.empty()) { | |
| instance.set_source_instance_id(source_instance_id); | |
| snapshot_version = next_test_snapshot_versionstamp(); | |
| instance.set_source_snapshot_id(snapshot_version->to_string()); | |
| } | |
| instance.set_snapshot_switch_status(enable_snapshot ? SNAPSHOT_SWITCH_ON | |
| : SNAPSHOT_SWITCH_DISABLED); | |
| auto* obj_info = instance.add_obj_info(); | |
| obj_info->set_user_id(user_id); | |
| obj_info->set_ak(ak); | |
| obj_info->set_sk(sk); | |
| obj_info->set_id("test_obj_info"); | |
| std::string key = instance_key({instance_id}); | |
| std::string val = instance.SerializeAsString(); | |
| txn->put(key, val); | |
| if (snapshot_version.has_value()) { | |
| versioned::SnapshotReferenceKeyInfo ref_key_info {source_instance_id, *snapshot_version, | |
| instance_id}; | |
| std::string ref_key = versioned::snapshot_reference_key(ref_key_info); | |
| txn->put(ref_key, ""); | |
| } | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| } | |
| // Helper to verify instance ak/sk | |
| static void verify_instance_aksk(MetaServiceProxy* meta_service, const std::string& instance_id, | |
| const std::string& expected_ak, const std::string& expected_sk) { | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| std::string key = instance_key({instance_id}); | |
| std::string val; | |
| ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK); | |
| InstanceInfoPB instance; | |
| ASSERT_TRUE(instance.ParseFromString(val)); | |
| ASSERT_GT(instance.obj_info_size(), 0); | |
| EXPECT_EQ(instance.obj_info(0).ak(), expected_ak); | |
| EXPECT_EQ(instance.obj_info(0).sk(), expected_sk); | |
| } | |
| static void create_instance_with_storage_vault(MetaServiceProxy* meta_service, | |
| const std::string& instance_id, | |
| const std::string& source_instance_id, | |
| const std::string& vault_id, | |
| const std::string& vault_name, const std::string& ak, | |
| const std::string& sk, bool enable_snapshot = true) { | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| InstanceInfoPB instance; | |
| instance.set_instance_id(instance_id); | |
| instance.set_enable_storage_vault(true); | |
| instance.add_resource_ids(vault_id); | |
| instance.add_storage_vault_names(vault_name); | |
| instance.set_default_storage_vault_id(vault_id); | |
| instance.set_default_storage_vault_name(vault_name); | |
| std::optional<Versionstamp> snapshot_version; | |
| if (!source_instance_id.empty()) { | |
| instance.set_source_instance_id(source_instance_id); | |
| snapshot_version = next_test_snapshot_versionstamp(); | |
| instance.set_source_snapshot_id(snapshot_version->to_string()); | |
| } | |
| instance.set_snapshot_switch_status(enable_snapshot ? SNAPSHOT_SWITCH_ON | |
| : SNAPSHOT_SWITCH_DISABLED); | |
| StorageVaultPB vault; | |
| vault.set_id(vault_id); | |
| vault.set_name(vault_name); | |
| auto* obj_info = vault.mutable_obj_info(); | |
| obj_info->set_id(vault_id); | |
| obj_info->set_ak(ak); | |
| obj_info->set_sk(sk); | |
| obj_info->set_bucket("bucket"); | |
| obj_info->set_prefix("prefix"); | |
| obj_info->set_endpoint("endpoint"); | |
| obj_info->set_external_endpoint("external-endpoint"); | |
| obj_info->set_region("region"); | |
| obj_info->set_provider(ObjectStoreInfoPB::S3); | |
| txn->put(instance_key({instance_id}), instance.SerializeAsString()); | |
| txn->put(storage_vault_key({instance_id, vault_id}), vault.SerializeAsString()); | |
| if (snapshot_version.has_value()) { | |
| versioned::SnapshotReferenceKeyInfo ref_key_info {source_instance_id, *snapshot_version, | |
| instance_id}; | |
| txn->put(versioned::snapshot_reference_key(ref_key_info), ""); | |
| } | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| } | |
| static void verify_storage_vault(MetaServiceProxy* meta_service, const std::string& instance_id, | |
| const std::string& vault_id, const std::string& expected_name, | |
| const std::string& expected_ak, const std::string& expected_sk) { | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| std::string val; | |
| ASSERT_EQ(txn->get(storage_vault_key({instance_id, vault_id}), &val), TxnErrorCode::TXN_OK); | |
| StorageVaultPB vault; | |
| ASSERT_TRUE(vault.ParseFromString(val)); | |
| ASSERT_TRUE(vault.has_obj_info()); | |
| EXPECT_EQ(vault.name(), expected_name); | |
| EXPECT_EQ(vault.obj_info().ak(), expected_ak); | |
| EXPECT_EQ(vault.obj_info().sk(), expected_sk); | |
| ASSERT_EQ(txn->get(instance_key({instance_id}), &val), TxnErrorCode::TXN_OK); | |
| InstanceInfoPB instance; | |
| ASSERT_TRUE(instance.ParseFromString(val)); | |
| ASSERT_EQ(instance.resource_ids_size(), 1); | |
| ASSERT_EQ(instance.storage_vault_names_size(), 1); | |
| EXPECT_EQ(instance.resource_ids(0), vault_id); | |
| EXPECT_EQ(instance.storage_vault_names(0), expected_name); | |
| EXPECT_EQ(instance.default_storage_vault_id(), vault_id); | |
| EXPECT_EQ(instance.default_storage_vault_name(), expected_name); | |
| } | |
| // Test AK/SK cascade update: two-level cascade | |
| TEST(AkSkCascadeTest, TwoLevelCascade) { | |
| auto meta_service = get_meta_service(); | |
| auto sp = SyncPoint::get_instance(); | |
| sp->enable_processing(); | |
| sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* ret = try_any_cast<int*>(args[0]); | |
| *ret = 0; | |
| auto* key = try_any_cast<std::string*>(args[1]); | |
| *key = "selectdbselectdbselectdbselectdb"; | |
| auto* key_id = try_any_cast<int64_t*>(args[2]); | |
| *key_id = 1; | |
| }); | |
| std::string cipher_sk = "JUkuTDctR+ckJtnPkLScWaQZRcOtWBhsLLpnCRxQLxr734qB8cs6gNLH6grE1FxO"; | |
| std::string plain_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t"; | |
| // Create parent and child instances | |
| create_instance_with_obj_info(meta_service.get(), "parent_inst", "", "user123", "old_ak", | |
| "old_sk"); | |
| create_instance_with_obj_info(meta_service.get(), "child_inst", "parent_inst", "user123", | |
| "old_ak", "old_sk"); | |
| // Update parent's AK/SK | |
| UpdateAkSkRequest req; | |
| req.set_instance_id("parent_inst"); | |
| auto* bucket_user = req.add_internal_bucket_user(); | |
| bucket_user->set_user_id("user123"); | |
| bucket_user->set_ak("new_ak"); | |
| bucket_user->set_sk(plain_sk); | |
| brpc::Controller cntl; | |
| UpdateAkSkResponse res; | |
| meta_service->update_ak_sk(&cntl, &req, &res, nullptr); | |
| ASSERT_EQ(res.status().code(), MetaServiceCode::OK); | |
| // Verify parent updated | |
| verify_instance_aksk(meta_service.get(), "parent_inst", "new_ak", cipher_sk); | |
| // Verify child also updated (cascaded) | |
| verify_instance_aksk(meta_service.get(), "child_inst", "new_ak", cipher_sk); | |
| sp->disable_processing(); | |
| sp->clear_all_call_backs(); | |
| } | |
| // Test AK/SK cascade update: three-level cascade | |
| TEST(AkSkCascadeTest, ThreeLevelCascade) { | |
| auto meta_service = get_meta_service(); | |
| auto sp = SyncPoint::get_instance(); | |
| sp->enable_processing(); | |
| sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* ret = try_any_cast<int*>(args[0]); | |
| *ret = 0; | |
| auto* key = try_any_cast<std::string*>(args[1]); | |
| *key = "selectdbselectdbselectdbselectdb"; | |
| auto* key_id = try_any_cast<int64_t*>(args[2]); | |
| *key_id = 1; | |
| }); | |
| std::string cipher_sk = "JUkuTDctR+ckJtnPkLScWaQZRcOtWBhsLLpnCRxQLxr734qB8cs6gNLH6grE1FxO"; | |
| std::string plain_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t"; | |
| // Create instance tree: parent -> child -> grandchild | |
| create_instance_with_obj_info(meta_service.get(), "parent", "", "user1", "old_ak", "old_sk"); | |
| create_instance_with_obj_info(meta_service.get(), "child", "parent", "user1", "old_ak", | |
| "old_sk"); | |
| create_instance_with_obj_info(meta_service.get(), "grandchild", "child", "user1", "old_ak", | |
| "old_sk"); | |
| // Update parent | |
| UpdateAkSkRequest req; | |
| req.set_instance_id("parent"); | |
| auto* bucket_user = req.add_internal_bucket_user(); | |
| bucket_user->set_user_id("user1"); | |
| bucket_user->set_ak("new_ak_v2"); | |
| bucket_user->set_sk(plain_sk); | |
| brpc::Controller cntl; | |
| UpdateAkSkResponse res; | |
| meta_service->update_ak_sk(&cntl, &req, &res, nullptr); | |
| ASSERT_EQ(res.status().code(), MetaServiceCode::OK); | |
| // Verify all three levels updated | |
| verify_instance_aksk(meta_service.get(), "parent", "new_ak_v2", cipher_sk); | |
| verify_instance_aksk(meta_service.get(), "child", "new_ak_v2", cipher_sk); | |
| verify_instance_aksk(meta_service.get(), "grandchild", "new_ak_v2", cipher_sk); | |
| sp->disable_processing(); | |
| sp->clear_all_call_backs(); | |
| } | |
| // Test AK/SK cascade update: multiple branches | |
| TEST(AkSkCascadeTest, MultipleBranchesCascade) { | |
| auto meta_service = get_meta_service(); | |
| auto sp = SyncPoint::get_instance(); | |
| sp->enable_processing(); | |
| sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* ret = try_any_cast<int*>(args[0]); | |
| *ret = 0; | |
| auto* key = try_any_cast<std::string*>(args[1]); | |
| *key = "selectdbselectdbselectdbselectdb"; | |
| auto* key_id = try_any_cast<int64_t*>(args[2]); | |
| *key_id = 1; | |
| }); | |
| std::string cipher_sk = "JUkuTDctR+ckJtnPkLScWaQZRcOtWBhsLLpnCRxQLxr734qB8cs6gNLH6grE1FxO"; | |
| std::string plain_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t"; | |
| // Create tree: parent -> child1, child2, child3 | |
| create_instance_with_obj_info(meta_service.get(), "parent", "", "user1", "old_ak", "old_sk"); | |
| create_instance_with_obj_info(meta_service.get(), "child1", "parent", "user1", "old_ak", | |
| "old_sk"); | |
| create_instance_with_obj_info(meta_service.get(), "child2", "parent", "user1", "old_ak", | |
| "old_sk"); | |
| create_instance_with_obj_info(meta_service.get(), "child3", "parent", "user1", "old_ak", | |
| "old_sk"); | |
| // Update parent | |
| UpdateAkSkRequest req; | |
| req.set_instance_id("parent"); | |
| auto* bucket_user = req.add_internal_bucket_user(); | |
| bucket_user->set_user_id("user1"); | |
| bucket_user->set_ak("new_ak_multi"); | |
| bucket_user->set_sk(plain_sk); | |
| brpc::Controller cntl; | |
| UpdateAkSkResponse res; | |
| meta_service->update_ak_sk(&cntl, &req, &res, nullptr); | |
| ASSERT_EQ(res.status().code(), MetaServiceCode::OK); | |
| // Verify all branches updated | |
| verify_instance_aksk(meta_service.get(), "parent", "new_ak_multi", cipher_sk); | |
| verify_instance_aksk(meta_service.get(), "child1", "new_ak_multi", cipher_sk); | |
| verify_instance_aksk(meta_service.get(), "child2", "new_ak_multi", cipher_sk); | |
| verify_instance_aksk(meta_service.get(), "child3", "new_ak_multi", cipher_sk); | |
| sp->disable_processing(); | |
| sp->clear_all_call_backs(); | |
| } | |
| // Test AK/SK cascade update: no children | |
| TEST(AkSkCascadeTest, NoChildrenInstance) { | |
| auto meta_service = get_meta_service(); | |
| auto sp = SyncPoint::get_instance(); | |
| sp->enable_processing(); | |
| sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* ret = try_any_cast<int*>(args[0]); | |
| *ret = 0; | |
| auto* key = try_any_cast<std::string*>(args[1]); | |
| *key = "selectdbselectdbselectdbselectdb"; | |
| auto* key_id = try_any_cast<int64_t*>(args[2]); | |
| *key_id = 1; | |
| }); | |
| std::string cipher_sk = "JUkuTDctR+ckJtnPkLScWaQZRcOtWBhsLLpnCRxQLxr734qB8cs6gNLH6grE1FxO"; | |
| std::string plain_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t"; | |
| // Create only parent | |
| create_instance_with_obj_info(meta_service.get(), "single_inst", "", "user1", "old_ak", | |
| "old_sk"); | |
| // Update parent | |
| UpdateAkSkRequest req; | |
| req.set_instance_id("single_inst"); | |
| auto* bucket_user = req.add_internal_bucket_user(); | |
| bucket_user->set_user_id("user1"); | |
| bucket_user->set_ak("new_ak_single"); | |
| bucket_user->set_sk(plain_sk); | |
| brpc::Controller cntl; | |
| UpdateAkSkResponse res; | |
| meta_service->update_ak_sk(&cntl, &req, &res, nullptr); | |
| ASSERT_EQ(res.status().code(), MetaServiceCode::OK); | |
| // Verify only parent updated | |
| verify_instance_aksk(meta_service.get(), "single_inst", "new_ak_single", cipher_sk); | |
| sp->disable_processing(); | |
| sp->clear_all_call_backs(); | |
| } | |
| // Snapshot disabled should skip cascading to derived instances | |
| TEST(AkSkCascadeTest, SnapshotDisabledSkipsCascade) { | |
| auto meta_service = get_meta_service(); | |
| auto sp = SyncPoint::get_instance(); | |
| sp->enable_processing(); | |
| sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* ret = try_any_cast<int*>(args[0]); | |
| *ret = 0; | |
| auto* key = try_any_cast<std::string*>(args[1]); | |
| *key = "selectdbselectdbselectdbselectdb"; | |
| auto* key_id = try_any_cast<int64_t*>(args[2]); | |
| *key_id = 1; | |
| }); | |
| std::string cipher_sk = "JUkuTDctR+ckJtnPkLScWaQZRcOtWBhsLLpnCRxQLxr734qB8cs6gNLH6grE1FxO"; | |
| std::string plain_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t"; | |
| // Parent keeps snapshot disabled; child is a derived instance | |
| create_instance_with_obj_info(meta_service.get(), "parent_snapshot_off", "", "user1", "old_ak", | |
| "old_sk", /*enable_snapshot=*/false); | |
| create_instance_with_obj_info(meta_service.get(), "child_snapshot_off", "parent_snapshot_off", | |
| "user1", "old_ak", "old_sk"); | |
| UpdateAkSkRequest req; | |
| req.set_instance_id("parent_snapshot_off"); | |
| auto* bucket_user = req.add_internal_bucket_user(); | |
| bucket_user->set_user_id("user1"); | |
| bucket_user->set_ak("new_ak_disabled"); | |
| bucket_user->set_sk(plain_sk); | |
| brpc::Controller cntl; | |
| UpdateAkSkResponse res; | |
| meta_service->update_ak_sk(&cntl, &req, &res, nullptr); | |
| ASSERT_EQ(res.status().code(), MetaServiceCode::OK); | |
| // Parent updated, child stays untouched because snapshot is off | |
| verify_instance_aksk(meta_service.get(), "parent_snapshot_off", "new_ak_disabled", cipher_sk); | |
| verify_instance_aksk(meta_service.get(), "child_snapshot_off", "old_ak", "old_sk"); | |
| sp->disable_processing(); | |
| sp->clear_all_call_backs(); | |
| } | |
| // Test AK/SK cascade update: child without obj_info should NOT be cascaded | |
| TEST(AkSkCascadeTest, ChildWithoutObjInfo) { | |
| auto meta_service = get_meta_service(); | |
| auto sp = SyncPoint::get_instance(); | |
| sp->enable_processing(); | |
| sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* ret = try_any_cast<int*>(args[0]); | |
| *ret = 0; | |
| auto* key = try_any_cast<std::string*>(args[1]); | |
| *key = "selectdbselectdbselectdbselectdb"; | |
| auto* key_id = try_any_cast<int64_t*>(args[2]); | |
| *key_id = 1; | |
| }); | |
| std::string cipher_sk = "JUkuTDctR+ckJtnPkLScWaQZRcOtWBhsLLpnCRxQLxr734qB8cs6gNLH6grE1FxO"; | |
| std::string plain_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t"; | |
| // Create parent with obj_info | |
| create_instance_with_obj_info(meta_service.get(), "parent", "", "user1", "old_ak", "old_sk"); | |
| // Create child WITHOUT obj_info (independent storage) | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| InstanceInfoPB child_instance; | |
| child_instance.set_instance_id("child_no_obj"); | |
| child_instance.set_source_instance_id("parent"); | |
| // No obj_info added - using independent storage | |
| std::string key = instance_key({"child_no_obj"}); | |
| std::string val = child_instance.SerializeAsString(); | |
| txn->put(key, val); | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| // Update parent | |
| UpdateAkSkRequest req; | |
| req.set_instance_id("parent"); | |
| auto* bucket_user = req.add_internal_bucket_user(); | |
| bucket_user->set_user_id("user1"); | |
| bucket_user->set_ak("new_ak"); | |
| bucket_user->set_sk(plain_sk); | |
| brpc::Controller cntl; | |
| UpdateAkSkResponse res; | |
| meta_service->update_ak_sk(&cntl, &req, &res, nullptr); | |
| ASSERT_EQ(res.status().code(), MetaServiceCode::OK); | |
| // Verify parent updated | |
| verify_instance_aksk(meta_service.get(), "parent", "new_ak", cipher_sk); | |
| // Verify child was NOT updated (has no obj_info) | |
| ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| ASSERT_EQ(txn->get(instance_key({"child_no_obj"}), &val), TxnErrorCode::TXN_OK); | |
| child_instance.ParseFromString(val); | |
| EXPECT_EQ(child_instance.obj_info_size(), 0); // Still no obj_info | |
| sp->disable_processing(); | |
| sp->clear_all_call_backs(); | |
| } | |
| TEST(StorageVaultCascadeTest, AlterS3VaultCascadesToDerivedInstances) { | |
| auto meta_service = get_meta_service(); | |
| auto sp = SyncPoint::get_instance(); | |
| sp->enable_processing(); | |
| sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* ret = try_any_cast<int*>(args[0]); | |
| *ret = 0; | |
| auto* key = try_any_cast<std::string*>(args[1]); | |
| *key = "selectdbselectdbselectdbselectdb"; | |
| auto* key_id = try_any_cast<int64_t*>(args[2]); | |
| *key_id = 1; | |
| }); | |
| create_instance_with_storage_vault(meta_service.get(), "vault_parent", "", "2", "vault_old", | |
| "old_ak", "old_sk"); | |
| create_instance_with_storage_vault(meta_service.get(), "vault_child", "vault_parent", "2", | |
| "vault_old", "old_ak", "old_sk"); | |
| AlterObjStoreInfoRequest req; | |
| req.set_cloud_unique_id("1:vault_parent:test"); | |
| req.set_op(AlterObjStoreInfoRequest::ALTER_S3_VAULT); | |
| StorageVaultPB vault; | |
| vault.set_name("vault_old"); | |
| vault.set_alter_name("vault_new"); | |
| vault.mutable_obj_info()->set_ak("new_ak"); | |
| vault.mutable_obj_info()->set_sk("new_sk"); | |
| req.mutable_vault()->CopyFrom(vault); | |
| brpc::Controller cntl; | |
| AlterObjStoreInfoResponse res; | |
| meta_service->alter_storage_vault(&cntl, &req, &res, nullptr); | |
| ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg(); | |
| std::string cipher_sk = "HNAGUf23voYuuqV2BCX9Tw=="; | |
| verify_storage_vault(meta_service.get(), "vault_parent", "2", "vault_new", "new_ak", cipher_sk); | |
| verify_storage_vault(meta_service.get(), "vault_child", "2", "vault_new", "new_ak", cipher_sk); | |
| sp->disable_processing(); | |
| sp->clear_all_call_backs(); | |
| } | |
| TEST(StorageVaultCascadeTest, SnapshotDisabledSkipsCascade) { | |
| auto meta_service = get_meta_service(); | |
| auto sp = SyncPoint::get_instance(); | |
| sp->enable_processing(); | |
| sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* ret = try_any_cast<int*>(args[0]); | |
| *ret = 0; | |
| auto* key = try_any_cast<std::string*>(args[1]); | |
| *key = "selectdbselectdbselectdbselectdb"; | |
| auto* key_id = try_any_cast<int64_t*>(args[2]); | |
| *key_id = 1; | |
| }); | |
| create_instance_with_storage_vault(meta_service.get(), "vault_parent_disabled", "", "2", | |
| "vault_old", "old_ak", "old_sk", | |
| /*enable_snapshot=*/false); | |
| create_instance_with_storage_vault(meta_service.get(), "vault_child_disabled", | |
| "vault_parent_disabled", "2", "vault_old", "old_ak", | |
| "old_sk"); | |
| AlterObjStoreInfoRequest req; | |
| req.set_cloud_unique_id("1:vault_parent_disabled:test"); | |
| req.set_op(AlterObjStoreInfoRequest::ALTER_S3_VAULT); | |
| StorageVaultPB vault; | |
| vault.set_name("vault_old"); | |
| vault.set_alter_name("vault_new"); | |
| vault.mutable_obj_info()->set_ak("new_ak"); | |
| vault.mutable_obj_info()->set_sk("new_sk"); | |
| req.mutable_vault()->CopyFrom(vault); | |
| brpc::Controller cntl; | |
| AlterObjStoreInfoResponse res; | |
| meta_service->alter_storage_vault(&cntl, &req, &res, nullptr); | |
| ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg(); | |
| std::string cipher_sk = "HNAGUf23voYuuqV2BCX9Tw=="; | |
| verify_storage_vault(meta_service.get(), "vault_parent_disabled", "2", "vault_new", "new_ak", | |
| cipher_sk); | |
| verify_storage_vault(meta_service.get(), "vault_child_disabled", "2", "vault_old", "old_ak", | |
| "old_sk"); | |
| sp->disable_processing(); | |
| sp->clear_all_call_backs(); | |
| } | |
| TEST(ResourceTest, RollbackInstance) { | |
| auto sp = SyncPoint::get_instance(); | |
| sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* ret = try_any_cast<int*>(args[0]); | |
| *ret = 0; | |
| auto* key = try_any_cast<std::string*>(args[1]); | |
| *key = "test"; | |
| auto* key_id = try_any_cast<int64_t*>(args[2]); | |
| *key_id = 1; | |
| }); | |
| sp->set_call_back("decrypt_ak_sk:get_encryption_key", [](auto&& args) { | |
| auto* key = try_any_cast<std::string*>(args[0]); | |
| *key = "test"; | |
| auto* ret = try_any_cast<int*>(args[1]); | |
| *ret = 0; | |
| }); | |
| sp->enable_processing(); | |
| auto txn_kv = create_txn_kv(); | |
| auto meta_service = get_meta_service(txn_kv); | |
| // Create original instance | |
| std::string original_instance_id = "original_instance"; | |
| create_instance(meta_service.get(), original_instance_id); | |
| create_cluster(meta_service.get(), original_instance_id, "cluster_id_1", "cluster_1", | |
| ClusterPB::COMPUTE); | |
| // Add a node to original instance | |
| std::vector<NodeInfo> to_add; | |
| std::vector<NodeInfo> to_del; | |
| auto ni = NodeInfo {.role = Role::COMPUTE_NODE, | |
| .instance_id = original_instance_id, | |
| .cluster_name = "cluster_1", | |
| .cluster_id = "cluster_id_1", | |
| .node_info = NodeInfoPB {}}; | |
| ni.node_info.set_ip("127.0.0.1"); | |
| ni.node_info.set_cloud_unique_id("cloud_unique_id_1"); | |
| ni.node_info.set_heartbeat_port(9999); | |
| to_add.push_back(ni); | |
| auto r = meta_service->resource_mgr()->modify_nodes(original_instance_id, to_add, to_del); | |
| ASSERT_EQ(r, ""); | |
| // Simulate rollback operation: create new instance with source_instance_id | |
| std::string new_instance_id = "new_instance_after_rollback"; | |
| create_instance(meta_service.get(), new_instance_id); | |
| // Set source_instance_id and original_instance_id for new instance | |
| // Set successor_instance_id for old instance | |
| { | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| // Update new instance with source info | |
| InstanceInfoPB new_instance; | |
| std::string new_key = instance_key(new_instance_id); | |
| std::string new_val; | |
| ASSERT_EQ(txn->get(new_key, &new_val), TxnErrorCode::TXN_OK); | |
| ASSERT_TRUE(new_instance.ParseFromString(new_val)); | |
| new_instance.set_source_instance_id(original_instance_id); | |
| new_instance.set_original_instance_id(original_instance_id); | |
| new_instance.set_source_snapshot_id("00000000000000000000"); // Valid versionstamp format | |
| // Inherit cluster from original instance | |
| InstanceInfoPB original_instance; | |
| std::string original_key = instance_key(original_instance_id); | |
| std::string original_val; | |
| ASSERT_EQ(txn->get(original_key, &original_val), TxnErrorCode::TXN_OK); | |
| ASSERT_TRUE(original_instance.ParseFromString(original_val)); | |
| new_instance.mutable_clusters()->CopyFrom(original_instance.clusters()); | |
| txn->put(new_key, new_instance.SerializeAsString()); | |
| // Update original instance with successor_instance_id | |
| original_instance.set_successor_instance_id(new_instance_id); | |
| txn->put(original_key, original_instance.SerializeAsString()); | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| } | |
| meta_service->resource_mgr()->refresh_instance(new_instance_id); | |
| std::string instance_id = get_instance_id(meta_service->resource_mgr(), "cloud_unique_id_1"); | |
| ASSERT_EQ(instance_id, new_instance_id); | |
| meta_service->resource_mgr()->refresh_instance(original_instance_id); | |
| instance_id = get_instance_id(meta_service->resource_mgr(), "cloud_unique_id_1"); | |
| ASSERT_EQ(instance_id, new_instance_id); | |
| sp->disable_processing(); | |
| sp->clear_all_call_backs(); | |
| } | |
| } // namespace doris::cloud |