mirror of https://github.com/grpc/grpc.git
The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)
https://grpc.io/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
457 lines
16 KiB
457 lines
16 KiB
// |
|
// Copyright 2022 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/gcp/observability_config.h" |
|
|
|
#include "gmock/gmock.h" |
|
#include "gtest/gtest.h" |
|
|
|
#include <grpc/support/alloc.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/core/lib/json/json_reader.h" |
|
#include "test/core/util/test_config.h" |
|
|
|
namespace grpc { |
|
namespace internal { |
|
namespace { |
|
|
|
TEST(GcpObservabilityConfigJsonParsingTest, Basic) { |
|
const char* json_str = R"json({ |
|
"cloud_logging": { |
|
"client_rpc_events": [ |
|
{ |
|
"methods": ["google.pubsub.v1.Subscriber/Acknowledge", "google.pubsub.v1.Publisher/CreateTopic"], |
|
"exclude": true |
|
}, |
|
{ |
|
"methods": ["google.pubsub.v1.Subscriber/*", "google.pubsub.v1.Publisher/*"], |
|
"max_metadata_bytes": 4096, |
|
"max_message_bytes": 4096 |
|
}], |
|
"server_rpc_events": [ |
|
{ |
|
"methods": ["*"], |
|
"max_metadata_bytes": 4096, |
|
"max_message_bytes": 4096 |
|
} |
|
] |
|
}, |
|
"cloud_monitoring": {}, |
|
"cloud_trace": { |
|
"sampling_rate": 0.05 |
|
}, |
|
"project_id": "project", |
|
"labels": { |
|
"SOURCE_VERSION": "v1", |
|
"SERVICE_NAME": "payment-service", |
|
"DATA_CENTER": "us-west1-a" |
|
} |
|
})json"; |
|
auto json = grpc_core::JsonParse(json_str); |
|
ASSERT_TRUE(json.ok()) << json.status(); |
|
grpc_core::ValidationErrors errors; |
|
auto config = grpc_core::LoadFromJson<GcpObservabilityConfig>( |
|
*json, grpc_core::JsonArgs(), &errors); |
|
ASSERT_TRUE(errors.ok()) << errors.status(absl::StatusCode::kInvalidArgument, |
|
"unexpected errors"); |
|
ASSERT_TRUE(config.cloud_logging.has_value()); |
|
ASSERT_EQ(config.cloud_logging->client_rpc_events.size(), 2); |
|
EXPECT_THAT(config.cloud_logging->client_rpc_events[0].qualified_methods, |
|
::testing::ElementsAre("google.pubsub.v1.Subscriber/Acknowledge", |
|
"google.pubsub.v1.Publisher/CreateTopic")); |
|
EXPECT_TRUE(config.cloud_logging->client_rpc_events[0].exclude); |
|
EXPECT_EQ(config.cloud_logging->client_rpc_events[0].max_metadata_bytes, 0); |
|
EXPECT_EQ(config.cloud_logging->client_rpc_events[0].max_message_bytes, 0); |
|
EXPECT_THAT(config.cloud_logging->client_rpc_events[1].qualified_methods, |
|
::testing::ElementsAre("google.pubsub.v1.Subscriber/*", |
|
"google.pubsub.v1.Publisher/*")); |
|
EXPECT_FALSE(config.cloud_logging->client_rpc_events[1].exclude); |
|
EXPECT_EQ(config.cloud_logging->client_rpc_events[1].max_metadata_bytes, |
|
4096); |
|
EXPECT_EQ(config.cloud_logging->client_rpc_events[1].max_message_bytes, 4096); |
|
ASSERT_EQ(config.cloud_logging->server_rpc_events.size(), 1); |
|
EXPECT_THAT(config.cloud_logging->server_rpc_events[0].qualified_methods, |
|
::testing::ElementsAre("*")); |
|
EXPECT_EQ(config.cloud_logging->server_rpc_events[0].max_metadata_bytes, |
|
4096); |
|
EXPECT_EQ(config.cloud_logging->server_rpc_events[0].max_message_bytes, 4096); |
|
EXPECT_TRUE(config.cloud_monitoring.has_value()); |
|
EXPECT_TRUE(config.cloud_trace.has_value()); |
|
EXPECT_FLOAT_EQ(config.cloud_trace->sampling_rate, 0.05); |
|
EXPECT_EQ(config.project_id, "project"); |
|
EXPECT_THAT(config.labels, |
|
::testing::UnorderedElementsAre( |
|
::testing::Pair("SOURCE_VERSION", "v1"), |
|
::testing::Pair("SERVICE_NAME", "payment-service"), |
|
::testing::Pair("DATA_CENTER", "us-west1-a"))); |
|
} |
|
|
|
TEST(GcpObservabilityConfigJsonParsingTest, Defaults) { |
|
const char* json_str = R"json({ |
|
})json"; |
|
auto json = grpc_core::JsonParse(json_str); |
|
ASSERT_TRUE(json.ok()) << json.status(); |
|
grpc_core::ValidationErrors errors; |
|
auto config = grpc_core::LoadFromJson<GcpObservabilityConfig>( |
|
*json, grpc_core::JsonArgs(), &errors); |
|
ASSERT_TRUE(errors.ok()) << errors.status(absl::StatusCode::kInvalidArgument, |
|
"unexpected errors"); |
|
EXPECT_FALSE(config.cloud_logging.has_value()); |
|
EXPECT_FALSE(config.cloud_monitoring.has_value()); |
|
EXPECT_FALSE(config.cloud_trace.has_value()); |
|
EXPECT_TRUE(config.project_id.empty()); |
|
EXPECT_TRUE(config.labels.empty()); |
|
} |
|
|
|
TEST(GcpObservabilityConfigJsonParsingTest, LoggingConfigMethodIllegalSlashes) { |
|
const char* json_str = R"json({ |
|
"cloud_logging": { |
|
"client_rpc_events": [ |
|
{ |
|
"methods": ["servicemethod", "service/method/foo"] |
|
} |
|
] |
|
} |
|
})json"; |
|
auto json = grpc_core::JsonParse(json_str); |
|
ASSERT_TRUE(json.ok()) << json.status(); |
|
grpc_core::ValidationErrors errors; |
|
auto config = grpc_core::LoadFromJson<GcpObservabilityConfig>( |
|
*json, grpc_core::JsonArgs(), &errors); |
|
EXPECT_THAT(errors.status(absl::StatusCode::kInvalidArgument, "Parsing error") |
|
.ToString(), |
|
::testing::AllOf( |
|
::testing::HasSubstr( |
|
"field:cloud_logging.client_rpc_events[0].methods[0]" |
|
" error:Illegal methods[] configuration"), |
|
::testing::HasSubstr( |
|
"field:cloud_logging.client_rpc_events[0].methods[1] " |
|
"error:methods[] can have at most a single '/'"))); |
|
} |
|
|
|
TEST(GcpObservabilityConfigJsonParsingTest, LoggingConfigEmptyMethod) { |
|
const char* json_str = R"json({ |
|
"cloud_logging": { |
|
"client_rpc_events": [ |
|
{ |
|
"methods": [""] |
|
} |
|
] |
|
} |
|
})json"; |
|
auto json = grpc_core::JsonParse(json_str); |
|
ASSERT_TRUE(json.ok()) << json.status(); |
|
grpc_core::ValidationErrors errors; |
|
auto config = grpc_core::LoadFromJson<GcpObservabilityConfig>( |
|
*json, grpc_core::JsonArgs(), &errors); |
|
EXPECT_THAT( |
|
errors.status(absl::StatusCode::kInvalidArgument, "Parsing error") |
|
.ToString(), |
|
::testing::HasSubstr("field:cloud_logging.client_rpc_events[0].methods[0]" |
|
" error:Empty configuration")); |
|
} |
|
|
|
TEST(GcpObservabilityConfigJsonParsingTest, LoggingConfigWildcardEntries) { |
|
const char* json_str = R"json({ |
|
"cloud_logging": { |
|
"client_rpc_events": [ |
|
{ |
|
"methods": ["*", "service/*"] |
|
} |
|
], |
|
"server_rpc_events": [ |
|
{ |
|
"methods": ["*", "service/*"] |
|
} |
|
] |
|
} |
|
})json"; |
|
auto json = grpc_core::JsonParse(json_str); |
|
ASSERT_TRUE(json.ok()) << json.status(); |
|
grpc_core::ValidationErrors errors; |
|
auto config = grpc_core::LoadFromJson<GcpObservabilityConfig>( |
|
*json, grpc_core::JsonArgs(), &errors); |
|
ASSERT_TRUE(errors.ok()) << errors.status(absl::StatusCode::kInvalidArgument, |
|
"unexpected errors"); |
|
ASSERT_TRUE(config.cloud_logging.has_value()); |
|
ASSERT_EQ(config.cloud_logging->client_rpc_events.size(), 1); |
|
EXPECT_THAT(config.cloud_logging->client_rpc_events[0].qualified_methods, |
|
::testing::ElementsAre("*", "service/*")); |
|
ASSERT_EQ(config.cloud_logging->server_rpc_events.size(), 1); |
|
EXPECT_THAT(config.cloud_logging->server_rpc_events[0].qualified_methods, |
|
::testing::ElementsAre("*", "service/*")); |
|
} |
|
|
|
TEST(GcpObservabilityConfigJsonParsingTest, |
|
LoggingConfigIncorrectWildcardSpecs) { |
|
const char* json_str = R"json({ |
|
"cloud_logging": { |
|
"client_rpc_events": [ |
|
{ |
|
"methods": ["*"], |
|
"exclude": true |
|
}, |
|
{ |
|
"methods": ["*/method", "service/*blah"], |
|
"exclude": true |
|
} |
|
] |
|
} |
|
})json"; |
|
auto json = grpc_core::JsonParse(json_str); |
|
ASSERT_TRUE(json.ok()) << json.status(); |
|
grpc_core::ValidationErrors errors; |
|
auto config = grpc_core::LoadFromJson<GcpObservabilityConfig>( |
|
*json, grpc_core::JsonArgs(), &errors); |
|
EXPECT_THAT( |
|
errors.status(absl::StatusCode::kInvalidArgument, "Parsing error") |
|
.ToString(), |
|
::testing::AllOf( |
|
::testing::HasSubstr( |
|
"field:cloud_logging.client_rpc_events[0].methods[0]" |
|
" error:Wildcard match '*' not allowed when 'exclude' is set"), |
|
::testing::HasSubstr( |
|
"field:cloud_logging.client_rpc_events[1].methods[0] " |
|
"error:Configuration of type '*/method' not allowed"), |
|
::testing::HasSubstr( |
|
"field:cloud_logging.client_rpc_events[1].methods[1] " |
|
"error:Wildcard specified for method in incorrect manner"))); |
|
} |
|
|
|
TEST(GcpObservabilityConfigJsonParsingTest, SamplingRateDefaults) { |
|
const char* json_str = R"json({ |
|
"cloud_trace": { |
|
"sampling_rate": 0.05 |
|
} |
|
})json"; |
|
auto json = grpc_core::JsonParse(json_str); |
|
ASSERT_TRUE(json.ok()) << json.status(); |
|
grpc_core::ValidationErrors errors; |
|
auto config = grpc_core::LoadFromJson<GcpObservabilityConfig>( |
|
*json, grpc_core::JsonArgs(), &errors); |
|
ASSERT_TRUE(errors.ok()) << errors.status(absl::StatusCode::kInvalidArgument, |
|
"unexpected errors"); |
|
ASSERT_TRUE(config.cloud_trace.has_value()); |
|
EXPECT_FLOAT_EQ(config.cloud_trace->sampling_rate, 0.05); |
|
} |
|
|
|
TEST(GcpEnvParsingTest, NoEnvironmentVariableSet) { |
|
auto config = GcpObservabilityConfig::ReadFromEnv(); |
|
EXPECT_EQ(config.status(), |
|
absl::FailedPreconditionError( |
|
"Environment variables GRPC_GCP_OBSERVABILITY_CONFIG_FILE or " |
|
"GRPC_GCP_OBSERVABILITY_CONFIG " |
|
"not defined")); |
|
} |
|
|
|
TEST(GcpEnvParsingTest, ConfigFileDoesNotExist) { |
|
grpc_core::SetEnv("GRPC_GCP_OBSERVABILITY_CONFIG_FILE", |
|
"/tmp/gcp_observability_config_does_not_exist"); |
|
|
|
auto config = GcpObservabilityConfig::ReadFromEnv(); |
|
|
|
EXPECT_EQ(config.status(), |
|
absl::FailedPreconditionError("Failed to load file")); |
|
|
|
grpc_core::UnsetEnv("GRPC_GCP_OBSERVABILITY_CONFIG_FILE"); |
|
} |
|
|
|
TEST(GcpEnvParsingTest, ProjectIdNotSet) { |
|
grpc_core::SetEnv("GRPC_GCP_OBSERVABILITY_CONFIG", "{}"); |
|
|
|
auto config = GcpObservabilityConfig::ReadFromEnv(); |
|
EXPECT_EQ(config.status(), |
|
absl::FailedPreconditionError("GCP Project ID not found.")); |
|
|
|
grpc_core::UnsetEnv("GRPC_GCP_OBSERVABILITY_CONFIG"); |
|
grpc_core::CoreConfiguration::Reset(); |
|
} |
|
|
|
TEST(GcpEnvParsingTest, ProjectIdFromGcpProjectEnvVar) { |
|
grpc_core::SetEnv("GRPC_GCP_OBSERVABILITY_CONFIG", "{}"); |
|
grpc_core::SetEnv("GCP_PROJECT", "gcp_project"); |
|
|
|
auto config = GcpObservabilityConfig::ReadFromEnv(); |
|
EXPECT_TRUE(config.ok()); |
|
EXPECT_EQ(config->project_id, "gcp_project"); |
|
|
|
grpc_core::UnsetEnv("GCP_PROJECT"); |
|
grpc_core::UnsetEnv("GRPC_GCP_OBSERVABILITY_CONFIG"); |
|
grpc_core::CoreConfiguration::Reset(); |
|
} |
|
|
|
TEST(GcpEnvParsingTest, ProjectIdFromGcloudProjectEnvVar) { |
|
grpc_core::SetEnv("GRPC_GCP_OBSERVABILITY_CONFIG", "{}"); |
|
grpc_core::SetEnv("GCLOUD_PROJECT", "gcloud_project"); |
|
|
|
auto config = GcpObservabilityConfig::ReadFromEnv(); |
|
EXPECT_TRUE(config.ok()); |
|
EXPECT_EQ(config->project_id, "gcloud_project"); |
|
|
|
grpc_core::UnsetEnv("GCLOUD_PROJECT"); |
|
grpc_core::UnsetEnv("GRPC_GCP_OBSERVABILITY_CONFIG"); |
|
grpc_core::CoreConfiguration::Reset(); |
|
} |
|
|
|
TEST(GcpEnvParsingTest, ProjectIdFromGoogleCloudProjectEnvVar) { |
|
grpc_core::SetEnv("GRPC_GCP_OBSERVABILITY_CONFIG", "{}"); |
|
grpc_core::SetEnv("GOOGLE_CLOUD_PROJECT", "google_cloud_project"); |
|
|
|
auto config = GcpObservabilityConfig::ReadFromEnv(); |
|
EXPECT_TRUE(config.ok()); |
|
EXPECT_EQ(config->project_id, "google_cloud_project"); |
|
|
|
grpc_core::UnsetEnv("GOOGLE_CLOUD_PROJECT"); |
|
grpc_core::UnsetEnv("GRPC_GCP_OBSERVABILITY_CONFIG"); |
|
grpc_core::CoreConfiguration::Reset(); |
|
} |
|
|
|
class EnvParsingTestType { |
|
public: |
|
enum class ConfigSource { |
|
kFile, |
|
kEnvVar, |
|
}; |
|
|
|
EnvParsingTestType& set_config_source(ConfigSource config_source) { |
|
config_source_ = config_source; |
|
return *this; |
|
} |
|
|
|
ConfigSource config_source() const { return config_source_; } |
|
|
|
std::string ToString() const { |
|
std::string ret_val; |
|
if (config_source_ == ConfigSource::kFile) { |
|
absl::StrAppend(&ret_val, "ConfigFromFile"); |
|
} else if (config_source_ == ConfigSource::kEnvVar) { |
|
absl::StrAppend(&ret_val, "ConfigFromEnvVar"); |
|
} |
|
return ret_val; |
|
} |
|
|
|
static std::string Name( |
|
const ::testing::TestParamInfo<EnvParsingTestType>& info) { |
|
return info.param.ToString(); |
|
} |
|
|
|
private: |
|
ConfigSource config_source_; |
|
}; |
|
|
|
class EnvParsingTest : public ::testing::TestWithParam<EnvParsingTestType> { |
|
protected: |
|
~EnvParsingTest() override { |
|
if (GetParam().config_source() == EnvParsingTestType::ConfigSource::kFile) { |
|
if (tmp_file_name != nullptr) { |
|
grpc_core::UnsetEnv("GRPC_GCP_OBSERVABILITY_CONFIG_FILE"); |
|
remove(tmp_file_name); |
|
gpr_free(tmp_file_name); |
|
} |
|
} else if (GetParam().config_source() == |
|
EnvParsingTestType::ConfigSource::kEnvVar) { |
|
grpc_core::UnsetEnv("GRPC_GCP_OBSERVABILITY_CONFIG"); |
|
} |
|
} |
|
|
|
void SetConfig(const char* json) { |
|
if (GetParam().config_source() == EnvParsingTestType::ConfigSource::kFile) { |
|
ASSERT_EQ(tmp_file_name, nullptr); |
|
FILE* tmp_config_file = |
|
gpr_tmpfile("gcp_observability_config", &tmp_file_name); |
|
fputs(json, tmp_config_file); |
|
fclose(tmp_config_file); |
|
grpc_core::SetEnv("GRPC_GCP_OBSERVABILITY_CONFIG_FILE", tmp_file_name); |
|
} else if (GetParam().config_source() == |
|
EnvParsingTestType::ConfigSource::kEnvVar) { |
|
grpc_core::SetEnv("GRPC_GCP_OBSERVABILITY_CONFIG", json); |
|
} |
|
} |
|
|
|
private: |
|
char* tmp_file_name = nullptr; |
|
}; |
|
|
|
TEST_P(EnvParsingTest, Basic) { |
|
SetConfig(R"json({ |
|
"project_id": "project" |
|
})json"); |
|
auto config = GcpObservabilityConfig::ReadFromEnv(); |
|
|
|
ASSERT_TRUE(config.ok()); |
|
EXPECT_EQ(config->project_id, "project"); |
|
} |
|
|
|
// Test that JSON parsing errors are propagated as expected. |
|
TEST_P(EnvParsingTest, BadJson) { |
|
SetConfig("{"); |
|
auto config = GcpObservabilityConfig::ReadFromEnv(); |
|
|
|
EXPECT_EQ(config.status().code(), absl::StatusCode::kInvalidArgument); |
|
EXPECT_THAT(config.status().message(), |
|
::testing::HasSubstr("JSON parsing failed")) |
|
<< config.status().message(); |
|
} |
|
|
|
TEST_P(EnvParsingTest, BadJsonEmptyString) { |
|
SetConfig(""); |
|
auto config = GcpObservabilityConfig::ReadFromEnv(); |
|
if (GetParam().config_source() == EnvParsingTestType::ConfigSource::kFile) { |
|
EXPECT_EQ(config.status().code(), absl::StatusCode::kInvalidArgument); |
|
EXPECT_THAT(config.status().message(), |
|
::testing::HasSubstr("JSON parsing failed")) |
|
<< config.status().message(); |
|
} else { |
|
EXPECT_EQ(config.status(), |
|
absl::FailedPreconditionError( |
|
"Environment variables GRPC_GCP_OBSERVABILITY_CONFIG_FILE or " |
|
"GRPC_GCP_OBSERVABILITY_CONFIG not defined")); |
|
} |
|
} |
|
|
|
// Make sure that GCP config errors are propagated as expected. |
|
TEST_P(EnvParsingTest, BadGcpConfig) { |
|
SetConfig(R"json({ |
|
"project_id": 123 |
|
})json"); |
|
auto config = GcpObservabilityConfig::ReadFromEnv(); |
|
|
|
EXPECT_EQ(config.status().code(), absl::StatusCode::kInvalidArgument); |
|
EXPECT_THAT(config.status().message(), |
|
::testing::HasSubstr("field:project_id error:is not a string")) |
|
<< config.status().message(); |
|
} |
|
|
|
INSTANTIATE_TEST_SUITE_P( |
|
GcpObservabilityConfigTest, EnvParsingTest, |
|
::testing::Values(EnvParsingTestType().set_config_source( |
|
EnvParsingTestType::ConfigSource::kFile), |
|
EnvParsingTestType().set_config_source( |
|
EnvParsingTestType::ConfigSource::kEnvVar)), |
|
&EnvParsingTestType::Name); |
|
|
|
} // namespace |
|
} // namespace internal |
|
} // namespace grpc |
|
|
|
int main(int argc, char** argv) { |
|
grpc::testing::TestEnvironment env(&argc, argv); |
|
::testing::InitGoogleTest(&argc, argv); |
|
return RUN_ALL_TESTS(); |
|
}
|
|
|