From d75787d9f9a792b227618387467a7b3c2220395b Mon Sep 17 00:00:00 2001 From: owent Date: Fri, 11 Sep 2026 11:14:36 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=9B=91=E5=90=AC?= =?UTF-8?q?=E5=9C=B0=E5=9D=80scope=E5=92=8Cnamespace=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E9=83=A8=E5=88=86=E4=B8=8D=E5=B8=B8=E7=94=A8=E7=9A=84?= =?UTF-8?q?=E7=BB=B4=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- atframework/libatbus | 2 +- include/atframe/atapp.h | 5 +- include/atframe/atapp_conf.proto | 29 +-- src/CMakeLists.txt | 6 +- src/atframe/atapp.cpp | 109 ++++++++--- src/atframe/connectors/atapp_endpoint.cpp | 81 +++++++- src/atframe/etcdcli/etcd_discovery.cpp | 59 +++--- .../case/atapp_configure_expression_test.conf | 4 +- .../atapp_configure_expression_test.env.txt | 4 +- .../case/atapp_configure_expression_test.yaml | 4 +- test/case/atapp_configure_loader_test.cpp | 18 +- test/case/atapp_direct_connect_test.cpp | 178 ++++++++++++++++++ test/case/atapp_discovery_test.cpp | 79 +++++++- test/case/atapp_setup_test.cpp | 137 ++++++++++++++ test/case/atapp_test_direct_1.yaml | 6 + test/case/atapp_test_isolation_1.yaml | 63 +++++++ 16 files changed, 685 insertions(+), 99 deletions(-) create mode 100644 test/case/atapp_test_isolation_1.yaml diff --git a/atframework/libatbus b/atframework/libatbus index ef1149f..d46c04d 160000 --- a/atframework/libatbus +++ b/atframework/libatbus @@ -1 +1 @@ -Subproject commit ef1149faffeb2fae7cf2eec8c0376dd88d4dd550 +Subproject commit d46c04d7da7601245703326859a30486badde4d8 diff --git a/include/atframe/atapp.h b/include/atframe/atapp.h index 5505ab2..84bce71 100644 --- a/include/atframe/atapp.h +++ b/include/atframe/atapp.h @@ -372,11 +372,9 @@ class app { LIBATAPP_MACRO_API void set_kind(gsl::string_view value); LIBATAPP_MACRO_API void set_group(gsl::string_view value); + LIBATAPP_MACRO_API void set_metadata_scope(gsl::string_view value); LIBATAPP_MACRO_API void set_metadata_name(gsl::string_view value); LIBATAPP_MACRO_API void set_metadata_namespace_name(gsl::string_view value); - LIBATAPP_MACRO_API void set_metadata_uid(gsl::string_view value); - - LIBATAPP_MACRO_API void set_metadata_service_subset(gsl::string_view value); LIBATAPP_MACRO_API void set_metadata_label(gsl::string_view key, gsl::string_view value); @@ -625,6 +623,7 @@ class app { void print_help(); static bool match_gateway_hosts(const atapp::protocol::atapp_gateway &checked) noexcept; + bool match_gateway_scope(const atapp::protocol::atapp_gateway &checked) const noexcept; bool match_gateway_namespace(const atapp::protocol::atapp_gateway &checked) const noexcept; bool match_gateway_labels(const atapp::protocol::atapp_gateway &checked) const noexcept; diff --git a/include/atframe/atapp_conf.proto b/include/atframe/atapp_conf.proto index 1a426e1..027e0a2 100644 --- a/include/atframe/atapp_conf.proto +++ b/include/atframe/atapp_conf.proto @@ -54,17 +54,21 @@ message atapp_area { message atapp_gateway { string address = 1 [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; - repeated string match_hosts = 11; // match atapp_configure.hostname with "In" operator + // match atapp_metadata.scope with "Equal" operator + string match_scope = 10 [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; + + // match atapp_configure.hostname with "In" operator + repeated string match_hosts = 11 [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; // Just like NetworkPolicyPeer in // https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/api/networking/v1/generated.proto // See also LabelSelector in // https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto - repeated string match_namespaces = 21 [ - (atframework.atapp.protocol.CONFIGURE) = { enable_expression: true } - ]; // match atapp_metadata.namespace_name with "In" operator - map match_labels = 22 - [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; // match all labels + // match atapp_metadata.namespace_name with "In" operator + repeated string match_namespaces = 21 [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; + + // match atapp_metadata.labels with "Contains" operator + map match_labels = 22 [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; } enum ATAPP_ACCESS_DATA_ALGORITHM_TYPE { @@ -226,8 +230,12 @@ message atbus_configure { uint64 access_token_max_number = 107; repeated string access_tokens = 108; repeated atapp_gateway gateways = 110; + bool overwrite_listen_path = 111 [(atapp.protocol.CONFIGURE) = { default_value: "false" }]; + // Which labels will be inherited by atbus from atapp_metadata.labels + repeated string inherited_labels = 113 [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; + atbus_topology topology = 120; google.protobuf.Duration first_idle_timeout = 201 [(atapp.protocol.CONFIGURE) = { default_value: "30s" }]; @@ -504,16 +512,13 @@ message atapp_metadata { string kind = 2 [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; string group = 3 [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; + // Scope of listen addresses, usually set to cluster.metadata.uid or cluster name of Kubernetes cluster + string scope = 4 [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; + // kubernetes ENV: ATAPP_METADATA_NAME: valueFrom: fieldRef: fieldPath: metadata.name string name = 11 [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; // kubernetes ENV: ATAPP_METADATA_NAMESPACE_NAME: valueFrom: fieldRef: fieldPath: metadata.namespace string namespace_name = 12 [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; - // kubernetes ENV: ATAPP_METADATA_UID: valueFrom: fieldRef: fieldPath: metadata.uid - string uid = 13 [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; - - // `service_subset` is similar to Destination in - // https://istio.io/latest/docs/reference/config/networking/virtual-service/#Destination - string service_subset = 23 [(atframework.atapp.protocol.CONFIGURE) = { enable_expression: true }]; // https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set // https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7ab10fb..4fdaf40 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -76,6 +76,11 @@ add_custom_command( "Generate ${PROJECT_LIBATAPP_GENERATED_DIR}/include/atframe/atapp_conf.pb.h and ${PROJECT_LIBATAPP_GENERATED_DIR}/src/atapp_conf.pb.cc" ) +set_property( + SOURCE ${PROJECT_LIBATAPP_SRC_LIST} + APPEND + PROPERTY OBJECT_DEPENDS "${PROJECT_LIBATAPP_GENERATED_DIR}/include/atframe/atapp_conf.pb.h") + if(NOT DEFINED LIBATAPP_MACRO_PROTOCOL_USE_DYNAMIC_LIBRARY) if(BUILD_SHARED_LIBS OR ATFRAMEWORK_USE_DYNAMIC_LIBRARY) if(DEFINED ATFRAMEWORK_CMAKE_TOOLSET_THIRD_PARTY_PROTOBUF_ALLOW_SHARED_LIBS) @@ -207,4 +212,3 @@ install( install(FILES "${PROJECT_LIBATAPP_GENERATED_DIR}/include/atframe/atapp_conf.pb.h" "${PROJECT_LIBATAPP_GENERATED_DIR}/include/atframe/atapp_config.h" DESTINATION "${ATFRAMEWORK_INSTALL_INCLUDEDIR}/atframe/") - diff --git a/src/atframe/atapp.cpp b/src/atframe/atapp.cpp index 27afc5c..b6188f7 100644 --- a/src/atframe/atapp.cpp +++ b/src/atframe/atapp.cpp @@ -40,10 +40,10 @@ #include #include #include +#include #include #include #include -#include #if !(defined(ATFRAMEWORK_UTILS_THREAD_TLS_USE_PTHREAD) && ATFRAMEWORK_UTILS_THREAD_TLS_USE_PTHREAD) && \ __cplusplus >= 201103L # include @@ -266,7 +266,8 @@ static atbus::protocol::ATBUS_COMPRESSION_LEVEL convert_atbus_configure(protocol } } -static void apply_atbus_configure(atbus::node::conf_t &to, const protocol::atbus_configure &from) { +static void apply_atbus_configure(atbus::node::conf_t &to, const protocol::atbus_configure &from, + const protocol::atapp_metadata &metadata) { atbus::node::default_conf(&to); to.upstream_address = from.proxy(); @@ -289,6 +290,46 @@ static void apply_atbus_configure(atbus::node::conf_t &to, const protocol::atbus protobuf_to_chrono_set_duration(to.first_idle_timeout, from.first_idle_timeout()); protobuf_to_chrono_set_duration(to.ping_interval, from.ping_interval()); protobuf_to_chrono_set_duration(to.retry_interval, from.retry_interval()); + protobuf_to_chrono_set_duration(to.max_retry_interval, from.reconnect_max_interval()); + + // network scope + to.scope = metadata.scope(); + to.namespace_name = metadata.namespace_name(); + to.node_labels.clear(); + to.node_labels.reserve( + (std::min)(static_cast(metadata.labels_size()), static_cast(from.inherited_labels_size()))); + std::unordered_set inherited_labels_set; + inherited_labels_set.reserve(static_cast(from.inherited_labels_size())); + for (const auto &label_k : from.inherited_labels()) { + inherited_labels_set.insert(label_k); + } + for (const auto &label_kv : metadata.labels()) { + if (inherited_labels_set.find(label_kv.first) != inherited_labels_set.end() && !label_kv.second.empty()) { + to.node_labels[label_kv.first] = label_kv.second; + } + } + to.gateway.clear(); + to.gateway.reserve(static_cast(from.gateways_size())); + for (const auto &gw : from.gateways()) { + if (gw.address().empty()) { + continue; + } + to.gateway.push_back(atbus::node::gateway_t()); + auto &gw_cfg = to.gateway.back(); + gw_cfg.address = gw.address(); + gw_cfg.match_scope = gw.match_scope(); + gw_cfg.match_hosts.reserve(static_cast(gw.match_hosts().size())); + gw_cfg.match_hosts = {gw.match_hosts().begin(), gw.match_hosts().end()}; + gw_cfg.match_namespaces.reserve(static_cast(gw.match_namespaces().size())); + gw_cfg.match_namespaces = {gw.match_namespaces().begin(), gw.match_namespaces().end()}; + gw_cfg.match_labels.reserve( + (std::min)(static_cast(gw.match_labels().size()), static_cast(from.inherited_labels_size()))); + for (const auto &label_kv : gw.match_labels()) { + if (inherited_labels_set.find(label_kv.first) != inherited_labels_set.end() && !label_kv.second.empty()) { + gw_cfg.match_labels[label_kv.first] = label_kv.second; + } + } + } to.fault_tolerant = static_cast(from.fault_tolerant()); to.message_size = static_cast(from.message_size()); @@ -1175,6 +1216,9 @@ LIBATAPP_MACRO_API int app::reload() { bus_node_->reload_compression(gsl::span( conf_.bus_conf.compression_allow_algorithms), conf_.bus_conf.compression_level); + bus_node_->reload_self_endpoint( + conf_.bus_conf.scope, conf_.bus_conf.namespace_name, conf_.bus_conf.node_labels, + gsl::span{conf_.bus_conf.gateway.data(), conf_.bus_conf.gateway.size()}); } if (atbus_connector_) { atbus_connector_->reload(); @@ -1687,21 +1731,8 @@ LIBATAPP_MACRO_API void app::set_group(gsl::string_view value) { conf_.metadata.set_group(value.data(), value.size()); } -LIBATAPP_MACRO_API void app::set_metadata_name(gsl::string_view value) { - if (gsl::string_view(conf_.metadata.name().data(), conf_.metadata.name().size()) == value) { - return; - } - - if (internal_module_service_discovery_) { - internal_module_service_discovery_->set_maybe_update_keepalive_discovery_metadata(); - } - - conf_.metadata.set_name(value.data(), value.size()); - conf_.runtime_pod_stateful_index = static_cast(atapp_pod_stateful_index::kUnset); -} - -LIBATAPP_MACRO_API void app::set_metadata_namespace_name(gsl::string_view value) { - if (gsl::string_view(conf_.metadata.namespace_name().c_str(), conf_.metadata.namespace_name().size()) == value) { +LIBATAPP_MACRO_API void app::set_metadata_scope(gsl::string_view value) { + if (gsl::string_view(conf_.metadata.scope().c_str(), conf_.metadata.scope().size()) == value) { return; } @@ -1709,11 +1740,11 @@ LIBATAPP_MACRO_API void app::set_metadata_namespace_name(gsl::string_view value) internal_module_service_discovery_->set_maybe_update_keepalive_discovery_metadata(); } - conf_.metadata.set_namespace_name(value.data(), value.size()); + conf_.metadata.set_scope(value.data(), value.size()); } -LIBATAPP_MACRO_API void app::set_metadata_uid(gsl::string_view value) { - if (gsl::string_view(conf_.metadata.uid().c_str(), conf_.metadata.uid().size()) == value) { +LIBATAPP_MACRO_API void app::set_metadata_name(gsl::string_view value) { + if (gsl::string_view(conf_.metadata.name().data(), conf_.metadata.name().size()) == value) { return; } @@ -1721,11 +1752,12 @@ LIBATAPP_MACRO_API void app::set_metadata_uid(gsl::string_view value) { internal_module_service_discovery_->set_maybe_update_keepalive_discovery_metadata(); } - conf_.metadata.set_uid(value.data(), value.size()); + conf_.metadata.set_name(value.data(), value.size()); + conf_.runtime_pod_stateful_index = static_cast(atapp_pod_stateful_index::kUnset); } -LIBATAPP_MACRO_API void app::set_metadata_service_subset(gsl::string_view value) { - if (gsl::string_view(conf_.metadata.service_subset().c_str(), conf_.metadata.service_subset().size()) == value) { +LIBATAPP_MACRO_API void app::set_metadata_namespace_name(gsl::string_view value) { + if (gsl::string_view(conf_.metadata.namespace_name().c_str(), conf_.metadata.namespace_name().size()) == value) { return; } @@ -1733,7 +1765,7 @@ LIBATAPP_MACRO_API void app::set_metadata_service_subset(gsl::string_view value) internal_module_service_discovery_->set_maybe_update_keepalive_discovery_metadata(); } - conf_.metadata.set_service_subset(value.data(), value.size()); + conf_.metadata.set_namespace_name(value.data(), value.size()); } LIBATAPP_MACRO_API void app::set_metadata_label(gsl::string_view key, gsl::string_view value) { @@ -2651,11 +2683,15 @@ LIBATAPP_MACRO_API bool app::match_gateway(const atapp::protocol::atapp_gateway return false; } + if (!checked.match_scope().empty() && !match_gateway_scope(checked)) { + return false; + } + if (checked.match_hosts_size() > 0 && !match_gateway_hosts(checked)) { return false; } - if (checked.match_namespaces_size() && !match_gateway_namespace(checked)) { + if (checked.match_namespaces_size() > 0 && !match_gateway_namespace(checked)) { return false; } @@ -2991,7 +3027,7 @@ int app::apply_configure() { } // atbus configure - apply_atbus_configure(conf_.bus_conf, conf_.origin.bus()); + apply_atbus_configure(conf_.bus_conf, conf_.origin.bus(), conf_.metadata); // windows minidump configure setup_windows_minidump(conf_.origin.debug(), conf_.origin.name()); @@ -3711,6 +3747,17 @@ LIBATAPP_MACRO_API void app::trigger_event_on_discovery_event(etcd_discovery_act FWLOGINFO("app {}({}, type={}:{}) got a PUT discovery event({}({}, type={}:{}))", get_app_name(), get_app_id(), get_type_id(), get_type_name(), discovery_info.name(), discovery_info.id(), discovery_info.type_id(), discovery_info.type_name()); + if (0 != discovery_info.id()) { + auto *ep = get_endpoint(discovery_info.id()); + if (ep != nullptr) { + ep->update_discovery(node); + } + } else { + auto *ep = get_endpoint(discovery_info.name()); + if (ep != nullptr) { + ep->update_discovery(node); + } + } } else { FWLOGINFO("app {}({}, type={}:{}) got a DELETE discovery event({}({}, type={}:{})", get_app_name(), get_app_id(), get_type_id(), get_type_name(), discovery_info.name(), discovery_info.id(), discovery_info.type_id(), @@ -4326,6 +4373,14 @@ void app::print_help() { } } +bool app::match_gateway_scope(const atapp::protocol::atapp_gateway &checked) const noexcept { + if (checked.match_scope().empty()) { + return true; + } + + return checked.match_scope() == get_metadata().scope(); +} + bool app::match_gateway_hosts(const atapp::protocol::atapp_gateway &checked) noexcept { bool has_matched_value = false; bool has_valid_conf = false; @@ -5450,6 +5505,8 @@ int app::send_last_command(ev_loop_t *ev_loop) { bool is_sync_channel = false; atbus::channel::channel_address_t use_addr; + // 发送指令的节点必须加载和目标节点相同的配置,并且部署在同一机器上。所以scope、namespace、label等都是相同的 + // 这里就不用再根据这些判定一次连接可达性 for (int i = 0; i < conf_.origin.bus().listen_size(); ++i) { atbus::channel::channel_address_t parsed_addr; make_address(conf_.origin.bus().listen(i), parsed_addr); diff --git a/src/atframe/connectors/atapp_endpoint.cpp b/src/atframe/connectors/atapp_endpoint.cpp index 95bdde1..0e309cf 100644 --- a/src/atframe/connectors/atapp_endpoint.cpp +++ b/src/atframe/connectors/atapp_endpoint.cpp @@ -133,9 +133,84 @@ LIBATAPP_MACRO_API void atapp_endpoint::update_discovery(const etcd_discovery_no discovery_ = discovery; - if (discovery) { - FWLOGINFO("update atapp endpoint {} with {}({})", reinterpret_cast(this), - discovery->get_discovery_info().id(), discovery->get_discovery_info().name()); + if (!discovery) { + return; + } + + FWLOGINFO("update atapp endpoint {} with {}({})", reinterpret_cast(this), + discovery->get_discovery_info().id(), discovery->get_discovery_info().name()); + + if (owner_->get_bus_node() && discovery->get_discovery_info().id() != 0) { + auto *bus_ep = owner_->get_bus_node()->get_endpoint(discovery->get_discovery_info().id()); + if (bus_ep != nullptr) { + std::unordered_map labels; + std::vector gateways; + const auto &metadata = discovery->get_discovery_info().metadata(); + std::unordered_set inherited_labels_set; + inherited_labels_set.reserve(static_cast(owner_->get_origin_configure().bus().inherited_labels().size())); + for (const auto &label_k : owner_->get_origin_configure().bus().inherited_labels()) { + inherited_labels_set.insert(label_k); + auto iter = metadata.labels().find(label_k); + if (iter != metadata.labels().end() && !iter->second.empty()) { + labels[label_k] = iter->second; + } + } + + gateways.reserve(static_cast(discovery->get_discovery_info().gateways_size())); + for (const auto &gateway : discovery->get_discovery_info().gateways()) { + if (gateway.address().empty()) { + continue; + } + gateways.push_back(atbus::node::gateway_t()); + auto &gw = gateways.back(); + + gw.address = gateway.address(); + gw.match_scope = gateway.match_scope(); + if (gateway.match_namespaces_size() > 0) { + gw.match_namespaces.reserve(static_cast(gateway.match_namespaces_size())); + for (const auto &ns : gateway.match_namespaces()) { + gw.match_namespaces.insert(ns); + } + } + if (gateway.match_hosts_size() > 0) { + gw.match_hosts.reserve(static_cast(gateway.match_hosts_size())); + for (const auto &host : gateway.match_hosts()) { + gw.match_hosts.insert(host); + } + } + if (gateway.match_labels_size() > 0) { + // 与 apply_atbus_configure 保持一致: 只按继承标签过滤, 对端只会用继承标签来匹配 + gw.match_labels.reserve( + (std::min)(static_cast(gateway.match_labels_size()), inherited_labels_set.size())); + for (const auto &label_kv : gateway.match_labels()) { + if (inherited_labels_set.find(label_kv.first) != inherited_labels_set.end() && !label_kv.second.empty()) { + gw.match_labels.emplace(label_kv.first, label_kv.second); + } + } + } + } + + if (gateways.empty()) { + // 未配置 gateway 的对端注册时通告的是 listen 地址, 这里按同样的规则合成匹配条件 + gateways.reserve(static_cast(discovery->get_discovery_info().listen_size())); + for (const auto &listen_address : discovery->get_discovery_info().listen()) { + if (listen_address.empty()) { + continue; + } + gateways.push_back(atbus::node::gateway_t()); + auto &gw = gateways.back(); + + gw.address = listen_address; + gw.match_scope = metadata.scope(); + if (!metadata.namespace_name().empty()) { + gw.match_namespaces.insert(metadata.namespace_name()); + } + } + } + + bus_ep->reload(metadata.scope(), metadata.namespace_name(), labels, + gsl::span(gateways.data(), gateways.size())); + } } } diff --git a/src/atframe/etcdcli/etcd_discovery.cpp b/src/atframe/etcdcli/etcd_discovery.cpp index d2db2b6..56da836 100644 --- a/src/atframe/etcdcli/etcd_discovery.cpp +++ b/src/atframe/etcdcli/etcd_discovery.cpp @@ -235,8 +235,8 @@ static bool is_empty(const etcd_discovery_set::metadata_type &metadata) noexcept } return metadata.api_version().empty() && metadata.kind().empty() && metadata.group().empty() && - metadata.namespace_name().empty() && metadata.name().empty() && metadata.uid().empty() && - metadata.service_subset().empty() && 0 == metadata.labels_size(); + metadata.namespace_name().empty() && metadata.name().empty() && metadata.scope().empty() && + 0 == metadata.labels_size(); } static bool node_equal(const etcd_discovery_node::ptr_t &l, const etcd_discovery_node::ptr_t &r) noexcept { @@ -272,6 +272,16 @@ LIBATAPP_MACRO_API void etcd_discovery_node::copy_from(const atapp::protocol::at const node_version &version, uintptr_t context_addr) { node_info_.CopyFrom(input); + // listen 回退地址的隔离规则与 libatbus 注册时通告的一致: 隔离 scope 和 namespace + // node_info_ 只会在这里整体替换, 匹配规则只需设置一次 + ingress_for_listen_.Clear(); + if (!node_info_.metadata().scope().empty()) { + ingress_for_listen_.set_match_scope(node_info_.metadata().scope()); + } + if (!node_info_.metadata().namespace_name().empty()) { + ingress_for_listen_.add_match_namespaces(node_info_.metadata().namespace_name()); + } + name_hash_ = consistent_hash_calc( gsl::span{reinterpret_cast(input.name().data()), input.name().size()}, LIBATAPP_MACRO_HASH_MAGIC_NUMBER); @@ -347,11 +357,12 @@ LIBATAPP_MACRO_API const atapp::protocol::atapp_gateway &etcd_discovery_node::ne if (ingress_index_ >= node_info_.listen_size()) { ingress_index_ %= node_info_.listen_size(); } + // 匹配规则已在 copy_from 中设置, 这里只需轮询替换地址 ingress_for_listen_.set_address(node_info_.listen(ingress_index_++)); return ingress_for_listen_; } - // if none of gateways or listen found, ingress_for_listen_ will always be empty + // 未配置 gateways 和 listen 时返回不带地址的对象, 调用方按 get_ingress_size 守卫, 不会走到这里 return ingress_for_listen_; } @@ -379,6 +390,10 @@ etcd_discovery_set::metadata_hash_type::operator()(const metadata_type &metadata consistent_hash_combine(consistent_hash_to_span(metadata.group()), hash_value); } + if (!metadata.scope().empty()) { + consistent_hash_combine(consistent_hash_to_span(metadata.scope()), hash_value); + } + if (!metadata.name().empty()) { consistent_hash_combine(consistent_hash_to_span(metadata.name()), hash_value); } @@ -387,14 +402,6 @@ etcd_discovery_set::metadata_hash_type::operator()(const metadata_type &metadata consistent_hash_combine(consistent_hash_to_span(metadata.namespace_name()), hash_value); } - if (!metadata.uid().empty()) { - consistent_hash_combine(consistent_hash_to_span(metadata.uid()), hash_value); - } - - if (!metadata.service_subset().empty()) { - consistent_hash_combine(consistent_hash_to_span(metadata.service_subset()), hash_value); - } - std::vector> kvs; if (metadata.labels_size() > 0) { sort_string_map(metadata.labels(), kvs); @@ -424,19 +431,15 @@ LIBATAPP_MACRO_API bool etcd_discovery_set::metadata_equal_type::operator()(cons return false; } - if (l.namespace_name().size() != r.namespace_name().size()) { + if (l.scope().size() != r.scope().size()) { return false; } - if (l.name().size() != r.name().size()) { - return false; - } - - if (l.uid().size() != r.uid().size()) { + if (l.namespace_name().size() != r.namespace_name().size()) { return false; } - if (l.service_subset().size() != r.service_subset().size()) { + if (l.name().size() != r.name().size()) { return false; } @@ -456,19 +459,15 @@ LIBATAPP_MACRO_API bool etcd_discovery_set::metadata_equal_type::operator()(cons return false; } - if (l.namespace_name() != r.namespace_name()) { - return false; - } - - if (l.name() != r.name()) { + if (l.scope() != r.scope()) { return false; } - if (l.uid() != r.uid()) { + if (l.namespace_name() != r.namespace_name()) { return false; } - if (l.service_subset() != r.service_subset()) { + if (l.name() != r.name()) { return false; } @@ -503,19 +502,15 @@ LIBATAPP_MACRO_API bool etcd_discovery_set::metadata_equal_type::filter(const me return false; } - if (!rule.namespace_name().empty() && rule.namespace_name() != metadata.namespace_name()) { + if (!rule.scope().empty() && rule.scope() != metadata.scope()) { return false; } - if (!rule.name().empty() && rule.name() != metadata.name()) { - return false; - } - - if (!rule.uid().empty() && rule.uid() != metadata.uid()) { + if (!rule.namespace_name().empty() && rule.namespace_name() != metadata.namespace_name()) { return false; } - if (!rule.service_subset().empty() && rule.service_subset() != metadata.service_subset()) { + if (!rule.name().empty() && rule.name() != metadata.name()) { return false; } diff --git a/test/case/atapp_configure_expression_test.conf b/test/case/atapp_configure_expression_test.conf index 13bda80..d3b197a 100644 --- a/test/case/atapp_configure_expression_test.conf +++ b/test/case/atapp_configure_expression_test.conf @@ -13,9 +13,9 @@ identity = ${ATAPP_EXPR_TEST_MISSING_VAR:-fallback_identity} ; Test ${VAR:+word} with existing variable — should use word metadata.namespace_name = ${ATAPP_EXPR_TEST_NAME:+ns_override} ; Test ${VAR:+word} with missing variable — should be empty -metadata.uid = ${ATAPP_EXPR_TEST_MISSING_VAR:+should_not_appear} +metadata.kind = ${ATAPP_EXPR_TEST_MISSING_VAR:+should_not_appear} ; Test multi-level nested default: ${A:-${B:-default}} -metadata.service_subset = ${ATAPP_EXPR_TEST_MISSING1:-${ATAPP_EXPR_TEST_MISSING2:-multi_level_default}} +metadata.scope = ${ATAPP_EXPR_TEST_MISSING1:-${ATAPP_EXPR_TEST_MISSING2:-multi_level_default}} ; Test nested expression: ${OUTER_${INNER}} metadata.name = ${ATAPP_EXPR_TEST_NESTED_${ATAPP_EXPR_TEST_SUFFIX}} metadata.api_version = v1 diff --git a/test/case/atapp_configure_expression_test.env.txt b/test/case/atapp_configure_expression_test.env.txt index a500c47..3a07cfa 100644 --- a/test/case/atapp_configure_expression_test.env.txt +++ b/test/case/atapp_configure_expression_test.env.txt @@ -12,9 +12,9 @@ ATAPP_IDENTITY=${ATAPP_EXPR_TEST_MISSING_VAR:-fallback_identity} ; Test ${VAR:+word} with existing variable ATAPP_METADATA_NAMESPACE_NAME=${ATAPP_EXPR_TEST_NAME:+ns_override} ; Test ${VAR:+word} with missing variable -ATAPP_METADATA_UID=${ATAPP_EXPR_TEST_MISSING_VAR:+should_not_appear} +ATAPP_METADATA_KIND=${ATAPP_EXPR_TEST_MISSING_VAR:+should_not_appear} ; Test \$ escape -ATAPP_METADATA_SERVICE_SUBSET=price\$100 +ATAPP_METADATA_SCOPE=price\$100 ; Test nested expression: ${OUTER_${INNER}} ATAPP_METADATA_NAME=${ATAPP_EXPR_TEST_NESTED_${ATAPP_EXPR_TEST_SUFFIX}} ATAPP_METADATA_API_VERSION=v1 diff --git a/test/case/atapp_configure_expression_test.yaml b/test/case/atapp_configure_expression_test.yaml index fd71fee..88629e0 100644 --- a/test/case/atapp_configure_expression_test.yaml +++ b/test/case/atapp_configure_expression_test.yaml @@ -18,11 +18,11 @@ atapp: # Test ${VAR:+word} with existing variable — should use word namespace_name: "${ATAPP_EXPR_TEST_NAME:+ns_override}" # Test ${VAR:+word} with missing variable — should be empty - uid: "${ATAPP_EXPR_TEST_MISSING_VAR:+should_not_appear}" + kind: "${ATAPP_EXPR_TEST_MISSING_VAR:+should_not_appear}" # Test nested expression: ${OUTER_${INNER}} name: "${ATAPP_EXPR_TEST_NESTED_${ATAPP_EXPR_TEST_SUFFIX}}" # Test multi-level nested default: ${A:-${B:-default}} - service_subset: "${ATAPP_EXPR_TEST_MISSING1:-${ATAPP_EXPR_TEST_MISSING2:-multi_level_default}}" + scope: "${ATAPP_EXPR_TEST_MISSING1:-${ATAPP_EXPR_TEST_MISSING2:-multi_level_default}}" labels: # Map keys and values should also support expressions "${ATAPP_EXPR_TEST_LABEL_KEY}": "${ATAPP_EXPR_TEST_LABEL_VALUE}" diff --git a/test/case/atapp_configure_loader_test.cpp b/test/case/atapp_configure_loader_test.cpp index 2045ae1..d5c3533 100644 --- a/test/case/atapp_configure_loader_test.cpp +++ b/test/case/atapp_configure_loader_test.cpp @@ -586,12 +586,12 @@ CASE_TEST(atapp_configure, expression_yaml) { CASE_EXPECT_EQ("ns_override", app_cfg.metadata().namespace_name()); CASE_MSG_INFO() << "metadata.namespace_name = " << app_cfg.metadata().namespace_name() << '\n'; - // metadata.uid: "${ATAPP_EXPR_TEST_MISSING_VAR:+should_not_appear}" -> "" - CASE_EXPECT_EQ("", app_cfg.metadata().uid()); + // metadata.kind: "${ATAPP_EXPR_TEST_MISSING_VAR:+should_not_appear}" -> "" + CASE_EXPECT_EQ("", app_cfg.metadata().kind()); - // metadata.service_subset: "${...:-${...:-multi_level_default}}" -> "multi_level_default" (multi-level nested) - CASE_EXPECT_EQ("multi_level_default", app_cfg.metadata().service_subset()); - CASE_MSG_INFO() << "metadata.service_subset = " << app_cfg.metadata().service_subset() << '\n'; + // metadata.scope: "${...:-${...:-multi_level_default}}" -> "multi_level_default" (multi-level nested) + CASE_EXPECT_EQ("multi_level_default", app_cfg.metadata().scope()); + CASE_MSG_INFO() << "metadata.scope = " << app_cfg.metadata().scope() << '\n'; // metadata.name: "${ATAPP_EXPR_TEST_NESTED_${ATAPP_EXPR_TEST_SUFFIX}}" -> "alpha_nested_value" CASE_EXPECT_EQ("alpha_nested_value", app_cfg.metadata().name()); @@ -672,11 +672,11 @@ CASE_TEST(atapp_configure, expression_conf) { // metadata.namespace_name: "${ATAPP_EXPR_TEST_NAME:+ns_override}" -> "ns_override" CASE_EXPECT_EQ("ns_override", app_cfg.metadata().namespace_name()); - // metadata.uid: "${ATAPP_EXPR_TEST_MISSING_VAR:+should_not_appear}" -> "" - CASE_EXPECT_EQ("", app_cfg.metadata().uid()); + // metadata.kind: "${ATAPP_EXPR_TEST_MISSING_VAR:+should_not_appear}" -> "" + CASE_EXPECT_EQ("", app_cfg.metadata().kind()); - // metadata.service_subset: "${...:-${...:-multi_level_default}}" -> "multi_level_default" - CASE_EXPECT_EQ("multi_level_default", app_cfg.metadata().service_subset()); + // metadata.scope: "${...:-${...:-multi_level_default}}" -> "multi_level_default" + CASE_EXPECT_EQ("multi_level_default", app_cfg.metadata().scope()); // metadata.name: "${ATAPP_EXPR_TEST_NESTED_${ATAPP_EXPR_TEST_SUFFIX}}" -> "beta_nested_value" CASE_EXPECT_EQ("beta_nested_value", app_cfg.metadata().name()); diff --git a/test/case/atapp_direct_connect_test.cpp b/test/case/atapp_direct_connect_test.cpp index 6e012da..4c8d24b 100644 --- a/test/case/atapp_direct_connect_test.cpp +++ b/test/case/atapp_direct_connect_test.cpp @@ -1094,3 +1094,181 @@ CASE_TEST(atapp_direct_connect, direct_prefer_direct_wait_discovery) { } #endif } + +// ============================================================ +// B.9: direct_scope_mismatch_skip_connect +// node1(prod) and node2(other) are in different scopes. +// The scope mismatch must be detected before dialing: neither side may +// establish any atbus connection to the other. +// ============================================================ +CASE_TEST(atapp_direct_connect, direct_scope_mismatch_skip_connect) { + reset_direct_test_context(); + + direct_three_node_apps apps; + if (!apps.init_all()) { + return; + } + + // 部署到不同 scope (等效于 yaml 中配置 metadata.scope) + apps.node1.set_metadata_scope("prod"); + apps.node2.set_metadata_scope("other"); + + apps.wait_for_upstream_connections(); + apps.inject_all_discovery(); + apps.setup_topology(); + apps.create_endpoints(); + + // 给错误实现留出拨号时间: 若未跳过不可达地址, loopback 上注册流程会立即完成 + apps.pump_until([]() { return false; }, std::chrono::seconds(3)); + + // 不可达判定发生在发起连接之前: 双方的 atbus 层都不得存在对端的 endpoint + auto bus1 = apps.node1.get_bus_node(); + auto bus2 = apps.node2.get_bus_node(); + CASE_EXPECT_TRUE(bus1 != nullptr); + CASE_EXPECT_TRUE(bus2 != nullptr); + if (bus1) { + CASE_EXPECT_TRUE(bus1->get_endpoint(apps.node2.get_app_id()) == nullptr); + } + if (bus2) { + CASE_EXPECT_TRUE(bus2->get_endpoint(apps.node1.get_app_id()) == nullptr); + } +} + +// ============================================================ +// B.10: direct_scope_match_connect +// node1 and node2 share scope "prod". The direct connection must be +// established with scope rules advertised and checked at every layer. +// ============================================================ +CASE_TEST(atapp_direct_connect, direct_scope_match_connect) { + reset_direct_test_context(); + + direct_three_node_apps apps; + if (!apps.init_all()) { + return; + } + + // 等效于 yaml 中配置 metadata.scope="prod" 并生效到 atbus 配置 + apps.node1.set_metadata_scope("prod"); + apps.node2.set_metadata_scope("prod"); + for (auto *app : {&apps.node1, &apps.node2}) { + auto bus = app->get_bus_node(); + CASE_EXPECT_TRUE(bus != nullptr); + if (bus) { + const auto &bus_conf = bus->get_conf(); + bus->reload_self_endpoint(app->get_metadata().scope(), bus_conf.namespace_name, bus_conf.node_labels, + gsl::span{bus_conf.gateway.data(), + bus_conf.gateway.size()}); + } + } + + apps.full_setup_and_connect(); + + // scope 匹配时直连必须成功: 双方 atbus 层都存在对端的 endpoint + auto bus1 = apps.node1.get_bus_node(); + auto bus2 = apps.node2.get_bus_node(); + CASE_EXPECT_TRUE(bus1 != nullptr); + CASE_EXPECT_TRUE(bus2 != nullptr); + if (bus1) { + CASE_EXPECT_TRUE(bus1->get_endpoint(apps.node2.get_app_id()) != nullptr); + } + if (bus2) { + CASE_EXPECT_TRUE(bus2->get_endpoint(apps.node1.get_app_id()) != nullptr); + } +} + +// ============================================================ +// B.11: direct_discovery_update_refresh_bus_endpoint +// A discovery update must refresh the atbus endpoint's scope/namespace/ +// labels/gateways. Labels and gateway match_labels keep only inherited +// labels; a peer without configured gateways gets listen-derived rules. +// ============================================================ +CASE_TEST(atapp_direct_connect, direct_discovery_update_refresh_bus_endpoint) { + reset_direct_test_context(); + + direct_three_node_apps apps; + if (!apps.init_all()) { + return; + } + apps.full_setup_and_connect(); + + auto bus1 = apps.node1.get_bus_node(); + CASE_EXPECT_TRUE(bus1 != nullptr); + auto *atapp_ep = apps.node1.get_endpoint(apps.node2.get_app_id()); + CASE_EXPECT_TRUE(atapp_ep != nullptr); + if (!bus1 || nullptr == atapp_ep) { + return; + } + + // 基线: 直连建立后 atbus endpoint 已存在, 身份信息来自注册包(direct 配置无 scope) + CASE_EXPECT_TRUE(bus1->get_endpoint(apps.node2.get_app_id()) != nullptr); + + // discovery 更新: 对端上报新的身份信息和 gateway 配置 + auto updated = atfw::util::memory::make_strong_rc(); + { + atapp::protocol::atapp_discovery info; + apps.node2.pack(info); + info.mutable_metadata()->set_scope("scope-x"); + info.mutable_metadata()->set_namespace_name("ns-x"); + (*info.mutable_metadata()->mutable_labels())["zone"] = "west"; + (*info.mutable_metadata()->mutable_labels())["extra"] = "skip"; + auto *gw = info.add_gateways(); + gw->set_address("ipv4://127.0.0.1:29999"); + gw->set_match_scope("scope-x"); + (*gw->mutable_match_labels())["zone"] = "west"; + (*gw->mutable_match_labels())["extra"] = "skip"; + updated->copy_from(info, atapp::etcd_discovery_node::node_version(), 0); + } + atapp_ep->update_discovery(updated); + + const auto *bus_ep = bus1->get_endpoint(apps.node2.get_app_id()); + CASE_EXPECT_TRUE(bus_ep != nullptr); + if (nullptr == bus_ep) { + return; + } + + // 身份信息以 discovery 为准刷新 + CASE_EXPECT_EQ(std::string("scope-x"), bus_ep->get_scope()); + CASE_EXPECT_EQ(std::string("ns-x"), bus_ep->get_namespace()); + + // labels 只保留本端 inherited_labels 声明的 zone + CASE_EXPECT_EQ(static_cast(1), bus_ep->get_labels().size()); + { + auto label_iter = bus_ep->get_labels().find("zone"); + CASE_EXPECT_TRUE(label_iter != bus_ep->get_labels().end() && label_iter->second == "west"); + } + + // gateway 同步刷新, match_labels 同样只保留继承标签 + CASE_EXPECT_EQ(static_cast(1), bus_ep->get_gateway().size()); + if (!bus_ep->get_gateway().empty()) { + const auto &gw = bus_ep->get_gateway()[0]; + CASE_EXPECT_EQ(std::string("ipv4://127.0.0.1:29999"), gw.address); + CASE_EXPECT_EQ(std::string("scope-x"), gw.match_scope); + CASE_EXPECT_EQ(static_cast(1), gw.match_labels.size()); + auto label_iter = gw.match_labels.find("zone"); + CASE_EXPECT_TRUE(label_iter != gw.match_labels.end() && label_iter->second == "west"); + CASE_EXPECT_TRUE(gw.match_labels.end() == gw.match_labels.find("extra")); + } + + // discovery 更新: 对端不再配置 gateway, 必须按 listen 地址合成隔离规则 + auto listen_only = atfw::util::memory::make_strong_rc(); + atapp::protocol::atapp_discovery listen_info; + apps.node2.pack(listen_info); + listen_info.mutable_metadata()->set_scope("scope-y"); + listen_info.mutable_metadata()->set_namespace_name("ns-y"); + listen_only->copy_from(listen_info, atapp::etcd_discovery_node::node_version(), 0); + atapp_ep->update_discovery(listen_only); + + bus_ep = bus1->get_endpoint(apps.node2.get_app_id()); + CASE_EXPECT_TRUE(bus_ep != nullptr); + if (nullptr == bus_ep) { + return; + } + CASE_EXPECT_EQ(std::string("scope-y"), bus_ep->get_scope()); + CASE_EXPECT_EQ(static_cast(listen_info.listen_size()), bus_ep->get_gateway().size()); + for (int i = 0; i < listen_info.listen_size() && static_cast(i) < bus_ep->get_gateway().size(); ++i) { + const auto &gw = bus_ep->get_gateway()[static_cast(i)]; + CASE_EXPECT_EQ(listen_info.listen(i), gw.address); + CASE_EXPECT_EQ(std::string("scope-y"), gw.match_scope); + CASE_EXPECT_TRUE(gw.match_namespaces.end() != gw.match_namespaces.find("ns-y")); + } +} diff --git a/test/case/atapp_discovery_test.cpp b/test/case/atapp_discovery_test.cpp index 4041ef7..9a9eaec 100644 --- a/test/case/atapp_discovery_test.cpp +++ b/test/case/atapp_discovery_test.cpp @@ -50,7 +50,7 @@ CASE_TEST(atapp_discovery, metadata_filter) { metadata.set_api_version("v1"); metadata.set_kind("unit test"); metadata.set_group("atapp_discovery"); - metadata.set_service_subset("next"); + metadata.set_scope("next"); (*metadata.mutable_labels())["label1"] = "value1"; (*metadata.mutable_labels())["label2"] = "value2"; @@ -92,12 +92,12 @@ CASE_TEST(atapp_discovery, metadata_filter) { CASE_EXPECT_FALSE(etcd_discovery_set::metadata_equal_type::filter(rule, metadata)); rule.set_group(metadata.group()); - // partly match - service_subset - rule.clear_service_subset(); + // partly match - scope + rule.clear_scope(); CASE_EXPECT_TRUE(etcd_discovery_set::metadata_equal_type::filter(rule, metadata)); - rule.set_service_subset("mismatch value"); + rule.set_scope("mismatch value"); CASE_EXPECT_FALSE(etcd_discovery_set::metadata_equal_type::filter(rule, metadata)); - rule.set_service_subset(metadata.service_subset()); + rule.set_scope(metadata.scope()); // labels { @@ -129,7 +129,7 @@ CASE_TEST(atapp_discovery, get_discovery_by_metadata) { discovery_data.mutable_metadata()->set_api_version("v1"); discovery_data.mutable_metadata()->set_kind("unit test"); discovery_data.mutable_metadata()->set_group("atapp_discovery"); - discovery_data.mutable_metadata()->set_service_subset("next"); + discovery_data.mutable_metadata()->set_scope("next"); (*discovery_data.mutable_metadata()->mutable_labels())["label1"] = "value1"; (*discovery_data.mutable_metadata()->mutable_labels())["label2"] = "value2"; @@ -604,6 +604,73 @@ CASE_TEST(atapp_discovery, discovery_node_ingress_round_robin) { CASE_EXPECT_EQ("ipv6://[::1]:9000", ingress.address()); } +// H.2.6 next_ingress_gateway() listen fallback carries the publisher's scope/namespace isolation rules +CASE_TEST(atapp_discovery, discovery_node_ingress_listen_fallback_scope_rules) { + atapp::etcd_discovery_node::node_version fake_version; + fake_version.create_revision = 1; + fake_version.modify_revision = 1; + fake_version.version = 1; + + // 未配置 gateway 时, listen 回退地址必须带上发布方的 scope/namespace 隔离规则, + // 与 libatbus 注册时通告的规则一致, 调用方据此跳过不可达地址 + auto node = atfw::util::memory::make_strong_rc(); + atapp::protocol::atapp_discovery fake_info; + fake_info.set_id(302); + fake_info.set_name("listen-isolated-node"); + fake_info.mutable_metadata()->set_scope("prod"); + fake_info.mutable_metadata()->set_namespace_name("game"); + fake_info.add_listen("ipv4://10.0.0.1:8001"); + fake_info.add_listen("ipv4://10.0.0.2:8002"); + node->copy_from(fake_info, fake_version, 0); + + CASE_EXPECT_EQ(2, node->get_ingress_size()); + + const auto &first = node->next_ingress_gateway(); + CASE_EXPECT_EQ("ipv4://10.0.0.1:8001", first.address()); + CASE_EXPECT_EQ("prod", first.match_scope()); + CASE_EXPECT_EQ(1, first.match_namespaces_size()); + if (first.match_namespaces_size() > 0) { + CASE_EXPECT_EQ("game", first.match_namespaces(0)); + } + + // 轮询到下一个 listen 地址时不得累积上一次的匹配规则 + const auto &second = node->next_ingress_gateway(); + CASE_EXPECT_EQ("ipv4://10.0.0.2:8002", second.address()); + CASE_EXPECT_EQ("prod", second.match_scope()); + CASE_EXPECT_EQ(1, second.match_namespaces_size()); + + // 无元数据的节点回退地址保持通配 + auto wildcard_node = atfw::util::memory::make_strong_rc(); + atapp::protocol::atapp_discovery wildcard_info; + wildcard_info.set_id(303); + wildcard_info.set_name("listen-wildcard-node"); + wildcard_info.add_listen("ipv4://10.0.0.3:8003"); + wildcard_node->copy_from(wildcard_info, fake_version, 0); + + const auto &wildcard_ingress = wildcard_node->next_ingress_gateway(); + CASE_EXPECT_EQ("ipv4://10.0.0.3:8003", wildcard_ingress.address()); + CASE_EXPECT_TRUE(wildcard_ingress.match_scope().empty()); + CASE_EXPECT_EQ(0, wildcard_ingress.match_namespaces_size()); + + // 配置了 gateway 的节点原样返回配置的 gateway, 不附加 listen 隔离规则 + auto gateway_node = atfw::util::memory::make_strong_rc(); + atapp::protocol::atapp_discovery gateway_info; + gateway_info.set_id(304); + gateway_info.set_name("gateway-node"); + gateway_info.mutable_metadata()->set_scope("prod"); + gateway_info.mutable_metadata()->set_namespace_name("game"); + auto *gw = gateway_info.add_gateways(); + gw->set_address("ipv4://10.0.0.4:8004"); + gateway_info.add_listen("ipv4://10.0.0.5:8005"); + gateway_node->copy_from(gateway_info, fake_version, 0); + + CASE_EXPECT_EQ(1, gateway_node->get_ingress_size()); + const auto &configured_ingress = gateway_node->next_ingress_gateway(); + CASE_EXPECT_EQ("ipv4://10.0.0.4:8004", configured_ingress.address()); + CASE_EXPECT_TRUE(configured_ingress.match_scope().empty()); + CASE_EXPECT_EQ(0, configured_ingress.match_namespaces_size()); +} + // H.2.5 All query operations on empty set return nullptr/empty without crash CASE_TEST(atapp_discovery, discovery_empty_set_operations) { auto discovery_set = atfw::util::memory::make_strong_rc(); diff --git a/test/case/atapp_setup_test.cpp b/test/case/atapp_setup_test.cpp index 2e0830f..a81bc27 100644 --- a/test/case/atapp_setup_test.cpp +++ b/test/case/atapp_setup_test.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include #include