// // // Copyright 2023 gRPC authors. // // Licensed 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 "src/cpp/ext/csm/metadata_exchange.h" #include "absl/functional/any_invocable.h" #include "api/include/opentelemetry/metrics/provider.h" #include "gmock/gmock.h" #include "google/cloud/opentelemetry/resource_detector.h" #include "gtest/gtest.h" #include "opentelemetry/sdk/metrics/meter_provider.h" #include "opentelemetry/sdk/metrics/metric_reader.h" #include #include "src/core/lib/channel/call_tracer.h" #include "src/core/lib/config/core_configuration.h" #include "src/core/lib/gpr/tmpfile.h" #include "src/core/lib/gprpp/env.h" #include "src/cpp/ext/csm/csm_observability.h" #include "src/cpp/ext/otel/otel_plugin.h" #include "test/core/util/test_config.h" #include "test/cpp/end2end/test_service_impl.h" #include "test/cpp/ext/otel/otel_test_library.h" namespace grpc { namespace testing { namespace { class TestScenario { public: enum class ResourceType : std::uint8_t { kGke, kUnknown }; enum class XdsBootstrapSource : std::uint8_t { kFromFile, kFromConfig }; explicit TestScenario(ResourceType type, XdsBootstrapSource bootstrap_source) : type_(type), bootstrap_source_(bootstrap_source) {} opentelemetry::sdk::resource::Resource GetTestResource() const { switch (type_) { case ResourceType::kGke: return TestGkeResource(); case ResourceType::kUnknown: return TestUnknownResource(); } } static std::string Name(const ::testing::TestParamInfo& info) { std::string ret_val; switch (info.param.type_) { case ResourceType::kGke: ret_val += "Gke"; break; case ResourceType::kUnknown: ret_val += "Unknown"; break; } switch (info.param.bootstrap_source_) { case TestScenario::XdsBootstrapSource::kFromFile: ret_val += "BootstrapFromFile"; break; case TestScenario::XdsBootstrapSource::kFromConfig: ret_val += "BootstrapFromConfig"; break; } return ret_val; } ResourceType type() const { return type_; } XdsBootstrapSource bootstrap_source() const { return bootstrap_source_; } private: static opentelemetry::sdk::resource::Resource TestGkeResource() { opentelemetry::sdk::common::AttributeMap attributes; attributes.SetAttribute("cloud.platform", "gcp_kubernetes_engine"); attributes.SetAttribute("k8s.pod.name", "pod"); attributes.SetAttribute("k8s.container.name", "container"); attributes.SetAttribute("k8s.namespace.name", "namespace"); attributes.SetAttribute("k8s.cluster.name", "cluster"); attributes.SetAttribute("cloud.region", "region"); attributes.SetAttribute("cloud.account.id", "id"); return opentelemetry::sdk::resource::Resource::Create(attributes); } static opentelemetry::sdk::resource::Resource TestUnknownResource() { opentelemetry::sdk::common::AttributeMap attributes; attributes.SetAttribute("cloud.platform", "random"); return opentelemetry::sdk::resource::Resource::Create(attributes); } ResourceType type_; XdsBootstrapSource bootstrap_source_; }; class MetadataExchangeTest : public OTelPluginEnd2EndTest, public ::testing::WithParamInterface { protected: void Init(const absl::flat_hash_set& metric_names) { const char* kBootstrap = "{\"node\": {\"id\": " "\"projects/1234567890/networks/mesh:mesh-id/nodes/" "01234567-89ab-4def-8123-456789abcdef\"}}"; switch (GetParam().bootstrap_source()) { case TestScenario::XdsBootstrapSource::kFromFile: { ASSERT_EQ(bootstrap_file_name_, nullptr); FILE* bootstrap_file = gpr_tmpfile("gcp_observability_config", &bootstrap_file_name_); fputs(kBootstrap, bootstrap_file); fclose(bootstrap_file); grpc_core::SetEnv("GRPC_XDS_BOOTSTRAP", bootstrap_file_name_); break; } case TestScenario::XdsBootstrapSource::kFromConfig: grpc_core::SetEnv("GRPC_XDS_BOOTSTRAP_CONFIG", kBootstrap); break; } OTelPluginEnd2EndTest::Init( metric_names, /*resource=*/GetParam().GetTestResource(), /*labels_injector=*/ std::make_unique( GetParam().GetTestResource().GetAttributes())); } ~MetadataExchangeTest() override { grpc_core::UnsetEnv("GRPC_GCP_OBSERVABILITY_CONFIG"); grpc_core::UnsetEnv("GRPC_XDS_BOOTSTRAP"); if (bootstrap_file_name_ != nullptr) { remove(bootstrap_file_name_); gpr_free(bootstrap_file_name_); } } void VerifyServiceMeshAttributes( const std::map& attributes) { EXPECT_EQ(absl::get(attributes.at("gsm.mesh_id")), "mesh-id"); switch (GetParam().type()) { case TestScenario::ResourceType::kGke: EXPECT_EQ( absl::get(attributes.at("gsm.remote_workload_type")), "gcp_kubernetes_engine"); EXPECT_EQ(absl::get( attributes.at("gsm.remote_workload_pod_name")), "pod"); EXPECT_EQ(absl::get( attributes.at("gsm.remote_workload_container_name")), "container"); EXPECT_EQ(absl::get( attributes.at("gsm.remote_workload_namespace_name")), "namespace"); EXPECT_EQ(absl::get( attributes.at("gsm.remote_workload_cluster_name")), "cluster"); EXPECT_EQ(absl::get( attributes.at("gsm.remote_workload_location")), "region"); EXPECT_EQ(absl::get( attributes.at("gsm.remote_workload_project_id")), "id"); EXPECT_EQ(absl::get( attributes.at("gsm.remote_workload_canonical_service")), "canonical_service"); break; case TestScenario::ResourceType::kUnknown: EXPECT_EQ( absl::get(attributes.at("gsm.remote_workload_type")), "random"); break; } } void VerifyNoServiceMeshAttributes( const std::map& attributes) { EXPECT_EQ(attributes.find("gsm.remote_workload_type"), attributes.end()); } private: char* bootstrap_file_name_ = nullptr; }; TEST_P(MetadataExchangeTest, ClientAttemptStarted) { Init(/*metric_names=*/{ grpc::internal::OTelClientAttemptStartedInstrumentName()}); SendRPC(); const char* kMetricName = "grpc.client.attempt.started"; auto data = ReadCurrentMetricsData( [&](const absl::flat_hash_map< std::string, std::vector>& data) { return !data.contains(kMetricName); }); ASSERT_EQ(data[kMetricName].size(), 1); auto point_data = absl::get_if( &data[kMetricName][0].point_data); ASSERT_NE(point_data, nullptr); auto client_started_value = absl::get_if(&point_data->value_); ASSERT_NE(client_started_value, nullptr); EXPECT_EQ(*client_started_value, 1); const auto& attributes = data[kMetricName][0].attributes.GetAttributes(); EXPECT_EQ(absl::get(attributes.at("grpc.method")), kMethodName); EXPECT_EQ(absl::get(attributes.at("grpc.target")), canonical_server_address_); VerifyNoServiceMeshAttributes(attributes); } TEST_P(MetadataExchangeTest, ClientAttemptDuration) { Init(/*metric_names=*/{ grpc::internal::OTelClientAttemptDurationInstrumentName()}); SendRPC(); const char* kMetricName = "grpc.client.attempt.duration"; auto data = ReadCurrentMetricsData( [&](const absl::flat_hash_map< std::string, std::vector>& data) { return !data.contains(kMetricName); }); ASSERT_EQ(data[kMetricName].size(), 1); auto point_data = absl::get_if( &data[kMetricName][0].point_data); ASSERT_NE(point_data, nullptr); ASSERT_EQ(point_data->count_, 1); const auto& attributes = data[kMetricName][0].attributes.GetAttributes(); EXPECT_EQ(absl::get(attributes.at("grpc.method")), kMethodName); EXPECT_EQ(absl::get(attributes.at("grpc.target")), canonical_server_address_); EXPECT_EQ(absl::get(attributes.at("grpc.status")), "OK"); VerifyServiceMeshAttributes(attributes); } TEST_P(MetadataExchangeTest, ServerCallStarted) { Init( /*metric_names=*/{grpc::internal::OTelServerCallStartedInstrumentName()}); SendRPC(); const char* kMetricName = "grpc.server.call.started"; auto data = ReadCurrentMetricsData( [&](const absl::flat_hash_map< std::string, std::vector>& data) { return !data.contains(kMetricName); }); ASSERT_EQ(data[kMetricName].size(), 1); auto point_data = absl::get_if( &data[kMetricName][0].point_data); ASSERT_NE(point_data, nullptr); ASSERT_EQ(absl::get(point_data->value_), 1); const auto& attributes = data[kMetricName][0].attributes.GetAttributes(); EXPECT_EQ(absl::get(attributes.at("grpc.method")), kMethodName); VerifyNoServiceMeshAttributes(attributes); } TEST_P(MetadataExchangeTest, ServerCallDuration) { Init(/*metric_names=*/{ grpc::internal::OTelServerCallDurationInstrumentName()}); SendRPC(); const char* kMetricName = "grpc.server.call.duration"; auto data = ReadCurrentMetricsData( [&](const absl::flat_hash_map< std::string, std::vector>& data) { return !data.contains(kMetricName); }); ASSERT_EQ(data[kMetricName].size(), 1); auto point_data = absl::get_if( &data[kMetricName][0].point_data); ASSERT_NE(point_data, nullptr); ASSERT_EQ(point_data->count_, 1); const auto& attributes = data[kMetricName][0].attributes.GetAttributes(); EXPECT_EQ(absl::get(attributes.at("grpc.method")), kMethodName); EXPECT_EQ(absl::get(attributes.at("grpc.status")), "OK"); VerifyServiceMeshAttributes(attributes); } INSTANTIATE_TEST_SUITE_P( MetadataExchange, MetadataExchangeTest, ::testing::Values( TestScenario(TestScenario::ResourceType::kGke, TestScenario::XdsBootstrapSource::kFromConfig), TestScenario(TestScenario::ResourceType::kGke, TestScenario::XdsBootstrapSource::kFromFile), TestScenario(TestScenario::ResourceType::kUnknown, TestScenario::XdsBootstrapSource::kFromConfig), TestScenario(TestScenario::ResourceType::kUnknown, TestScenario::XdsBootstrapSource::kFromFile)), &TestScenario::Name); } // namespace } // namespace testing } // namespace grpc int main(int argc, char** argv) { grpc::testing::TestEnvironment env(&argc, argv); ::testing::InitGoogleTest(&argc, argv); grpc_core::SetEnv("GSM_CANONICAL_SERVICE_NAME", "canonical_service"); return RUN_ALL_TESTS(); }