GcpObservability: Code for reading config from env var (#30889)

* GcpObservability: Code for reading config from env var

* Comments

* Fix deps

* clang-tidy
pull/30905/head^2
Yash Tibrewal 2 years ago committed by GitHub
parent a27073f7a8
commit f7f4c9b584
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      src/cpp/ext/gcp/BUILD
  2. 90
      src/cpp/ext/gcp/observability_config.cc
  3. 8
      src/cpp/ext/gcp/observability_config.h
  4. 133
      test/cpp/ext/gcp/observability_config_test.cc
  5. 1
      tools/distrib/fix_build_deps.py

@ -53,14 +53,26 @@ grpc_cc_library(
grpc_cc_library(
name = "observability_config",
srcs = [
"observability_config.cc",
],
hdrs = [
"observability_config.h",
],
external_deps = [
"absl/status",
"absl/status:statusor",
"absl/strings",
],
language = "c++",
visibility = ["//test:__subpackages__"],
deps = [
"//:gpr",
"//:grpc_base",
"//:grpc_public_hdrs",
"//:json",
"//:json_args",
"//:json_object_loader",
"//:slice_refcount",
],
)

@ -0,0 +1,90 @@
//
// 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 <grpc/support/port_platform.h>
#include "src/cpp/ext/gcp/observability_config.h"
#include <memory>
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include <grpc/slice.h>
#include <grpc/status.h>
#include "src/core/lib/gpr/env.h"
#include "src/core/lib/gprpp/memory.h"
#include "src/core/lib/iomgr/error.h"
#include "src/core/lib/iomgr/load_file.h"
#include "src/core/lib/json/json.h"
#include "src/core/lib/slice/slice_internal.h"
#include "src/core/lib/slice/slice_refcount.h"
#include "src/core/lib/transport/error_utils.h"
namespace grpc {
namespace internal {
namespace {
// Loads the contents of the file pointed by env var
// GRPC_OBSERVABILITY_CONFIG_FILE. If unset, falls back to the contents of
// GRPC_OBSERVABILITY_CONFIG.
absl::StatusOr<std::string> GetGcpObservabilityConfigContents() {
// First, try GRPC_OBSERVABILITY_CONFIG_FILE
std::string contents_str;
grpc_core::UniquePtr<char> path(gpr_getenv("GRPC_OBSERVABILITY_CONFIG_FILE"));
if (path != nullptr) {
grpc_slice contents;
grpc_error_handle error =
grpc_load_file(path.get(), /*add_null_terminator=*/true, &contents);
if (!GRPC_ERROR_IS_NONE(error)) {
return grpc_error_to_absl_status(grpc_error_set_int(
error, GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_FAILED_PRECONDITION));
}
std::string contents_str(grpc_core::StringViewFromSlice(contents));
grpc_slice_unref_internal(contents);
return contents_str;
}
// Next, try GRPC_OBSERVABILITY_CONFIG env var.
grpc_core::UniquePtr<char> env_config(
gpr_getenv("GRPC_OBSERVABILITY_CONFIG"));
if (env_config != nullptr) {
return env_config.get();
}
// No observability config found.
return absl::FailedPreconditionError(
"Environment variables GRPC_OBSERVABILITY_CONFIG_FILE or "
"GRPC_OBSERVABILITY_CONFIG "
"not defined");
}
} // namespace
absl::StatusOr<GcpObservabilityConfig> GcpObservabilityConfig::ReadFromEnv() {
auto config_contents = GetGcpObservabilityConfigContents();
if (!config_contents.ok()) {
return config_contents.status();
}
auto config_json = grpc_core::Json::Parse(*config_contents);
if (!config_json.ok()) {
return config_json.status();
}
return grpc_core::LoadFromJson<GcpObservabilityConfig>(*config_json);
}
} // namespace internal
} // namespace grpc

@ -19,6 +19,8 @@
#include <string>
#include "absl/status/statusor.h"
#include "src/core/lib/json/json_args.h"
#include "src/core/lib/json/json_object_loader.h"
@ -85,6 +87,12 @@ struct GcpObservabilityConfig {
.Finish();
return loader;
}
// Tries to load the contents of GcpObservabilityConfig from the file located
// by the value of environment variable `GRPC_OBSERVABILITY_CONFIG_FILE`. If
// `GRPC_OBSERVABILITY_CONFIG_FILE` is unset, falls back to
// `GRPC_OBSERVABILITY_CONFIG`.
static absl::StatusOr<GcpObservabilityConfig> ReadFromEnv();
};
} // namespace internal

@ -19,6 +19,10 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <grpc/support/alloc.h>
#include "src/core/lib/gpr/env.h"
#include "src/core/lib/gpr/tmpfile.h"
#include "test/core/util/test_config.h"
namespace grpc {
@ -67,6 +71,135 @@ TEST(GcpObservabilityConfigJsonParsingTest, Defaults) {
EXPECT_TRUE(config.project_id.empty());
}
TEST(GcpEnvParsingTest, NoEnvironmentVariableSet) {
auto config = GcpObservabilityConfig::ReadFromEnv();
EXPECT_EQ(config.status(),
absl::FailedPreconditionError(
"Environment variables GRPC_OBSERVABILITY_CONFIG_FILE or "
"GRPC_OBSERVABILITY_CONFIG "
"not defined"));
}
TEST(GcpEnvParsingTest, ConfigFileDoesNotExist) {
gpr_setenv("GRPC_OBSERVABILITY_CONFIG_FILE",
"/tmp/gcp_observability_config_does_not_exist");
auto config = GcpObservabilityConfig::ReadFromEnv();
EXPECT_EQ(config.status(),
absl::FailedPreconditionError("Failed to load file"));
gpr_unsetenv("GRPC_OBSERVABILITY_CONFIG_FILE");
}
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) {
gpr_unsetenv("GRPC_OBSERVABILITY_CONFIG_FILE");
remove(tmp_file_name);
gpr_free(tmp_file_name);
}
} else if (GetParam().config_source() ==
EnvParsingTestType::ConfigSource::kEnvVar) {
gpr_unsetenv("GRPC_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);
gpr_setenv("GRPC_OBSERVABILITY_CONFIG_FILE", tmp_file_name);
} else if (GetParam().config_source() ==
EnvParsingTestType::ConfigSource::kEnvVar) {
gpr_setenv("GRPC_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();
}
// 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

@ -395,6 +395,7 @@ args = parser.parse_args()
for dirname in [
"",
"src/cpp/ext/gcp",
"test/core/uri",
"test/core/util",
"test/core/end2end",

Loading…
Cancel
Save