SERVER-114627 remove grpc option (#44601)

GitOrigin-RevId: 9406004781bf1aee9793a542142f8672982b4599
This commit is contained in:
Daniel Moody 2026-01-12 21:56:14 -06:00 committed by MongoDB Bot
parent 2f1b8fdaa1
commit ad27dbb8da
4714 changed files with 26 additions and 1241425 deletions

View File

@ -1,8 +1,5 @@
# TODO(SERVER-81039): Remove once these can be compiled from the root directory.
src/third_party/grpc/dist
src/third_party/abseil-cpp/dist
src/third_party/protobuf/dist
src/third_party/re2/dist
src/third_party/tcmalloc/dist
src/third_party/wiredtiger/dist
bazel/auto_header/.auto_header

View File

@ -116,7 +116,6 @@ common --flag_alias=use_disable_ref_track=//bazel/config:use_disable_ref_track
common --flag_alias=use_wiredtiger=//bazel/config:use_wiredtiger
common --flag_alias=use_glibcxx_debug=//bazel/config:use_glibcxx_debug
common --flag_alias=use_tracing_profiler=//bazel/config:use_tracing_profiler
common --flag_alias=build_otel=//bazel/config:build_otel
common --flag_alias=detect_odr_violations=//bazel/config:detect_odr_violations
common --flag_alias=shared_archive=//bazel/config:shared_archive
common --flag_alias=skip_archive=//bazel/config:skip_archive

View File

@ -111,18 +111,6 @@ local_path_override(
path = "src/third_party/abseil-cpp/dist",
)
bazel_dep(name = "protobuf", version = "", repo_name = "com_google_protobuf")
local_path_override(
module_name = "protobuf",
path = "src/third_party/protobuf/dist",
)
bazel_dep(name = "grpc", version = "", repo_name = "com_github_grpc_grpc")
local_path_override(
module_name = "grpc",
path = "src/third_party/grpc/dist",
)
bazel_dep(name = "buildifier_prebuilt", version = "6.4.0", dev_dependency = True)
single_version_override(
module_name = "buildifier_prebuilt",

50
MODULE.bazel.lock generated
View File

@ -17,8 +17,14 @@
"https://bcr.bazel.build/modules/buildifier_prebuilt/6.4.0/source.json": "83eb01b197ed0b392f797860c9da5ed1bf95f4d0ded994d694a3d44731275916",
"https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84",
"https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8",
"https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4",
"https://bcr.bazel.build/modules/googletest/1.11.0/source.json": "c73d9ef4268c91bd0c1cd88f1f9dfa08e814b1dbe89b5f594a9f08ba0244d206",
"https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc",
"https://bcr.bazel.build/modules/platforms/0.0.9/source.json": "cd74d854bf16a9e002fb2ca7b1a421f4403cda29f824a765acd3a8c56f8d43e6",
"https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7",
"https://bcr.bazel.build/modules/protobuf/21.7/source.json": "bbe500720421e582ff2d18b0802464205138c06056f443184de39fbb8187b09b",
"https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0",
"https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858",
"https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647",
"https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
@ -28,6 +34,7 @@
"https://bcr.bazel.build/modules/rules_java/6.3.0/MODULE.bazel": "a97c7678c19f236a956ad260d59c86e10a463badb7eb2eda787490f4c969b963",
"https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1",
"https://bcr.bazel.build/modules/rules_java/7.6.5/source.json": "a805b889531d1690e3c72a7a7e47a870d00323186a9904b36af83aa3d053ee8d",
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
"https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036",
"https://bcr.bazel.build/modules/rules_jvm_external/5.2/source.json": "10572111995bc349ce31c78f74b3c147f6b3233975c7fa5eff9211f6db0d34d9",
"https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0",
@ -51,9 +58,12 @@
"https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58",
"https://bcr.bazel.build/modules/rules_python/0.31.0/source.json": "a41c836d4065888eef4377f2f27b6eea0fedb9b5adb1bab1970437373fe90dc7",
"https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
"https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
"https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c",
"https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd",
"https://bcr.bazel.build/modules/stardoc/0.6.2/source.json": "d2ff8063b63b4a85e65fe595c4290f99717434fa9f95b4748a79a7d04dfed349",
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/source.json": "f1ef7d3f9e0e26d4b23d1c39b5f5de71f584dd7d1b4ef83d9bbba6ec7a6a6459",
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198",
"https://bcr.bazel.build/modules/zlib/1.3.1/source.json": "61d55210bd9e9b52fe40b438a377ed1e9594703a354ef6f24acc923571613476"
},
@ -441,46 +451,6 @@
"recordedRepoMappingEntries": []
}
},
"@@protobuf~//:non_module_deps.bzl%non_module_deps": {
"general": {
"bzlTransitiveDigest": "Jxv+uyr4jygQkRgK5VHnn/9EY9yprSZPW/D8tq5GIJs=",
"usagesDigest": "wKgqJ8owrH40lhri+GQKifyIxm+nKhbvJbi7Mu8UvUU=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"utf8_range": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"urls": [
"https://github.com/protocolbuffers/utf8_range/archive/de0b4a8ff9b5d4c98108bdfe723291a33c52c54f.zip"
],
"strip_prefix": "utf8_range-de0b4a8ff9b5d4c98108bdfe723291a33c52c54f",
"sha256": "5da960e5e5d92394c809629a03af3c7709d2d3d0ca731dacb3a9fb4bf28f7702"
}
},
"rules_ruby": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"urls": [
"https://github.com/protocolbuffers/rules_ruby/archive/b7f3e9756f3c45527be27bc38840d5a1ba690436.zip"
],
"strip_prefix": "rules_ruby-b7f3e9756f3c45527be27bc38840d5a1ba690436",
"sha256": "347927fd8de6132099fcdc58e8f7eab7bde4eb2fd424546b9cd4f1c6f8f8bad8"
}
}
},
"recordedRepoMappingEntries": [
[
"protobuf~",
"bazel_tools",
"bazel_tools"
]
]
}
},
"@@rules_multitool~//multitool:extension.bzl%multitool": {
"general": {
"bzlTransitiveDigest": "AtvPzG/SAawYMKVVHcMoJq4EXkVPTIhS3AeNwENXp9E=",

View File

@ -19,9 +19,6 @@ filters:
- ".clang-format":
approvers:
- devprod-build
- ".eslint-ignore":
approvers:
- devprod-correctness
- ".eslintrc.yml":
approvers:
- devprod-correctness

View File

@ -27,13 +27,11 @@ a notice will be included in
| [Asio C++ Library] | BSL-1.0 | 1.12.2 | | ✗ |
| [benchmark] | Apache-2.0 | 1.5.2 | | |
| [Boost C++ Libraries] | BSL-1.0 | 1.79.0 | | ✗ |
| [c-ares] | MIT | 1.27.0 | | ✗ |
| [CRoaring] | Apache-2.0 OR MIT | 2.1.2 | | ✗ |
| [Cyrus SASL] | BSD-Attribution-HPND-disclaimer | 2.1.28 | | |
| [fmt] | MIT | 7.1.3 | | ✗ |
| [folly] | Apache-2.0 | 2025.04.21.00 | | ✗ |
| [gperftools] | BSD-3-Clause | 2.9.1 | | ✗ |
| [gRPC (C++)] | Apache-2.0 | 1.59.5 | | ✗ |
| [ICU4C - International Components for Unicode C/C++] | Unicode-3.0 | 57.1 | ✗ | ✗ |
| [immer] | BSL-1.0 | 0b3aaf699b9d6f2e89f8e2b6d1221c307e02bda3 | | ✗ |
| [Intel® Decimal Floating-Point Math Library] | BSD-3-Clause | 2.0.1 | | ✗ |
@ -48,10 +46,8 @@ a notice will be included in
| [Mozilla Firefox ESR] | MPL-2.0 | 128.11.0esr | | ✗ |
| [MurmurHash3] | Public Domain | a6bd3ce7be8ad147ea820a7cf6229a975c0c96bb | | ✗ |
| [PCRE2 - Perl-Compatible Regular Expressions] | BSD-3-Clause WITH PCRE2-exception | 10.40 | | ✗ |
| [Protobuf] | BSD-3-Clause | v25.0 | | ✗ |
| [pypi/ocspbuilder] | MIT | 0.10.2 | | |
| [pypi/ocspresponder] | Apache-2.0 | 0.5.0 | | |
| [re2] | BSD-3-Clause | 2023-11-01 | | ✗ |
| [S2 Geometry Library] | Apache-2.0 | a25c502bda9d7e0274b9e2b7825fbddf13cc0306 | ✗ | ✗ |
| [SafeInt] | MIT | 3.0.26 | | ✗ |
| [snappy] | BSD-3-Clause | 1.1.10 | ✗ | ✗ |
@ -79,7 +75,6 @@ a notice will be included in
[Mozilla Firefox ESR]: https://github.com/mozilla-firefox/firefox.git
[MurmurHash3]: https://github.com/aappleby/smhasher/blob/a6bd3ce/
[PCRE2 - Perl-Compatible Regular Expressions]: https://github.com/pcre2project/pcre2.git
[Protobuf]: https://github.com/protocolbuffers/protobuf.git
[S2 Geometry Library]: https://github.com/google/s2geometry.git
[SafeInt]: https://github.com/dcleblanc/safeint.git
[Snowball Stemming Algorithms (libstemmer)]: http://github.com/snowballstem/snowball.git
@ -87,19 +82,16 @@ a notice will be included in
[WiredTiger]: https://github.com/wiredtiger/wiredtiger.git
[Zstandard (zstd)]: https://github.com/facebook/zstd.git
[benchmark]: https://github.com/google/benchmark.git
[c-ares]: https://github.com/c-ares/c-ares.git
[fmt]: https://github.com/fmtlib/fmt.git
[folly]: https://github.com/facebook/folly.git
[gRPC (C++)]: https://github.com/grpc/grpc.git
[gperftools]: https://github.com/gperftools/gperftools.git
[immer]: https://github.com/arximboldi/immer.git
[libmongocrypt]: https://github.com/mongodb/libmongocrypt.git
[librdkafka - The Apache Kafka C/C++ library]: https://github.com/confluentinc/librdkafka.git
[libunwind]: https://github.com/libunwind/libunwind.git
[linenoise]: https://github.com/antirez/linenoise
[pypi/ocspbuilder]: https://pypi.org/project/ocspbuilder/
[pypi/ocspresponder]: https://pypi.org/project/ocspresponder/
[re2]: https://github.com/google/re2.git
[pypi/ocspbuilder]: https://github.com/wbond/ocspbuilder
[pypi/ocspresponder]: https://github.com/threema-ch/ocspresponder
[snappy]: https://github.com/google/tcmalloc.git
[tcmalloc]: https://github.com/google/tcmalloc.git
[timelib]: https://github.com/derickr/timelib.git

View File

@ -141,35 +141,6 @@ load("@npm//:repositories.bzl", "npm_repositories")
npm_repositories()
# Sub in the system openssl for boringssl since we don't want two implementations of
# ssl in the same address space.
new_local_repository(
name = "boringssl",
build_file_content = """
cc_library(
name = "crypto",
linkopts = ["-lcrypto"],
visibility = ["//visibility:public"],
)
cc_library(
name = "ssl",
linkopts = ["-lssl"],
visibility = ["//visibility:public"],
)
""",
path = "bazel/_openssl_placeholder_for_grpc",
)
# Overloads for the vendored repositories.
#
# WARNING: Don't change the order of the deps() calls and local_repositories.
# They're read linearly dependencies that come first override later
# ones. Dependency updates might change the correct order, though it's
# unlikely. This is obviously a temporary solution and will no longer
# be necessary once migration to bzlmod is complete.
# Note: rules_python is implicitly loaded with a grpc-compatible version.
load("//bazel/install_rules:pigz.bzl", "setup_pigz")
setup_pigz(

View File

@ -9,8 +9,6 @@ load(
"allocator",
"asan",
"build_enterprise",
"build_grpc",
"build_otel",
"compiler_type",
"compress_debug_compile",
"create_dwp",
@ -1262,45 +1260,6 @@ selects.config_setting_group(
],
)
# --------------------------------------
# grpc options
# --------------------------------------
build_grpc(
name = "build_grpc",
build_setting_default = False,
)
config_setting(
name = "build_grpc_enabled",
constraint_values = [
"@platforms//os:linux",
],
flag_values = {
"//bazel/config:build_grpc": "True",
},
)
# --------------------------------------
# otel options
# --------------------------------------
build_otel(
name = "build_otel",
build_setting_default = False,
)
config_setting(
name = "build_otel_enabled",
constraint_values = [
"@platforms//os:linux",
],
flag_values = {
"//bazel/config:build_otel": "True",
"//bazel/config:release": "False",
},
)
# --------------------------------------
# linkstatic options
# --------------------------------------

View File

@ -243,34 +243,6 @@ use_glibcxx_debug = rule(
build_setting = config.bool(flag = True),
)
# =========
# otel
# =========
build_otel_provider = provider(
doc = """Enable building otel and protobuf compiler. This has no effect on non-linux operating systems.""",
fields = ["enabled"],
)
build_otel = rule(
implementation = lambda ctx: build_otel_provider(enabled = ctx.build_setting_value),
build_setting = config.bool(flag = True),
)
# =========
# grpc
# =========
build_grpc_provider = provider(
doc = """Enable building grpc and protobuf compiler. This has no effect on non-linux operating systems.""",
fields = ["enabled"],
)
build_grpc = rule(
implementation = lambda ctx: build_grpc_provider(enabled = ctx.build_setting_value),
build_setting = config.bool(flag = True),
)
# =========
# sanitize
# =========

View File

@ -13,7 +13,6 @@ load(
"get_linkopts",
)
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
load("@com_github_grpc_grpc//bazel:generate_cc.bzl", "generate_cc")
load("@poetry//:dependencies.bzl", "dependency")
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
load("@rules_proto//proto:defs.bzl", "proto_library")
@ -249,7 +248,6 @@ MONGO_GLOBAL_SRC_DEPS = [
"//src/third_party/SafeInt:headers",
"//src/third_party/sasl:windows_sasl",
"//src/third_party/valgrind:headers",
"//src/third_party/abseil-cpp:absl_local_repo_deps",
]
MONGO_GLOBAL_ADDITIONAL_LINKER_INPUTS = SYMBOL_ORDER_FILES
@ -737,13 +735,10 @@ def _mongo_cc_binary_and_test(
# This is used as a tool in part of the shared archive build, so it needs to be marked
# as compatible with a shared archive build.
if name in ["grpc_cpp_plugin", "protobuf_compiler"]:
features = features + ["-pie", "pic"]
else:
target_compatible_with += select({
"//bazel/config:shared_archive_enabled": ["@platforms//:incompatible"],
"//conditions:default": [],
})
target_compatible_with += select({
"//bazel/config:shared_archive_enabled": ["@platforms//:incompatible"],
"//conditions:default": [],
})
args = {
"name": name + WITH_DEBUG_SUFFIX,
@ -1231,64 +1226,6 @@ def mongo_cc_proto_library(
tags = tags + ["gen_source"],
)
def mongo_cc_grpc_library(
name,
srcs,
cc_proto,
deps = [],
grpc_only = True,
proto_only = False,
well_known_protos = False,
generate_mocks = False,
tags = [],
no_undefined_ref_DO_NOT_USE = True,
**kwargs):
codegen_grpc_target = "_" + name + "_grpc_codegen"
# TODO(SERVER-100148): Re-enable sandboxing on protobuf compilation
# once we can rely on //external:grpc_cpp_plugin.
#
# TSAN is currently being applied to protoc which is failing to run
# under Bazel's sandbox due to the system call to disable ASLR
# failing.
#
# To workaround this issue, disable the sandbox only when compiling
# protobufs, since we don't care about threading issues in the
# proto compiler itself.
generate_cc(
name = codegen_grpc_target,
srcs = srcs,
plugin = "//src/third_party/grpc:grpc_cpp_plugin",
well_known_protos = well_known_protos,
generate_mocks = generate_mocks,
tags = tags + ["gen_source"],
disable_sandbox = select({
"//bazel/config:tsan_enabled": True,
"//conditions:default": False,
}),
**kwargs
)
# cc_proto_library tacks on unnecessary link-time dependencies to
# @com_google_protobuf and @com_google_absl, forcefully remove them
# to avoid intefering with thin targets link line generation.
cc_proto_target = "_" + name + "_cc_proto_stripped_deps"
strip_deps(
name = cc_proto_target,
input = cc_proto,
)
mongo_cc_library(
name = name,
srcs = [":" + codegen_grpc_target],
hdrs = [":" + codegen_grpc_target],
deps = deps +
["//src/third_party/grpc:grpc++_codegen_proto"],
cc_deps = [":" + cc_proto_target],
no_undefined_ref_DO_NOT_USE = no_undefined_ref_DO_NOT_USE,
**kwargs
)
def mongo_idl_library(
name,
src,

View File

@ -12,7 +12,6 @@
- featureFlagAlwaysCreateConfigTransactionsPartialIndexOnStepUp
- featureFlagUpdateDocumentShardKeyUsingTransactionApi
- featureFlagAllMongodsAreSharded
- featureFlagGRPC
- featureFlagReplicaSetEndpoint
- featureFlagCreateCollectionInPreparedTransactions
- featureFlagMongodProxyProcolSupport

View File

@ -128,7 +128,6 @@ DEFAULTS = {
"export_mongod_config": "off",
"tls_mode": None,
"tls_ca_file": None,
"shell_grpc": False,
"shell_tls_enabled": False,
"shell_tls_certificate_key_file": None,
"mongos_tls_certificate_key_file": None,
@ -550,8 +549,6 @@ MIXED_BIN_VERSIONS = None
# Specifies the binary version of last-lts or last-continous when multiversion enabled
MULTIVERSION_BIN_VERSION = None
# Specifies whether to use gRPC when connecting via the shell by default.
SHELL_GRPC = None
# Specifies what tlsMode the server(s) should be started with.
TLS_MODE = None

View File

@ -450,7 +450,6 @@ or explicitly pass --installDir to the run subcommand of buildscripts/resmoke.py
_config.TLS_CA_FILE = config.pop("tls_ca_file")
_config.SHELL_TLS_ENABLED = config.pop("shell_tls_enabled")
_config.SHELL_TLS_CERTIFICATE_KEY_FILE = config.pop("shell_tls_certificate_key_file")
_config.SHELL_GRPC = config.pop("shell_grpc")
_config.MONGOD_TLS_CERTIFICATE_KEY_FILE = config.pop("mongod_tls_certificate_key_file")
_config.MONGOS_TLS_CERTIFICATE_KEY_FILE = config.pop("mongos_tls_certificate_key_file")
_config.NUM_SHARDS = config.pop("num_shards")

View File

@ -121,8 +121,6 @@ def mongod_program(logger, job_num, executable, process_kwargs, mongod_options):
remove_set_parameter_if_before_version(suite_set_parameters, "defaultConfigCommandTimeoutMS",
bin_version, "7.3.0")
if "grpcPort" not in mongod_options and suite_set_parameters.get("featureFlagGRPC"):
mongod_options["grpcPort"] = network.PortAllocator.next_fixture_port(job_num)
remove_set_parameter_if_before_version(suite_set_parameters, "internalQueryStatsRateLimit",
bin_version, "7.3.0")
@ -131,8 +129,6 @@ def mongod_program(logger, job_num, executable, process_kwargs, mongod_options):
remove_set_parameter_if_before_version(suite_set_parameters, "enableAutoCompaction",
bin_version, "7.3.0")
if "grpcPort" not in mongod_options and suite_set_parameters.get("featureFlagGRPC"):
mongod_options["grpcPort"] = network.PortAllocator.next_fixture_port(job_num)
_apply_set_parameters(args, suite_set_parameters)
final_mongod_options = mongod_options.copy()
@ -184,16 +180,12 @@ def mongos_program(logger, job_num, executable=None, process_kwargs=None, mongos
remove_set_parameter_if_before_version(suite_set_parameters, "defaultConfigCommandTimeoutMS",
bin_version, "7.3.0")
if "grpcPort" not in mongos_options and suite_set_parameters.get("featureFlagGRPC"):
mongos_options["grpcPort"] = network.PortAllocator.next_fixture_port(job_num)
remove_set_parameter_if_before_version(suite_set_parameters, "internalQueryStatsRateLimit",
bin_version, "7.3.0")
remove_set_parameter_if_before_version(
suite_set_parameters, "internalQueryStatsErrorsAreCommandFatal", bin_version, "7.3.0")
if "grpcPort" not in mongos_options and suite_set_parameters.get("featureFlagGRPC"):
mongos_options["grpcPort"] = network.PortAllocator.next_fixture_port(job_num)
_apply_set_parameters(args, suite_set_parameters)
final_mongos_options = mongos_options.copy()
@ -281,8 +273,6 @@ def mongo_shell_program(logger, executable=None, connection_string=None, filenam
if config.SHELL_TLS_CERTIFICATE_KEY_FILE:
test_data["shellTlsCertificateKeyFile"] = config.SHELL_TLS_CERTIFICATE_KEY_FILE
if config.SHELL_GRPC:
test_data["shellGRPC"] = True
if config.TLS_CA_FILE:
test_data["tlsCAFile"] = config.TLS_CA_FILE
@ -450,8 +440,6 @@ def mongo_shell_program(logger, executable=None, connection_string=None, filenam
if config.SHELL_TLS_CERTIFICATE_KEY_FILE:
kwargs["tlsCertificateKeyFile"] = config.SHELL_TLS_CERTIFICATE_KEY_FILE
if config.SHELL_GRPC:
args.append("--gRPC")
if connection_string is not None:
# The --host and --port options are ignored by the mongo shell when an explicit connection

View File

@ -1009,9 +1009,6 @@ class RunPlugin(PluginInterface):
" existing MongoDB cluster with the URL mongodb://localhost:[PORT]."
" This is useful for connecting to a server running in a debugger.")
parser.add_argument("--shellGRPC", dest="shell_grpc", action="store_true",
help="Whether to use gRPC by default when connecting via the shell.")
parser.add_argument("--shellTls", dest="shell_tls_enabled", action="store_true",
help="Whether to use TLS when connecting.")

View File

@ -153,7 +153,6 @@ class _FixtureConfig(object):
self.LINEAR_CHAIN = config.LINEAR_CHAIN
self.TLS_MODE = config.TLS_MODE
self.TLS_CA_FILE = config.TLS_CA_FILE
self.SHELL_GRPC = config.SHELL_GRPC
self.SHELL_TLS_CERTIFICATE_KEY_FILE = config.SHELL_TLS_CERTIFICATE_KEY_FILE
self.NUM_REPLSET_NODES = config.NUM_REPLSET_NODES
self.NUM_SHARDS = config.NUM_SHARDS

View File

@ -738,8 +738,6 @@ class _MongoSFixture(interface.Fixture, interface._DockerComposeInterface):
self.mongos = None
self.port = fixturelib.get_next_port(job_num)
self.mongos_options["port"] = self.port
if "featureFlagGRPC" in self.config.ENABLED_FEATURE_FLAGS:
self.mongos_options["grpcPort"] = fixturelib.get_next_port(job_num)
self._dbpath_prefix = dbpath_prefix
@ -864,7 +862,7 @@ class _MongoSFixture(interface.Fixture, interface._DockerComposeInterface):
return f"{self._get_hostname()}:{self.port}"
def get_shell_connection_url(self):
port = self.port if not self.config.SHELL_GRPC else self.grpcPort
port = self.port
return f"{self._get_hostname()}:{port}"
def get_driver_connection_url(self):

View File

@ -74,10 +74,6 @@ class MongoDFixture(interface.Fixture, interface._DockerComposeInterface):
self.router_port = fixturelib.get_next_port(job_num)
mongod_options["routerPort"] = self.router_port
if "featureFlagGRPC" in self.config.ENABLED_FEATURE_FLAGS:
self.grpcPort = fixturelib.get_next_port(job_num)
self.mongod_options["grpcPort"] = self.grpcPort
# Always log backtraces to a file in the dbpath in our testing.
backtrace_log_file_name = os.path.join(self.get_dbpath_prefix(),
uuid.uuid4().hex + ".stacktrace")
@ -245,7 +241,7 @@ class MongoDFixture(interface.Fixture, interface._DockerComposeInterface):
return f"{self._get_hostname()}:{self.port}"
def get_shell_connection_url(self):
port = self.port if not self.config.SHELL_GRPC else self.grpcPort
port = self.port
return f"{self._get_hostname()}:{port}"
def get_driver_connection_url(self):

View File

@ -199,47 +199,6 @@ buildvariants:
- name: .crypt
- name: crypt_build_debug_and_test
- <<: *generic_linux_compile_params
name: &linux-x86-dynamic-grpc-suggested linux-x86-dynamic-grpc-suggested
display_name: "* Linux x86 Shared Library Enterprise with GRPC"
tags: ["suggested"]
stepback: false
expansions:
<<: *generic_linux_compile_expansions
bazel_compile_flags: >-
--define=MONGO_DISTMOD=rhel88
--linkstatic=False
--build_grpc=True
--use_diagnostic_latches=True
compile_variant: *linux-x86-dynamic-grpc-suggested
clang_tidy_toolchain: v4
large_distro_name: rhel8.8-xlarge
test_flags: >-
--additionalFeatureFlags "featureFlagGRPC"
--excludeWithAnyTags=requires_external_data_source,requires_mongobridge,requires_auth,grpc_incompatible,creates_and_authenticates_user
--tlsMode preferTLS
--tlsCAFile jstests/libs/ca.pem
--shellTls
--shellTlsCertificateKeyFile jstests/libs/client.pem
--mongosTlsCertificateKeyFile jstests/libs/server.pem
--mongodTlsCertificateKeyFile jstests/libs/server.pem
--shellGRPC
tasks:
- name: run_unit_tests_TG
- name: compile_test_parallel_core_stream_TG
- name: compile_test_parallel_dbtest_stream_TG
- name: generate_buildid_to_debug_symbols_mapping
# sharding_uninitialized_fcv_jscore_passthrough_gen spawns too many connections
# and processes to be used with TLS on a single host.
- name: .jscore .common !sharding_uninitialized_fcv_jscore_passthrough_gen !.auth !.sharding !.txns
- name: .lint
- name: libdeps_graph_linting
distros:
- rhel8.8-large
- name: .clang_tidy
distros:
- rhel8.8-xxlarge
- &enterprise-rhel-8-64-bit-dynamic-all-feature-flags-template
<<: *linux_x86_dynamic_compile_variant_dependency
name: &enterprise-rhel-8-64-bit-dynamic-all-feature-flags enterprise-rhel-8-64-bit-dynamic-all-feature-flags

View File

@ -374,25 +374,3 @@ buildvariants:
- name: .release_critical .requires_large_host
distros:
- rhel93-arm64-large
- name: enterprise-rhel-8-arm64-grpc
display_name: "Enterprise RHEL 8 arm64 GRPC"
tags: []
cron: "0 4 * * *" # From the ${project_nightly_cron} parameter.
run_on:
- rhel8.8-arm64-large
stepback: false
expansions:
test_flags: >-
--excludeWithAnyTags=requires_latch_analyzer
--mongodSetParameters="{internalQueryEnableAggressiveSpillsInGroup: true}"
bazel_compile_flags: >-
--dbg=True
--define=MONGO_DISTMOD=rhel88
--linkstatic=False
--build_grpc=True
compile_variant: enterprise-rhel-8-arm64-grpc
tasks:
- name: run_unit_tests_TG
- name: compile_test_parallel_core_stream_TG
- name: compile_test_parallel_dbtest_stream_TG

View File

@ -172,14 +172,6 @@ components:
# TODO - fix the version number in Black Duck
upgrade_suppression: TODO SERVER-67432
grpc:
homepage_url: https://grpc.io/
open_hub_url: https://www.openhub.net/p/grpc
release_monitoring_id: 19117
local_directory_path: src/third_party/grpc
team_owner: "Service Architecture"
upgrade_suppression: TODO SERVER-75761
"ICU for C/C++ (ICU4C)":
homepage_url: http://site.icu-project.org/
open_hub_url: https://www.openhub.net/p/icu4c

View File

@ -11,7 +11,6 @@
* # This test contains assertions for the hostname that operations run on.
* tenant_migration_incompatible,
* docker_incompatible,
* grpc_incompatible,
* ]
*/
import {FixtureHelpers} from "jstests/libs/fixture_helpers.js";

View File

@ -6,8 +6,6 @@
* assumes_superuser_permissions,
* does_not_support_stepdowns,
* no_selinux,
* # This test searches for a MongoRPC-specific log string (*conn).
* grpc_incompatible,
* ]
*/

View File

@ -5,8 +5,6 @@
// not_allowed_with_signed_security_token,
// uses_multiple_connections,
// docker_incompatible,
// # TODO SERVER-84471 - Enable this test, as runMongoProgram will add on the --gRPC option.
// grpc_incompatible,
// ]
const mongod = new MongoURI(db.getMongo().host).servers[0];

View File

@ -100,14 +100,7 @@ export function testGetCmdLineOptsMongod(mongoRunnerConfig, expectedResult) {
delete getCmdLineOptsExpected.parsed.net.port;
delete getCmdLineOptsResult.parsed.net.port;
}
if (!_containsNestedKey(expectedResult, "parsed", "net", "grpc", "port")) {
if (_containsNestedKey(getCmdLineOptsExpected, "parsed", "net", "grpc", "port")) {
delete getCmdLineOptsExpected.parsed.net.grpc.port;
}
if (_containsNestedKey(getCmdLineOptsResult, "parsed", "net", "grpc", "port")) {
delete getCmdLineOptsResult.parsed.net.grpc.port;
}
}
if (!_containsNestedKey(expectedResult, "parsed", "storage", "dbPath")) {
delete getCmdLineOptsExpected.parsed.storage.dbPath;
delete getCmdLineOptsResult.parsed.storage.dbPath;
@ -196,14 +189,6 @@ export function testGetCmdLineOptsMongos(mongoRunnerConfig, expectedResult) {
delete getCmdLineOptsResult.parsed.net.port;
delete getCmdLineOptsExpected.parsed.net.port;
}
if (!_containsNestedKey(expectedResult, "parsed", "net", "grpc", "port")) {
if (_containsNestedKey(getCmdLineOptsResult, "parsed", "net", "grpc", "port")) {
delete getCmdLineOptsResult.parsed.net.grpc.port;
}
if (_containsNestedKey(getCmdLineOptsExpected, "parsed", "net", "grpc", "port")) {
delete getCmdLineOptsExpected.parsed.net.grpc.port;
}
}
// Merge with the result that we expect
expectedResult = mergeOptions(getCmdLineOptsExpected, expectedResult);

View File

@ -33,7 +33,6 @@ delete m2result.parsed.storage.inMemory;
delete m2result.parsed.storage.wiredTiger;
delete m2result.parsed.replication; // Removes enableMajorityReadConcern setting.
delete m2result.parsed.net.tls;
delete m2result.parsed.net.grpc;
assert.docEq(m2expected.parsed, m2result.parsed);
// test JSON config file
@ -59,5 +58,4 @@ delete m3result.parsed.storage.inMemory;
delete m3result.parsed.storage.wiredTiger;
delete m3result.parsed.replication; // Removes enableMajorityReadConcern setting.
delete m3result.parsed.net.tls;
delete m3result.parsed.net.grpc;
assert.docEq(m3expected.parsed, m3result.parsed);

View File

@ -2,10 +2,7 @@
* Test for the ingressConnectionEstablishment rate limiter behavior when the client side
* disconnects while queued.
*
* The default baton, which is used for gRPC, doesn't get marked as disconnected when the
* client disconnects without an additional read or write on the socket.
* @tags: [
* grpc_incompatible,
* ]
*/

View File

@ -1,10 +1,7 @@
/**
* Tests for the ingressConnectionEstablishment rate limiter IP-based exemptions.
*
* The ip-based exemptions tests are complicated by the gRPC testing logic, and so it is
* excluded for now.
* @tags: [
* grpc_incompatible,
* ]
*/

View File

@ -2,9 +2,7 @@
* Tests that the connection establishment rate-limiter exemptions are respected for exempt IPs if
* the proxy protocol is in use and the sourceClient IP is different from the load balancer IP.
*
* IP-based overrides are not implemented for gRPC.
* @tags: [
* grpc_incompatible,
* ]
*/

View File

@ -1,9 +1,7 @@
/**
* Tests for the ingressConnectionEstablishment rate limiter metrics.
*
* gRPC outputs different metrics from the metrics we assert on here.
* @tags: [
* grpc_incompatible,
* ]
*/

View File

@ -1,215 +0,0 @@
/**
* Test that gRPC-based connections to mongos have any open cursors and transactions cleaned
* up upon disconnection.
*/
import {FeatureFlagUtil} from "jstests/libs/feature_flag_util.js";
import {Thread} from "jstests/libs/parallelTester.js";
const kThisFile = "jstests/noPassthrough/grpc_disconnect_cleanup.js";
const kTestName = "grpc_disconnect_cleanup";
function setupShardedCollection(st, dbName, collName) {
const fullNss = dbName + "." + collName;
const admin = st.s.getDB("admin");
// Shard collection; ensure docs on each shard
assert.commandWorked(admin.runCommand({enableSharding: dbName}));
assert.commandWorked(admin.runCommand({movePrimary: dbName, to: st.shard0.shardName}));
assert.commandWorked(admin.runCommand({shardCollection: fullNss, key: {_id: 1}}));
assert.commandWorked(admin.runCommand({split: fullNss, middle: {_id: 0}}));
assert.commandWorked(
admin.runCommand({moveChunk: fullNss, find: {_id: 0}, to: st.shard1.shardName}));
// Insert some docs on each shard
let coll = admin.getSiblingDB(dbName).getCollection(collName);
var bulk = coll.initializeUnorderedBulkOp();
for (let i = -150; i < 150; i++) {
bulk.insert({_id: i});
}
assert.commandWorked(bulk.execute());
}
// Opens a cursor and then waits on the countdown latch.
function openCursor(host, dbName, collName, comment, countdownLatch) {
jsTestLog("Opening new connection in which to open a cursor.");
const conn = new Mongo(`mongodb://${host}/?gRPC=true`);
const testDB = conn.getDB(dbName);
const result = testDB.runCommand({find: collName, comment: comment, batchSize: 1});
assert.commandWorked(result);
const cursorId = result.cursor.id;
assert.neq(cursorId, NumberLong(0));
jsTestLog("Waiting for main thread");
countdownLatch.await();
jsTestLog("Closing cursor thread connection");
conn.close();
return cursorId;
}
function runCursorTest(conn) {
const admin = conn.getDB("admin");
let countdownLatch = new CountDownLatch(1);
// Start the cursor thread
jsTestLog("Starting thread to open cursor");
const dbName = kTestName;
const collName = kTestName;
const comment = kTestName;
const cursorThread =
new Thread(openCursor, conn.host, dbName, collName, comment, countdownLatch);
cursorThread.start();
// Wait until we see the cursor is idle
jsTestLog("Waiting for the cursor to become idle");
let idleCursor = {};
assert.soon(() => {
const curopCursor = admin.aggregate([
{$currentOp: {allUsers: true, idleCursors: true, localOps: true}},
{$match: {type: "idleCursor"}},
{$match: {"cursor.originatingCommand.comment": comment}}
]);
if (curopCursor.hasNext()) {
idleCursor = curopCursor.next().cursor;
return true;
}
return false;
}, "Couldn't find cursor opened by cursorThread");
// Join the cursor thread
jsTestLog("Detected idle cursor, joining cursor thread");
countdownLatch.countDown();
cursorThread.join();
// Assert that the idle cursor we found is the same one from the thread
let cursorId = cursorThread.returnData();
assert.eq(idleCursor.cursorId, cursorId);
// Assert that the cursor is cleaned up
jsTestLog("Waiting for the cursor to get cleaned up");
assert.soon(() => {
const numCursorsFoundWithId =
admin
.aggregate([
{$currentOp: {allUsers: true, idleCursors: true, localOps: true}},
{$match: {type: "idleCursor"}},
{$match: {"cursor.cursorId": cursorId}}
])
.itcount();
return (numCursorsFoundWithId == 0);
}, "The cursor was not cleaned up", 10000, 1000);
}
// Starts a transaction and then waits on the countdown latch
function startTransaction(host, dbName, collName, appName, countdownLatch) {
jsTestLog("Opening new connection in which to start a transaction.");
const conn = new Mongo(`mongodb://${host}/?appName=${appName}&gRPC=true`);
// We manually generate a logical session and send it to the server explicitly, to prevent
// the shell from making its own logical session object which will attempt to explicitly
// abort the transaction on disconnection.
const session = {id: UUID()};
const txnNumber = NumberLong(0);
const result = conn.getDB(dbName).runCommand({
find: collName,
batchSize: 1,
lsid: session,
txnNumber: txnNumber,
startTransaction: true,
autocommit: false
});
assert.commandWorked(result);
jsTestLog("Waiting for main thread");
countdownLatch.await();
jsTestLog("Closing transaction thread connection");
conn.close();
return [session, txnNumber];
}
function runTransactionTest(conn) {
const admin = conn.getDB("admin");
let countdownLatch = new CountDownLatch(1);
// capture txn statistics before opening and aborting the txn.
const preStatus = admin.adminCommand({'serverStatus': 1}).transactions;
const dbName = kTestName;
const collName = kTestName;
const appName = kTestName;
const transactionThread =
new Thread(startTransaction, conn.host, dbName, collName, appName, countdownLatch);
transactionThread.start();
let idleSession = {};
// Wait until we can see the transaction, identified by the appName, as idle.
jsTestLog("Waiting for the transaction's session to become idle");
assert.soon(() => {
const curopCursor = admin.aggregate([
{$currentOp: {allUsers: true, idleCursors: true, localOps: true, idleSessions: true}},
{$match: {type: "idleSession"}},
{$match: {appName: appName}}
]);
if (curopCursor.hasNext()) {
idleSession = curopCursor.next();
return true;
}
return false;
}, "Couldn't find transaction opened by transactionThread.");
// Join the transaction thread.
jsTestLog("Detected idle transaction, joining transaction thread");
countdownLatch.countDown();
transactionThread.join();
// Assert that the idle session we found was indeed the one from the thread.
const [sessionLsid, txnNumber] = transactionThread.returnData();
assert.eq(idleSession.lsid.id, sessionLsid.id);
assert.eq(idleSession.transaction.parameters.txnNumber, txnNumber);
// Assert that the transaction is cleaned up.
jsTestLog("Waiting for the transaction to be cleaned up.");
const numPrevInterrupted =
preStatus.abortCause.hasOwnProperty('Interrupted') ? preStatus.abortCause.Interrupted : 0;
assert.soon(() => {
const postStatus = admin.adminCommand({'serverStatus': 1}).transactions;
return (postStatus.totalAborted == preStatus.totalAborted + 1) &&
(postStatus.abortCause.Interrupted, numPrevInterrupted + 1);
});
}
function runTest(conn) {
runCursorTest(conn);
runTransactionTest(conn);
}
if (typeof inner == 'undefined') {
jsTestLog("Outer shell: setting up test environment");
let st = new ShardingTest({shards: 2});
if (!FeatureFlagUtil.isPresentAndEnabled(st.s.getDB("admin"), "GRPC")) {
jsTestLog("Skipping grpc_disconnect_cleanup.js test due to featureFlagGRPC being disabled");
st.stop();
quit();
}
setupShardedCollection(st, kTestName, kTestName);
const mongosHost = st.s.host.split(":")[0];
const grpcUri = `mongodb://localhost:${st.s.fullOptions.grpcPort}/?gRPC=true`;
jsTestLog("Outer shell: launching inner shell to connect over gRPC to " + grpcUri);
const exitCode = runMongoProgram('mongo', grpcUri, '--eval', `const inner=true;`, kThisFile);
assert.eq(exitCode, 0);
jsTestLog("Outer shell: inner shell exited cleanly.");
st.stop();
} else {
jsTestLog("Inner shell: running test under gRPC connection.");
runTest(db.getMongo());
}

View File

@ -1,142 +0,0 @@
import {configureFailPoint} from "jstests/libs/fail_point_util.js";
import {FeatureFlagUtil} from "jstests/libs/feature_flag_util.js";
// For each shell invocation, we perform 4 + N operations.
// {hello:...}, {whatsmyuri:...}, {buildInfo:...}
// Then what ever operations are in the --eval body
// And finally an implicit {endSessions:...}
const kShellPrefixOperations = 3;
const kShellSuffixOperations = 1;
function evalCmd(uri, evalstr, ok = true, asyncCb = null) {
const args = ['mongo', uri, '--eval', evalstr];
let exitCode = undefined;
if (asyncCb) {
const pid = startMongoProgramNoConnect(...args);
asyncCb(pid);
exitCode = waitProgram(pid);
} else {
// Simple synchronous call.
exitCode = runMongoProgram(...args);
}
assert(exitCode !== undefined, `"${evalstr}" did not run?`);
const assertion = ok ? assert.eq : assert.neq;
assertion(exitCode, 0, `While executing "${evalstr}"`);
}
function runCmd(uri, runOnDB, cmd, ok = true) {
const evalFunc = function(dbname, cmd) {
jsTest.log(assert.commandWorked(db.getSiblingDB(dbname).runCommand(cmd)));
};
const evalstr = `(${evalFunc})(${tojson(runOnDB)}, ${tojson(cmd)});`;
evalCmd(uri, evalstr, ok);
}
function checkGRPCStats(conn, expect) {
const grpcStats = assert.commandWorked(conn.adminCommand({serverStatus: 1})).gRPC;
jsTest.log(grpcStats);
function search(prefix, obj, expect) {
return function(key) {
assert(obj[key] !== undefined, `Missing '${prefix}.${key}' field`);
if (typeof expect[key] == 'object') {
assert.eq(typeof obj[key], 'object', `'${prefix}.${key}' expected object`);
Object.keys(expect[key]).forEach(search(`${prefix}.${key}`, obj[key], expect[key]));
} else {
assert.eq(obj[key], expect[key], `'${prefix}.${key}' value mismatch`);
}
};
}
Object.keys(expect).forEach(search('serverStatus.gRPC', grpcStats, expect));
}
function runTest(conn) {
let expect = {
'streams': {
'total': 0,
'current': 0,
'successful': 0,
},
'operations': {
'total': 0,
'active': 0,
},
'uniqueClientsSeen': 0,
};
// Test currently makes assumption that connections via Mongo objects are using ASIO
// When that changes the count expectations will change as well.
function expectSuccess(explicitOps = 1) {
const implicitOps = kShellPrefixOperations + kShellSuffixOperations;
expect.streams.total++;
expect.streams.successful++;
expect.operations.total += implicitOps + explicitOps;
expect.uniqueClientsSeen++;
}
function expectFailed(opCountTotal) {
expect.streams.total++;
expect.operations.total += opCountTotal;
expect.uniqueClientsSeen++;
}
function expectPartialSuccess(opCountTotal) {
expectFailed(opCountTotal);
expect.streams.successful++;
}
const uri = `mongodb://localhost:${conn.fullOptions.grpcPort}/?gRPC=true`;
// Connect with {failureURI} to have the server abort the connection during the reply cycle.
const failureURI = uri + '&appName=Failure%20Client';
configureFailPoint(
conn, 'sessionWorkflowDelayOrFailSendMessage', {appName: 'Failure Client'}, 'alwaysOn');
runCmd(uri, 'admin', {ping: 1});
expectSuccess();
checkGRPCStats(conn, expect);
runCmd(uri, 'admin', {noSuchCommand: 1}, false);
// Althrough the execution of the command failed,
// the stream itself, and the operation lifetime, succeeded.
expectSuccess();
checkGRPCStats(conn, expect);
// The server fails to send its response via the stream, so it cancels the RPC,
// thus not marking it as successful.
runCmd(failureURI, 'admin', {ping: 1}, false);
expectFailed(1);
checkGRPCStats(conn, expect);
// Killing the client while it's processing should cause a stream failure on the server.
// While a test failure may take up to 15s, this test should take no longer than a normal shell
// exection.
const kSIGKILL = 9;
const kShellShutdownDelay = 15 * 1000;
const kShellStartTimeout = 5 * 1000;
const kShellStartInterval = 500;
clearRawMongoProgramOutput();
evalCmd(uri, `print("Kill Test\\n"); sleep(${kShellShutdownDelay});`, false, function(pid) {
// Wait for the output from the eval string so that we know prefix ops have completed,
// then kill the shell so that the stream shuts down unsuccessfully.
assert.soon(() => rawMongoProgramOutput().includes("Kill Test"),
"Shell start failure",
kShellStartTimeout,
kShellStartInterval);
stopMongoProgramByPid(pid, kSIGKILL);
});
expectFailed(kShellPrefixOperations);
checkGRPCStats(conn, expect);
}
const mongod = MongoRunner.runMongod({});
if (!FeatureFlagUtil.isPresentAndEnabled(mongod.getDB("admin"), "GRPC")) {
jsTestLog("Skipping grpc_metrics.js test due to featureFlagGRPC being disabled");
MongoRunner.stopMongod(mongod);
quit();
}
runTest(mongod);
MongoRunner.stopMongod(mongod);

View File

@ -2,9 +2,7 @@
* Tests that the max connections overrides are respected for exempt IPs if the sourceClient IP is
* different from the load balancer IP.
*
* Maximum connection overrides are not implemented for gRPC.
* @tags: [
* grpc_incompatible,
* ]
*/
if (_isWindows()) {

View File

@ -1,9 +1,7 @@
/**
* Tests that the max connections overrides are respected for exempt IPs.
*
* Maximum connection overrides are not implemented for gRPC.
* @tags: [
* grpc_incompatible,
* requires_sharding,
* ]
*/

View File

@ -3,7 +3,6 @@
* @tags: [
* requires_fcv_80,
* featureFlagMongodProxyProcolSupport,
* grpc_incompatible,
* ]
*/

View File

@ -4,7 +4,6 @@
* requires_fcv_80,
* # TODO (SERVER-97257): Re-enable this test or add an explanation why it is incompatible.
* embedded_router_incompatible,
* grpc_incompatible,
* ]
*/

View File

@ -4,7 +4,6 @@
* # TODO (SERVER-97257): Re-enable this test or add an explanation why it is incompatible.
* embedded_router_incompatible,
* requires_fcv_80,
* grpc_incompatible,
* ]
*/

View File

@ -1,7 +1,6 @@
/**
* @tags: [
* requires_fcv_80,
* grpc_incompatible,
* ]
*/

View File

@ -1,54 +0,0 @@
import {FeatureFlagUtil} from "jstests/libs/feature_flag_util.js";
// Constructs a new Mongo instance with the provided URI and asserts it fails with the provided
// error code.
function assertConnectFailsWithErrorCode(uri, errorCode) {
jsTestLog(`Connecting to ${uri}`);
assert.throwsWithCode(() => new Mongo(uri), errorCode);
}
// Runs a new shell process with the provided arguments and asserts that its exit code matches `ok`
// (true for success). This is used over assertConnectFailsWithErrorCode when CLI-only arguments
// need to be specified.
function testShellConnect(ok, ...args) {
const cmd = 'assert.commandWorked(db.runCommand({hello: 1}));';
const exitCode = runMongoProgram('mongo', '--eval', cmd, ...args);
if (ok) {
assert.eq(exitCode, 0, "failed to connect with `" + args.join(' ') + "`");
} else {
assert.neq(exitCode, 0, "unexpectedly succeeded connecting with `" + args.join(' ') + "`");
}
}
const mongod = MongoRunner.runMongod({});
if (!FeatureFlagUtil.isPresentAndEnabled(mongod.getDB("admin"), "GRPC")) {
jsTestLog("Skipping shell_grpc_uri.js test due to featureFlagGRPC being disabled");
MongoRunner.stopMongod(mongod);
quit();
}
const host = `localhost:${mongod.fullOptions.grpcPort}`;
function testGRPCConnect(ok, ...args) {
testShellConnect(ok, `mongodb://${host}`, '--gRPC', ...args);
testShellConnect(ok, `mongodb://${host}/?gRPC=true`, ...args);
}
testGRPCConnect(true);
// Options currently prohibited when using gRPC.
testGRPCConnect(false, '--tlsCRLFile', 'jstests/libs/crl.pem');
testGRPCConnect(false,
'--tlsCertificateKeyFile',
'jstests/libs/password_protected.pem',
'--tlsCertificateKeyFilePassword',
'qwerty');
testGRPCConnect(false, '--tlsFIPSMode');
assertConnectFailsWithErrorCode(`mongodb://user:password@${host}/?gRPC=true&tls=true`,
ErrorCodes.InvalidOptions);
assertConnectFailsWithErrorCode(`mongodb://${host}/?gRPC=true&tls=true&replicaSet=blah`,
ErrorCodes.InvalidOptions);
MongoRunner.stopMongod(mongod);

232
sbom.json
View File

@ -498,59 +498,6 @@
},
"scope": "required"
},
{
"type": "library",
"bom-ref": "pkg:github/c-ares/c-ares@cares-1_27_0",
"supplier": {
"name": "The c-ares Project",
"url": [
"https://c-ares.org/"
]
},
"author": "Daniel Stenberg",
"group": "c-ares",
"name": "c-ares",
"version": "1.27.0",
"description": "A C library for asynchronous DNS requests",
"licenses": [
{
"license": {
"id": "MIT"
}
}
],
"copyright": "Copyright (c) 2007 - 2023 Daniel Stenberg with many contributors, see AUTHORS file.",
"cpe": "cpe:2.3:a:c-ares:c-ares:1.27.0:*:*:*:*:*:*:*",
"purl": "pkg:github/c-ares/c-ares@cares-1_27_0",
"externalReferences": [
{
"url": "https://github.com/c-ares/c-ares.git",
"type": "distribution"
}
],
"properties": [
{
"name": "internal:team_responsible",
"value": "Networking & Observability"
},
{
"name": "emits_persisted_data",
"value": "false"
},
{
"name": "import_script_path",
"value": "src/third_party/cares/scripts/import.sh"
}
],
"evidence": {
"occurrences": [
{
"location": "src/third_party/cares"
}
]
},
"scope": "required"
},
{
"type": "library",
"bom-ref": "pkg:github/chriskohlhoff/asio@asio-1-12-2",
@ -990,59 +937,6 @@
},
"scope": "excluded"
},
{
"type": "library",
"bom-ref": "pkg:github/google/re2@2023-11-01",
"supplier": {
"name": "Google LLC",
"url": [
"https://opensource.google/"
]
},
"author": "The RE2 Authors",
"group": "google.opensource",
"name": "re2",
"version": "2023-11-01",
"description": "RE2 is a fast, safe, thread-friendly alternative to backtracking regular expression engines like those used in PCRE, Perl, and Python. It is a C++ library.",
"licenses": [
{
"license": {
"id": "BSD-3-Clause"
}
}
],
"copyright": "Copyright (c) 2009 The RE2 Authors. All rights reserved.",
"cpe": "cpe:2.3:h:google:re2:2023-11-01:*:*:*:*:*:*:*",
"purl": "pkg:github/google/re2@2023-11-01",
"externalReferences": [
{
"url": "https://github.com/google/re2.git",
"type": "distribution"
}
],
"properties": [
{
"name": "internal:team_responsible",
"value": "Server Programmability"
},
{
"name": "emits_persisted_data",
"value": "false"
},
{
"name": "import_script_path",
"value": "src/third_party/re2/scripts/import.sh"
}
],
"evidence": {
"occurrences": [
{
"location": "src/third_party/re2"
}
]
},
"scope": "required"
},
{
"type": "library",
"bom-ref": "pkg:github/google/s2geometry@a25c502bda9d7e0274b9e2b7825fbddf13cc0306",
@ -1268,59 +1162,6 @@
},
"scope": "required"
},
{
"type": "library",
"bom-ref": "pkg:github/grpc/grpc@1.59.5",
"supplier": {
"name": "Google LLC",
"url": [
"https://opensource.google/"
]
},
"author": "gRPC authors",
"group": "google.opensource",
"name": "gRPC (C++)",
"version": "1.59.5",
"description": "gRPC is a modern, open source, high-performance remote procedure call (RPC) framework that can run anywhere. gRPC enables client and server applications to communicate transparently, and simplifies the building of connected systems.",
"licenses": [
{
"license": {
"id": "Apache-2.0"
}
}
],
"copyright": "Copyright 2015 gRPC authors",
"cpe": "cpe:2.3:a:grpc:grpc:1.59.5:*:*:*:*:*:*:*",
"purl": "pkg:github/grpc/grpc@1.59.5",
"externalReferences": [
{
"url": "https://github.com/grpc/grpc.git",
"type": "distribution"
}
],
"properties": [
{
"name": "internal:team_responsible",
"value": "Networking & Observability"
},
{
"name": "emits_persisted_data",
"value": "false"
},
{
"name": "import_script_path",
"value": "src/third_party/grpc/scripts/import.sh"
}
],
"evidence": {
"occurrences": [
{
"location": "src/third_party/grpc"
}
]
},
"scope": "required"
},
{
"type": "library",
"bom-ref": "pkg:github/jbeder/yaml-cpp@yaml-cpp-0.6.3",
@ -1732,59 +1573,6 @@
},
"scope": "required"
},
{
"type": "library",
"bom-ref": "pkg:github/protocolbuffers/protobuf@v25.0",
"supplier": {
"name": "Google LLC",
"url": [
"https://protobuf.dev/"
]
},
"author": "Google LLC",
"group": "google.opensource",
"name": "Protobuf",
"version": "v25.0",
"description": "Protocol Buffers - Google's data interchange format",
"licenses": [
{
"license": {
"id": "BSD-3-Clause"
}
}
],
"copyright": "Copyright 2008 Google Inc. Copyright 2023 Google LLC. All rights reserved.",
"cpe": "cpe:2.3:a:google:protobuf:v25.0:*:*:*:*:*:*:*",
"purl": "pkg:github/protocolbuffers/protobuf@v25.0",
"externalReferences": [
{
"url": "https://github.com/protocolbuffers/protobuf.git",
"type": "distribution"
}
],
"properties": [
{
"name": "internal:team_responsible",
"value": "Networking & Observability"
},
{
"name": "emits_persisted_data",
"value": "false"
},
{
"name": "import_script_path",
"value": "src/third_party/protobuf/scripts/import.sh"
}
],
"evidence": {
"occurrences": [
{
"location": "src/third_party/protobuf"
}
]
},
"scope": "required"
},
{
"type": "library",
"bom-ref": "pkg:github/roaringbitmap/croaring@v2.1.2",
@ -2140,7 +1928,6 @@
"pkg:github/antirez/linenoise@6cdc775807e57b2c3fd64bd207814f8ee1fe35f3",
"pkg:github/arximboldi/immer@0b3aaf699b9d6f2e89f8e2b6d1221c307e02bda3",
"pkg:github/boostorg/boost@boost-{{VERSION}}",
"pkg:github/c-ares/c-ares@cares-1_27_0",
"pkg:github/chriskohlhoff/asio@asio-1-12-2",
"pkg:github/confluentinc/librdkafka@v2.0.2-RC2",
"pkg:github/cyrusimap/cyrus-sasl@cyrus-sasl-2.1.28",
@ -2150,12 +1937,10 @@
"pkg:github/facebook/zstd@v1.5.5",
"pkg:github/fmtlib/fmt@7.1.3",
"pkg:github/google/benchmark@v1.5.2",
"pkg:github/google/re2@2023-11-01",
"pkg:github/google/s2geometry@a25c502bda9d7e0274b9e2b7825fbddf13cc0306",
"pkg:github/google/snappy@1.1.10",
"pkg:github/google/tcmalloc@f3b20f9a07e175c5d897df7b49d9830d4efa6110",
"pkg:github/gperftools/gperftools@gperftools-2.9.1",
"pkg:github/grpc/grpc@1.59.5",
"pkg:github/jbeder/yaml-cpp@yaml-cpp-0.6.3",
"pkg:github/json-schema-org/json-schema-test-suite@728066f9c5c258ba3b1804a22a5b998f2ec77ec0",
"pkg:github/libtom/libtomcrypt@v1.18.2",
@ -2164,7 +1949,6 @@
"pkg:github/mongodb/libmongocrypt@1.8.4",
"pkg:github/mongodb/mongo-c-driver@1.27.6",
"pkg:github/pcre2project/pcre2@pcre2-10.40",
"pkg:github/protocolbuffers/protobuf@v25.0",
"pkg:github/roaringbitmap/croaring@v2.1.2",
"pkg:github/schemastore/schemastore@6847cfc3a17a04a7664474212db50c627e1e3408",
"pkg:github/snowballstem/snowball@1.0.0",
@ -2208,10 +1992,6 @@
"ref": "pkg:github/boostorg/boost@boost-{{VERSION}}",
"dependsOn": []
},
{
"ref": "pkg:github/c-ares/c-ares@cares-1_27_0",
"dependsOn": []
},
{
"ref": "pkg:github/chriskohlhoff/asio@asio-1-12-2",
"dependsOn": []
@ -2248,10 +2028,6 @@
"ref": "pkg:github/google/benchmark@v1.5.2",
"dependsOn": []
},
{
"ref": "pkg:github/google/re2@2023-11-01",
"dependsOn": []
},
{
"ref": "pkg:github/google/s2geometry@a25c502bda9d7e0274b9e2b7825fbddf13cc0306",
"dependsOn": []
@ -2268,10 +2044,6 @@
"ref": "pkg:github/gperftools/gperftools@gperftools-2.9.1",
"dependsOn": []
},
{
"ref": "pkg:github/grpc/grpc@1.59.5",
"dependsOn": []
},
{
"ref": "pkg:github/jbeder/yaml-cpp@yaml-cpp-0.6.3",
"dependsOn": []
@ -2304,10 +2076,6 @@
"ref": "pkg:github/pcre2project/pcre2@pcre2-10.40",
"dependsOn": []
},
{
"ref": "pkg:github/protocolbuffers/protobuf@v25.0",
"dependsOn": []
},
{
"ref": "pkg:github/roaringbitmap/croaring@v2.1.2",
"dependsOn": []

View File

@ -685,7 +685,6 @@ mongo_cc_library(
"//src/mongo/scripting/mozjs:scripting_util_idl_gen",
"//src/mongo/shell:kms_idl_gen",
"//src/mongo/shell:shell_global_hdrs",
"//src/mongo/shell:shell_options_grpc_idl_gen",
"//src/mongo/shell:shell_options_idl_gen",
"//src/mongo/stdx:stdx_global_hdrs",
"//src/mongo/tla_plus:tla_plus_global_hdrs",
@ -710,9 +709,6 @@ mongo_cc_library(
"//src/mongo/transport:transport_global_hdrs",
"//src/mongo/transport:transport_options_idl_gen",
"//src/mongo/transport/asio:asio_global_hdrs",
"//src/mongo/transport/grpc:grpc_feature_flag_idl_gen",
"//src/mongo/transport/grpc:grpc_global_hdrs",
"//src/mongo/transport/grpc:grpc_parameters_idl_gen",
"//src/mongo/unittest:benchmark_options_idl_gen",
"//src/mongo/unittest:integration_test_main_idl_gen",
"//src/mongo/unittest:unittest_global_hdrs",

View File

@ -29,16 +29,6 @@ generate_config_header(
"MONGO_CONFIG_WIREDTIGER_ENABLED": "1",
},
"//conditions:default": {},
}) | select({
"//bazel/config:build_grpc_enabled": {
"MONGO_CONFIG_GRPC": "1",
},
"//conditions:default": {},
}) | select({
"//bazel/config:build_otel_enabled": {
"MONGO_CONFIG_OTEL": "1",
},
"//conditions:default": {},
}) | select({
"//bazel/config:tcmalloc_google_enabled": {
"MONGO_CONFIG_TCMALLOC_GOOGLE": "1",

View File

@ -372,21 +372,6 @@ mongo_cc_unit_test(
],
)
mongo_cc_unit_test(
name = "dbclient_grpc_stream_test",
srcs = [
"//src/mongo/client:dbclient_grpc_stream_test.cpp",
],
tags = ["mongo_unittest_fourth_group"],
deps = [
"//src/mongo/client:clientdriver_network",
"//src/mongo/db:service_context_non_d",
"//src/mongo/db:service_context_test_fixture",
"//src/mongo/transport/grpc:grpc_transport_mock",
"//src/mongo/util:version_impl",
],
)
mongo_cc_library(
name = "native_sasl_client",
srcs = [
@ -474,10 +459,7 @@ mongo_cc_library(
"streamable_replica_set_monitor_discovery_time_processor.cpp",
"streamable_replica_set_monitor_error_handler.cpp",
"streamable_replica_set_monitor_query_processor.cpp",
] + select({
"//bazel/config:build_grpc_enabled": ["dbclient_grpc_stream.cpp"],
"//conditions:default": [],
}),
],
deps = [
"clientdriver_minimal",
"global_conn_pool_idl",
@ -499,10 +481,7 @@ mongo_cc_library(
"//src/mongo/util:md5",
"//src/mongo/util/net:network",
"//src/mongo/util/net:ssl_manager",
] + select({
"//bazel/config:build_grpc_enabled": ["//src/mongo/transport/grpc:grpc_transport_layer"],
"//conditions:default": [],
}),
],
)
mongo_idl_library(

View File

@ -57,10 +57,6 @@
#include "mongo/util/assert_util.h"
#include "mongo/util/net/ssl_options.h"
#ifdef MONGO_CONFIG_GRPC
#include "mongo/client/dbclient_grpc_stream.h"
#endif
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kNetwork
@ -87,17 +83,7 @@ StatusWith<std::unique_ptr<DBClientBase>> ConnectionString::connect(
"Invalid standalone connection string with empty server list.");
for (const auto& server : _servers) {
std::unique_ptr<DBClientSession> c;
#ifdef MONGO_CONFIG_GRPC
if (newURI.isGRPC()) {
c = std::make_unique<DBClientGRPCStream>(
/* authToken */ boost::none,
/* autoReconnect */ true,
/* socket timeout */ 0,
newURI,
DBClientGRPCStream::HandshakeValidationHook(),
apiParameters);
} else
#endif
{
c = std::make_unique<DBClientConnection>(
/* autoReconnect */ true,
@ -108,15 +94,7 @@ StatusWith<std::unique_ptr<DBClientBase>> ConnectionString::connect(
c->setSoTimeout(socketTimeout);
}
#ifdef MONGO_CONFIG_GRPC
LOGV2_DEBUG(8050201,
1,
"Creating new connection",
"hostAndPort"_attr = server,
"gRPC"_attr = newURI.isGRPC());
#else
LOGV2_DEBUG(20109, 1, "Creating new connection", "hostAndPort"_attr = server);
#endif
try {
c->connect(server,

View File

@ -679,10 +679,6 @@ public:
}
#endif
virtual bool isGRPC() {
return false;
}
const ClientAPIVersionParameters& getApiParameters() const {
return _apiParameters;
}

View File

@ -1,133 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/client/dbclient_grpc_stream.h"
#include "mongo/base/error_codes.h"
#include "mongo/db/service_context.h"
#include "mongo/logv2/log.h"
#include "mongo/transport/grpc/grpc_transport_layer.h"
#include "mongo/transport/transport_layer_manager.h"
#include "mongo/util/assert_util.h"
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kNetwork
namespace mongo {
DBClientGRPCStream::~DBClientGRPCStream() {
if (auto session = _getSession()) {
if (auto status = session->finish(); !status.isOK()) {
LOGV2(8393201,
"RPC associated with DBClientGRPCStream did not terminate successfully",
"clientId"_attr = session->getClientId(),
"remote"_attr = getServerHostAndPort(),
"terminationStatus"_attr = status);
}
}
}
StatusWith<std::shared_ptr<transport::Session>> DBClientGRPCStream::_makeSession(
const HostAndPort& host,
transport::ConnectSSLMode sslMode,
Milliseconds timeout,
boost::optional<TransientSSLParams> transientSSLParams) {
auto tl = dynamic_cast<transport::grpc::GRPCTransportLayer*>(
getGlobalServiceContext()->getTransportLayerManager()->getEgressLayer());
invariant(tl,
"DBClientGRPCStream can only be used with a gRPC transport layer configured as the "
"egress transport layer.");
return tl->connectWithAuthToken(host, std::move(timeout), _authToken);
}
void DBClientGRPCStream::_reconnectSession() {
if (auto oldSession = _getSession()) {
auto status = oldSession->finish();
LOGV2(8393202,
"Trying to re-establish gRPC stream",
"remote"_attr = getServerHostAndPort(),
"priorStreamTerminationStatus"_attr = status);
} else {
LOGV2_DEBUG(8057001,
_logLevel.toInt(),
"Trying to re-establish gRPC stream",
"remote"_attr = getServerHostAndPort());
}
try {
connect(_serverAddress, _applicationName, _transientSSLParams);
} catch (const DBException& e) {
_markFailed(kSetFlag);
LOGV2_DEBUG(8057002,
_logLevel.toInt(),
"gRPC stream re-establishment failed",
"remote"_attr = getServerHostAndPort(),
"error"_attr = e.toStatus());
throw;
}
LOGV2_DEBUG(8057003,
_logLevel.toInt(),
"Successfully re-established gRPC stream",
"remote"_attr = getServerHostAndPort());
}
transport::grpc::EgressSession* DBClientGRPCStream::_getSession() {
if (!_session) {
return nullptr;
}
auto egressSession = dynamic_cast<transport::grpc::EgressSession*>(_session.get());
invariant(egressSession,
"_session must be an instance of GRPCSession:EgressSession in DBClientGRPCStream.");
return egressSession;
}
void DBClientGRPCStream::_killSession() {
transport::grpc::EgressSession* session = _getSession();
if (!session) {
return;
}
session->cancel(Status(ErrorCodes::CallbackCanceled,
"Client is disconnecting, cancelling the outstanding RPC"));
}
int DBClientGRPCStream::getMinWireVersion() {
return DBClientSession::getMinWireVersion();
};
int DBClientGRPCStream::getMaxWireVersion() {
transport::grpc::EgressSession* session = _getSession();
if (!session) {
return DBClientSession::getMaxWireVersion();
}
return session->getClusterMaxWireVersion();
};
} // namespace mongo

View File

@ -1,122 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <boost/optional/optional.hpp>
#include <string>
#include "mongo/base/status.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/client/authenticate.h"
#include "mongo/client/dbclient_session.h"
#include "mongo/client/mongo_uri.h"
#include "mongo/transport/grpc/grpc_session.h"
#include "mongo/util/net/hostandport.h"
#include "mongo/util/net/ssl_options.h"
namespace mongo {
class ClientAPIVersionParameters;
/**
* A basic connection to the database, backed by a gRPC stream.
* This is the main entry point for talking to a simple Mongo setup through gRPC.
*/
class DBClientGRPCStream : public DBClientSession {
public:
DBClientGRPCStream(boost::optional<std::string> authToken = boost::none,
bool _autoReconnect = false,
double so_timeout = 0,
MongoURI uri = {},
const HandshakeValidationHook& hook = HandshakeValidationHook(),
const ClientAPIVersionParameters* apiParameters = nullptr)
: DBClientSession(_autoReconnect, so_timeout, uri, hook, apiParameters),
_authToken{std::move(authToken)} {}
~DBClientGRPCStream();
/**
* Logout is not implemented for gRPC, throws an exception.
*/
void logout(const DatabaseName& dbname, BSONObj& info) override {
uasserted(ErrorCodes::NotImplemented, "gRPC does not support logout() command.");
}
/**
* Authentication is not implemented for gRPC, throws an exception.
*/
void authenticateInternalUser(auth::StepDownBehavior stepDownBehavior =
auth::StepDownBehavior::kKillConnection) override {
uasserted(ErrorCodes::NotImplemented, "gRPC does not support user authentication.");
}
/**
* The value returned from the initial connection handshake's minWireVersion.
*/
int getMinWireVersion() override;
/**
* clusterMaxWireVersion for gRPC EgressSession, or the value returned from the
* DBClientSession::getMaxWireVersion() if connect() has not been called.
*/
int getMaxWireVersion() override;
#ifdef MONGO_CONFIG_SSL
/**
* Returns nullptr. SSL config is handled by gRPC.
*/
const SSLConfiguration* getSSLConfiguration() override {
return nullptr;
}
bool isTLS() override {
return true;
}
#endif
bool isGRPC() override {
return true;
}
private:
StatusWith<std::shared_ptr<transport::Session>> _makeSession(
const HostAndPort& host,
transport::ConnectSSLMode sslMode,
Milliseconds timeout,
boost::optional<TransientSSLParams> transientSSLParams = boost::none) override;
void _reconnectSession() override;
void _killSession() override;
transport::grpc::EgressSession* _getSession();
boost::optional<std::string> _authToken;
};
} // namespace mongo

View File

@ -1,312 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/client/dbclient_grpc_stream.h"
#include "mongo/transport/grpc/grpc_transport_layer_mock.h"
#include "mongo/transport/grpc/mock_wire_version_provider.h"
#include "mongo/transport/grpc/test_fixtures.h"
#include "mongo/transport/transport_layer_manager_impl.h"
/**
* This file contains tests for DBClientGRPCStream. It utilizes the mocking framework provided by
* the gRPC TL to mock the gRPC transport layer.
*/
namespace mongo {
namespace {
using namespace transport::grpc;
class DBClientGRPCTest : public ServiceContextTest {
public:
inline static const HostAndPort kServerHostAndPort = HostAndPort("localhost", 12345);
void setUp() {
ServiceContextTest::setUp();
// Mock resolver that automatically returns the producer end of the test's pipe.
auto resolver = [&](const HostAndPort&) -> MockRPCQueue::Producer {
return _pipe.producer;
};
auto tl = std::make_unique<GRPCTransportLayerMock>(
getServiceContext(),
CommandServiceTestFixtures::makeTLOptions(),
resolver,
HostAndPort(MockStubTestFixtures::kClientAddress));
getServiceContext()->setTransportLayerManager(
std::make_unique<transport::TransportLayerManagerImpl>(std::move(tl)));
uassertStatusOK(getServiceContext()->getTransportLayerManager()->setup());
uassertStatusOK(getServiceContext()->getTransportLayerManager()->start());
_server = std::make_unique<MockServer>(std::move(_pipe.consumer));
}
void tearDown() {
getServiceContext()->getTransportLayerManager()->shutdown();
ServiceContextTest::tearDown();
}
Message helloResponse() {
OpMsg response;
WireVersionInfo wv =
WireSpec::getWireSpec(getServiceContext()).get()->incomingExternalClient;
BSONObjBuilder bob;
bob.append("ok", 1);
WireVersionInfo::appendToBSON(wv, &bob);
response.body = bob.obj();
return response.serialize();
}
OpMsgRequest pingRequest() {
OpMsgRequest request;
request.body = BSON("msg"
<< "ping");
return request;
}
Message pongResponse() {
OpMsg response;
response.body = BSON("ok" << 1 << "msg"
<< "pong");
return response.serialize();
}
void confirmHelloAndRespond(std::shared_ptr<mongo::transport::grpc::GRPCSession> session) {
ASSERT_EQ(session->remote().toString(), MockStubTestFixtures::kClientAddress);
auto msg = session->sourceMessage();
ASSERT_OK(msg);
OpMsg request = OpMsg::parse(msg.getValue());
ASSERT_EQ(request.body["hello"].numberInt(), 1);
ASSERT_OK(session->sinkMessage(helloResponse()));
}
void confirmPingAndRespondPong(std::shared_ptr<mongo::transport::grpc::GRPCSession> session) {
ASSERT_EQ(session->remote().toString(), MockStubTestFixtures::kClientAddress);
auto ping = session->sourceMessage();
ASSERT_OK(ping);
OpMsg pingRequest = OpMsg::parse(ping.getValue());
ASSERT_EQ(pingRequest.body["msg"].str(), "ping");
ASSERT_OK(session->sinkMessage(pongResponse()));
}
void runTest(
CommandService::RPCHandler serverCb,
std::function<void(DBClientGRPCStream&)> clientThread,
std::shared_ptr<WireVersionProvider> wvProvider = std::make_unique<WireVersionProvider>()) {
unittest::threadAssertionMonitoredTest([&](unittest::ThreadAssertionMonitor& monitor) {
_server->start(monitor, serverCb, wvProvider);
ON_BLOCK_EXIT([&] { _server->shutdown(); });
DBClientGRPCStream dbclient =
DBClientGRPCStream(/* authToken */ boost::none, /* autoReconnect */ true);
ASSERT_DOES_NOT_THROW(clientThread(dbclient));
});
}
private:
MockRPCQueue::Pipe _pipe;
std::unique_ptr<MockServer> _server;
};
TEST_F(DBClientGRPCTest, BasicConnect) {
auto serverCb = [&](auto session) {
ON_BLOCK_EXIT([&] { session->setTerminationStatus(Status::OK()); });
confirmHelloAndRespond(session);
};
auto clientThread = [&](DBClientGRPCStream& dbclient) {
dbclient.connect(kServerHostAndPort, "test", boost::none);
ON_BLOCK_EXIT([&] { dbclient.shutdown(); });
ASSERT_TRUE(dbclient.isStillConnected());
};
runTest(serverCb, clientThread);
}
TEST_F(DBClientGRPCTest, BasicRunCommand) {
auto serverCb = [&](auto session) {
ON_BLOCK_EXIT([&] { session->setTerminationStatus(Status::OK()); });
confirmHelloAndRespond(session);
confirmPingAndRespondPong(session);
};
auto clientThread = [&](DBClientGRPCStream& dbclient) {
dbclient.connect(kServerHostAndPort, "test", boost::none);
ON_BLOCK_EXIT([&] { dbclient.shutdown(); });
auto reply = dbclient.runCommand(pingRequest())->getCommandReply();
ASSERT_OK(getStatusFromCommandResult(reply));
};
runTest(serverCb, clientThread);
}
TEST_F(DBClientGRPCTest, BasicConnectNoHelloWithCommand) {
auto serverCb = [&](auto session) {
ON_BLOCK_EXIT([&] { session->setTerminationStatus(Status::OK()); });
// We should only get a ping.
confirmPingAndRespondPong(session);
};
auto clientThread = [&](DBClientGRPCStream& dbclient) {
dbclient.connectNoHello(kServerHostAndPort, boost::none);
ON_BLOCK_EXIT([&] { dbclient.shutdown(); });
ASSERT_TRUE(dbclient.isStillConnected());
auto reply = dbclient.runCommand(pingRequest())->getCommandReply();
ASSERT_OK(getStatusFromCommandResult(reply));
};
runTest(serverCb, clientThread);
}
TEST_F(DBClientGRPCTest, GetMaxWireVersionCallsClusterMaxWireVersion) {
const int kDefaultMaxWireVersion = 10;
const int kNewMaxWireVersion = 70;
// Make the server use a mocked wire version to ensure that the client actually receives the
// wire version from the server.
auto wvProvider = std::make_shared<MockWireVersionProvider>();
wvProvider->setClusterMaxWireVersion(kNewMaxWireVersion);
auto serverCb = [&](auto session) {
ON_BLOCK_EXIT([&] { session->setTerminationStatus(Status::OK()); });
confirmHelloAndRespond(session);
};
auto clientThread = [&](DBClientGRPCStream& dbclient) {
// Before connecting, it should fall back to called DBClientSession::getMaxWireVersion().
ASSERT_EQ(dbclient.getMaxWireVersion(), 0);
dbclient.setWireVersions(0, kDefaultMaxWireVersion);
ASSERT_EQ(dbclient.getMaxWireVersion(), kDefaultMaxWireVersion);
// After connecting, returns the value of EgressSession::getClusterMaxWireVersion(), as
// determined by the value recieved by the server hello.
dbclient.connect(kServerHostAndPort, "test", boost::none);
ON_BLOCK_EXIT([&] { dbclient.shutdown(); });
ASSERT_EQ(dbclient.getMaxWireVersion(), kNewMaxWireVersion);
};
runTest(serverCb, clientThread, wvProvider);
}
TEST_F(DBClientGRPCTest, AutoReconnectionSucceeds) {
AtomicWord<bool> hasBeenCancelledOnce(false);
auto serverCb = [&](auto session) {
// Cancel the first RPC.
if (!hasBeenCancelledOnce.load()) {
session->setTerminationStatus(Status(ErrorCodes::CallbackCanceled, "test"));
hasBeenCancelledOnce.store(true);
return;
}
ON_BLOCK_EXIT([&] { session->setTerminationStatus(Status::OK()); });
// Respond hello to the second connection request, and then respond to the ping.
confirmHelloAndRespond(session);
confirmPingAndRespondPong(session);
};
auto clientThread = [&](DBClientGRPCStream& dbclient) {
// Initially, connect fails.
ASSERT_THROWS(dbclient.connect(kServerHostAndPort, "test", boost::none), DBException);
ON_BLOCK_EXIT([&] { dbclient.shutdown(); });
ASSERT_TRUE(dbclient.isFailed());
// runCommand calls ensureConnection under the hood to auto-reconnect if possible.
auto reply = dbclient.runCommand(pingRequest())->getCommandReply();
ASSERT_OK(getStatusFromCommandResult(reply));
};
runTest(serverCb, clientThread);
}
TEST_F(DBClientGRPCTest, ShutdownBehavior) {
AtomicWord<bool> firstRun(true);
auto serverCb = [&](auto session) {
ON_BLOCK_EXIT([&] { session->setTerminationStatus(Status::OK()); });
confirmHelloAndRespond(session);
if (firstRun.swap(false)) {
// Cannot read from the stream after shutdown.
auto ping = session->sourceMessage();
ASSERT_NOT_OK(ping.getStatus());
ASSERT_EQ(ping.getStatus().code(), ErrorCodes::CallbackCanceled);
return;
}
// Now that we have reconnected, we can successfully recieve messages again.
confirmPingAndRespondPong(session);
};
auto clientThread = [&](DBClientGRPCStream& dbclient) {
dbclient.connect(kServerHostAndPort, "test", boost::none);
ON_BLOCK_EXIT([&] { dbclient.shutdown(); });
ASSERT_TRUE(dbclient.isStillConnected());
// After shutdown, we can reconnect and send messages over the stream again.
dbclient.shutdown();
ASSERT_FALSE(dbclient.isStillConnected());
auto reply = dbclient.runCommand(pingRequest())->getCommandReply();
ASSERT_OK(getStatusFromCommandResult(reply));
};
runTest(serverCb, clientThread);
}
TEST_F(DBClientGRPCTest, ShutdownDisallowReconnectBehavior) {
auto serverCb = [&](auto session) {
ON_BLOCK_EXIT([&] { session->setTerminationStatus(Status::OK()); });
confirmHelloAndRespond(session);
};
auto clientThread = [&](DBClientGRPCStream& dbclient) {
dbclient.connect(kServerHostAndPort, "test", boost::none);
ON_BLOCK_EXIT([&] { dbclient.shutdown(); });
ASSERT_TRUE(dbclient.isStillConnected());
dbclient.shutdownAndDisallowReconnect();
ASSERT_THROWS(dbclient.runCommand(pingRequest())->getCommandReply(), DBException);
};
runTest(serverCb, clientThread);
}
TEST_F(DBClientGRPCTest, EnsureConnectionFailsAfterShutdown) {
DBClientGRPCStream dbclient;
dbclient.shutdown();
// ensureConnection fails when the client connection is in a failed state.
ASSERT_THROWS(dbclient.ensureConnection(), DBException);
}
} // namespace
} // namespace mongo

View File

@ -507,10 +507,7 @@ MongoURI MongoURI::parseImpl(StringData url) {
const auto retryWrites = extractBooleanOption("retryWrites");
const auto helloOk = extractBooleanOption("helloOk");
// TODO: SERVER-80343 Remove this ifdef once gRPC is compiled on all variants
#ifdef MONGO_CONFIG_GRPC
const auto gRPC = extractBooleanOption("gRPC");
#endif
auto tlsEnabled = extractBooleanOption("tls");
if (!tlsEnabled.has_value()) {
tlsEnabled = extractBooleanOption("ssl");
@ -532,10 +529,6 @@ MongoURI MongoURI::parseImpl(StringData url) {
retryWrites,
tlsMode,
helloOk,
// TODO: SERVER-80343 Remove this ifdef once gRPC is compiled on all variants
#ifdef MONGO_CONFIG_GRPC
gRPC,
#endif
std::move(options));
}

View File

@ -264,12 +264,6 @@ public:
_helloOk.emplace(helloOk);
}
// TODO: SERVER-80343 Remove this ifdef once gRPC is compiled on all variants
#ifdef MONGO_CONFIG_GRPC
bool isGRPC() const {
return _gRPC.get_value_or(false);
}
#endif
// If you are trying to clone a URI (including its options/auth information) for a single
// server (say a member of a replica-set), you can pass in its HostAndPort information to
@ -319,28 +313,6 @@ private:
_helloOk(helloOk),
_options(std::move(options)) {}
// TODO: SERVER-80343 Remove this ifdef once gRPC is compiled on all variants
#ifdef MONGO_CONFIG_GRPC
MongoURI(ConnectionString connectString,
const std::string& user,
const std::string& password,
const std::string& database,
boost::optional<bool> retryWrites,
transport::ConnectSSLMode sslMode,
boost::optional<bool> helloOk,
boost::optional<bool> grpc,
OptionsMap options)
: _connectString(std::move(connectString)),
_user(user),
_password(password),
_database(database),
_retryWrites(std::move(retryWrites)),
_sslMode(sslMode),
_helloOk(helloOk),
_gRPC(grpc),
_options(std::move(options)) {}
#endif
static MongoURI parseImpl(StringData url);
ConnectionString _connectString;
@ -350,10 +322,6 @@ private:
boost::optional<bool> _retryWrites;
transport::ConnectSSLMode _sslMode = transport::kGlobalSSLMode;
boost::optional<bool> _helloOk;
// TODO: SERVER-80343 Remove this ifdef once gRPC is compiled on all variants
#ifdef MONGO_CONFIG_GRPC
boost::optional<bool> _gRPC;
#endif
OptionsMap _options;
};

View File

@ -77,10 +77,6 @@ struct URITestCase {
MongoURI::OptionsMap options;
std::string database;
ConnectSSLMode sslMode;
// TODO: SERVER-80343 Remove this ifdef once gRPC is compiled on all variants
#ifdef MONGO_CONFIG_GRPC
bool gRPC = false;
#endif
};
struct InvalidURITestCase {
@ -513,30 +509,6 @@ const URITestCase validCases[] = {
{"mongodb://localhost/?ssl=false", "", "", kMaster, "", 1, {{"ssl", "false"}}, "", kDisableSSL},
{"mongodb://localhost/?tls=true", "", "", kMaster, "", 1, {{"tls", "true"}}, "", kEnableSSL},
{"mongodb://localhost/?tls=false", "", "", kMaster, "", 1, {{"tls", "false"}}, "", kDisableSSL},
// TODO: SERVER-80343 Remove this ifdef once gRPC is compiled on all variants
#ifdef MONGO_CONFIG_GRPC
{"mongodb://localhost", "", "", kMaster, "", 1, {}, "", kDisableSSL, false},
{"mongodb://localhost/?grpc=false",
"",
"",
kMaster,
"",
1,
{{"grpc", "false"}},
"",
kGlobalSSLMode,
false},
{"mongodb://localhost/?grpc=true",
"",
"",
kMaster,
"",
1,
{{"grpc", "true"}},
"",
kGlobalSSLMode,
true},
#endif
};
const InvalidURITestCase invalidCases[] = {
@ -612,10 +584,6 @@ const InvalidURITestCase invalidCases[] = {
{"mongodb://127.0.0.1:1234/dbName?ssl=blah", ErrorCodes::FailedToParse},
{"mongodb://127.0.0.1:1234/dbName?tls=blah", ErrorCodes::FailedToParse},
// TODO: SERVER-80343 Remove this ifdef once gRPC is compiled on all variants
#ifdef MONGO_CONFIG_GRPC
{"mongodb://127.0.0.1:1234/dbName?gRPC=blah", ErrorCodes::FailedToParse},
#endif
};
// Helper Method to take a filename for a json file and return the array of tests inside of it

View File

@ -104,9 +104,6 @@
// Defined if WiredTiger storage engine is enabled
@mongo_config_wiredtiger_enabled@
// Defined if grpc support is enabled
@mongo_config_grpc@
// Defined if the glibc rseq header is present
@mongo_config_glibc_rseq@

View File

@ -2924,9 +2924,6 @@ mongo_cc_library(
}) + select({
"//bazel/config:use_wiredtiger_enabled": ["//src/mongo/db/storage/wiredtiger:storage_wiredtiger"],
"//conditions:default": [],
}) + select({
"//bazel/config:build_grpc_enabled": ["//src/mongo/transport/grpc:grpc_parameters_idl"],
"//conditions:default": [],
}) + select({
"//bazel/config:tcmalloc_google_enabled": ["//src/mongo/util:tcmalloc_set_parameter"],
"//bazel/config:tcmalloc_gperf_enabled": ["//src/mongo/util:tcmalloc_set_parameter"],

View File

@ -102,11 +102,6 @@ public:
using namespace fmt::literals;
auto const addr = T::addr(environment);
if (addr.getType() == AF_UNSPEC) {
// GRPCTransportLayer doesn't know server local address.
return {ErrorCodes::AuthenticationRestrictionUnmet,
"{} restriction can not be verified when address is unknown"_format(T::label)};
}
if (!addr.isIP()) {
std::ostringstream s;

View File

@ -73,10 +73,6 @@ struct ServerGlobalParams {
ShardServerPort = 27018,
ConfigServerPort = 27019,
CryptDServerPort = 27020,
// TODO: SERVER-80343 Remove this ifdef once gRPC is compiled on all variants
#ifdef MONGO_CONFIG_GRPC
DefaultGRPCServerPort = 27021,
#endif
DefaultMagicRestorePort = 27022,
};
@ -163,12 +159,6 @@ struct ServerGlobalParams {
// True if the current binary version is an LTS Version.
static constexpr bool kIsLTSBinaryVersion = false;
// TODO: SERVER-80343 Remove this ifdef once gRPC is compiled on all variants
#ifdef MONGO_CONFIG_GRPC
int grpcPort = DefaultGRPCServerPort;
int grpcServerMaxThreads = 1000;
#endif
/**
* Represents a "snapshot" of the in-memory FCV at a particular point in time.
* This is useful for callers who need to perform multiple FCV checks and expect the checks to

View File

@ -423,7 +423,6 @@ def get_config_header_substs():
('@mongo_config_use_libunwind@', 'MONGO_CONFIG_USE_LIBUNWIND'),
('@mongo_config_use_raw_latches@', 'MONGO_CONFIG_USE_RAW_LATCHES'),
('@mongo_config_wiredtiger_enabled@', 'MONGO_CONFIG_WIREDTIGER_ENABLED'),
('@mongo_config_grpc@', 'MONGO_CONFIG_GRPC'),
('@mongo_config_glibc_rseq@', 'MONGO_CONFIG_GLIBC_RSEQ'),
('@mongo_config_tcmalloc_google@', 'MONGO_CONFIG_TCMALLOC_GOOGLE'),
('@mongo_config_tcmalloc_gperf@', 'MONGO_CONFIG_TCMALLOC_GPERF'),

View File

@ -636,9 +636,6 @@ mongo_cc_library(
] + select({
"@platforms//os:windows": ["//src/mongo/db:windows_options_idl"],
"//conditions:default": [],
}) + select({
"//bazel/config:build_grpc_enabled": ["//src/mongo/transport/grpc:grpc_parameters_idl"],
"//conditions:default": [],
}) + select({
"//bazel/config:ssl_enabled": ["//src/mongo/util/net:ssl_options_server"],
"//conditions:default": [],

View File

@ -118,7 +118,6 @@ const JSFunctionSpec MongoBase::methods[] = {
MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(isReplicaSetMember, MongoExternalInfo),
MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(isMongos, MongoExternalInfo),
MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(isTLS, MongoExternalInfo),
MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(isGRPC, MongoExternalInfo),
MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(getApiParameters, MongoExternalInfo),
MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(_getCompactionTokens, MongoExternalInfo),
MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(_runCommandImpl, MongoExternalInfo),
@ -754,17 +753,6 @@ void MongoExternalInfo::construct(JSContext* cx, JS::CallArgs args) {
}
}
}
#ifdef MONGO_CONFIG_GRPC
if (cs.isGRPC()) {
uassert(ErrorCodes::InvalidOptions,
"Cannot enable gRPC mode when connecting to a replica set",
cs.type() != ConnectionString::ConnectionType::kReplicaSet);
uassert(ErrorCodes::InvalidOptions,
"Authentication is not currently supported when gRPC mode is enabled",
cs.getUser().empty());
}
#endif
boost::optional<std::string> appname = cs.getAppName();
std::string errmsg;
@ -845,12 +833,6 @@ void MongoBase::Functions::isTLS::call(JSContext* cx, JS::CallArgs args) {
args.rval().setBoolean(conn->isTLS());
}
void MongoBase::Functions::isGRPC::call(JSContext* cx, JS::CallArgs args) {
auto conn = getConnection(args);
args.rval().setBoolean(conn->isGRPC());
}
void MongoBase::Functions::getApiParameters::call(JSContext* cx, JS::CallArgs args) {
auto conn = getConnection(args);
ValueReader(cx, args.rval()).fromBSON(conn->getApiParameters().toBSON(), nullptr, false);

View File

@ -100,7 +100,6 @@ struct MongoBase : public BaseInfo {
MONGO_DECLARE_JS_FUNCTION(isReplicaSetMember);
MONGO_DECLARE_JS_FUNCTION(isMongos);
MONGO_DECLARE_JS_FUNCTION(isTLS);
MONGO_DECLARE_JS_FUNCTION(isGRPC);
MONGO_DECLARE_JS_FUNCTION(getApiParameters);
MONGO_DECLARE_JS_FUNCTION(_getCompactionTokens);
MONGO_DECLARE_JS_FUNCTION(_runCommandImpl);
@ -110,7 +109,7 @@ struct MongoBase : public BaseInfo {
MONGO_DECLARE_JS_FUNCTION(getShellPort);
};
static const JSFunctionSpec methods[32];
static const JSFunctionSpec methods[31];
static const char* const className;
static const unsigned classFlags = JSCLASS_HAS_RESERVED_SLOTS(MongoBaseSlotCount);

View File

@ -206,10 +206,7 @@ mongo_cc_library(
"//src/mongo:base",
"//src/mongo/client:native_sasl_client",
"//src/mongo/util/options_parser",
] + select({
"//bazel/config:build_grpc_enabled": ["shell_options_grpc_idl"],
"//conditions:default": [],
}),
],
)
mongo_cc_library(
@ -434,17 +431,6 @@ mongo_cc_binary(
],
)
mongo_idl_library(
name = "shell_options_grpc_idl",
src = "shell_options_grpc.idl",
idl_deps = [
"//src/mongo/db:basic_types_idl_gen",
],
deps = [
"//src/mongo/db:basic_types_idl",
],
)
mongo_idl_library(
name = "kms_idl",
src = "kms.idl",

View File

@ -127,11 +127,6 @@
#include "mongo/util/version.h"
#include "mongo/util/version/releases.h"
#ifdef MONGO_CONFIG_GRPC
#include "mongo/transport/grpc/grpc_transport_layer.h"
#include "mongo/transport/grpc/grpc_transport_layer_impl.h"
#endif
#ifdef _WIN32
#include <io.h>
#include <shlobj.h>
@ -824,29 +819,10 @@ int mongo_main(int argc, char* argv[]) {
parsedURI.setOptionIfNecessary("authSource"s, shellGlobalParams.authenticationDatabase);
parsedURI.setOptionIfNecessary("gssapiServiceName"s, shellGlobalParams.gssapiServiceName);
parsedURI.setOptionIfNecessary("gssapiHostName"s, shellGlobalParams.gssapiHostName);
// TODO: SERVER-80343 Remove this ifdef once gRPC is compiled on all variants
#ifdef MONGO_CONFIG_GRPC
parsedURI.setOptionIfNecessary("gRPC"s, shellGlobalParams.gRPC ? "true" : "false");
#endif
// Configure the correct TL based on URI options.
std::unique_ptr<transport::TransportLayer> tl;
#ifdef MONGO_CONFIG_GRPC
if (parsedURI.isGRPC() || shellGlobalParams.gRPC) {
// Create the client metadata.
boost::optional<std::string> appname = parsedURI.getAppName();
BSONObjBuilder bob;
uassertStatusOK(DBClientSession::appendClientMetadata(
appname.value_or(MongoURI::kDefaultTestRunnerAppName), &bob));
auto metadataDoc = bob.obj();
transport::grpc::GRPCTransportLayer::Options grpcOpts;
grpcOpts.enableEgress = true;
grpcOpts.clientMetadata = metadataDoc.getObjectField(kMetadataDocumentName).getOwned();
tl = std::make_unique<transport::grpc::GRPCTransportLayerImpl>(
serviceContext, grpcOpts, nullptr);
} else
#endif
{
transport::AsioTransportLayer::Options opts;
opts.enableIPv6 = shellGlobalParams.enableIPv6;

View File

@ -646,9 +646,6 @@ MongoRunner.mongoOptions = function(opts) {
const setParameters = jsTestOptions().setParameters || {};
const tlsEnabled = (opts.tlsMode && opts.tlsMode != "disabled") ||
(opts.sslMode && opts.sslMode != "disabled");
if (setParameters.featureFlagGRPC && tlsEnabled) {
opts.grpcPort = opts.grpcPort || allocatePort();
}
opts.pathOpts =
Object.merge(opts.pathOpts || {}, {port: "" + opts.port, runId: "" + opts.runId});

View File

@ -153,9 +153,6 @@ startParallelShell = function(jsCode, port, noConnect, ...optionArgs) {
args.push("--nodb");
} else if (typeof (globalThis.db) == "object") {
const setParameters = jsTestOptions().setParameters || {};
if (globalThis.db.getMongo().isGRPC()) {
args.push("--gRPC");
}
jsCode = "db = db.getSiblingDB('" + globalThis.db.getName() + "');" + jsCode;
}

View File

@ -214,16 +214,6 @@ Status storeMongoShellOptions(const moe::Environment& params,
shellGlobalParams.shouldUseImplicitSessions = false;
}
// TODO: SERVER-80343 Remove this ifdef once gRPC is compiled on all variants
#ifdef MONGO_CONFIG_GRPC
if (params.count("gRPC")) {
shellGlobalParams.gRPC = true;
}
if (params.count("gRPCAuthToken")) {
shellGlobalParams.gRPCAuthToken = params["gRPCAuthToken"].as<string>();
}
#endif
/* This is a bit confusing, here are the rules:
*
* if nodb is set then all positional parameters are files

View File

@ -102,12 +102,6 @@ struct ShellGlobalParams {
int jsHeapLimitMB = 0;
AssignableAtomicBool nokillop{false};
Seconds idleSessionTimeout = Seconds{0};
// TODO: SERVER-80343 Remove this ifdef once gRPC is compiled on all variants
#ifdef MONGO_CONFIG_GRPC
bool gRPC = false;
boost::optional<std::string> gRPCAuthToken;
#endif
};
extern ShellGlobalParams shellGlobalParams;

View File

@ -1,51 +0,0 @@
# Copyright (C) 2023-present MongoDB, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the Server Side Public License, version 1,
# as published by MongoDB, Inc.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Server Side Public License for more details.
#
# You should have received a copy of the Server Side Public License
# along with this program. If not, see
# <http://www.mongodb.com/licensing/server-side-public-license>.
#
# As a special exception, the copyright holders give permission to link the
# code of portions of this program with the OpenSSL library under certain
# conditions as described in each individual source file and distribute
# linked combinations including the program with the OpenSSL library. You
# must comply with the Server Side Public License in all respects for
# all of the code used other than as permitted herein. If you modify file(s)
# with this exception, you may extend this exception to your version of the
# file(s), but you are not obligated to do so. If you do not wish to do so,
# delete this exception statement from your version. If you delete this
# exception statement from all source files in the program, then also delete
# it in the license file.
#
global:
cpp_namespace: "mongo"
cpp_includes:
- "mongo/shell/shell_options.h"
- "mongo/shell/shell_utils.h"
configs:
source: [ cli, ini, yaml ]
imports:
- "mongo/db/basic_types.idl"
configs:
"gRPC":
description: "Enable gRPC support (disabled by default)"
arg_vartype: Switch
requires: tls
conflicts: [ "tls.CRLFile", "tls.disabledProtocols", "tls.FIPSMode", "tls.tlsCertificateKeyFilePassword" ]
"gRPCAuthToken":
description: "Authentication token to be attached to gRPC requests"
arg_vartype: String
requires: gRPC
hidden: true

View File

@ -245,10 +245,7 @@ mongo_cc_library(
"transport_layer",
"//src/mongo/db:server_base",
"//src/third_party/asio-master:asio",
] + select({
"//bazel/config:build_grpc_enabled": ["//src/mongo/transport/grpc:grpc_transport_layer"],
"//conditions:default": [],
}),
],
)
mongo_idl_library(
@ -298,9 +295,6 @@ mongo_cc_unit_test(
] + select({
"@platforms//os:linux": ["session_manager_common_test.cpp"],
"//conditions:default": [],
}) + select({
"//bazel/config:build_grpc_enabled": ["transport_layer_manager_grpc_test.cpp"],
"//conditions:default": [],
}),
data = ["//jstests/libs:test_pem_files"],
tags = ["mongo_unittest_fourth_group"],

View File

@ -1,134 +0,0 @@
load("//bazel:mongo_src_rules.bzl", "mongo_cc_library", "mongo_cc_unit_test", "mongo_idl_library")
package(default_visibility = ["//visibility:public"])
exports_files(glob([
"*.cpp",
"*.h",
"*.inl",
"*.hpp",
"*.py",
"*.in",
]))
filegroup(
name = "grpc_global_hdrs",
srcs = glob([
"*.h",
"*.inl",
"*.hpp",
]),
)
mongo_cc_library(
name = "grpc_transport_layer",
srcs = [
"//src/mongo/transport/grpc:client.cpp",
"//src/mongo/transport/grpc:client_cache.cpp",
"//src/mongo/transport/grpc:grpc_session_manager.cpp",
"//src/mongo/transport/grpc:grpc_transport_layer_impl.cpp",
"//src/mongo/transport/grpc:server.cpp",
"//src/mongo/transport/grpc:service.cpp",
"//src/mongo/transport/grpc:util.cpp",
"//src/mongo/transport/grpc:wire_version_provider.cpp",
],
deps = [
"//src/mongo/client:connection_string",
"//src/mongo/db:server_base",
"//src/mongo/db:service_context",
"//src/mongo/db:wire_version",
"//src/mongo/db/commands:server_status_core",
"//src/mongo/rpc:client_metadata",
"//src/mongo/transport:service_executor",
"//src/mongo/transport:session_manager",
"//src/mongo/transport:transport_layer_common",
"//src/mongo/transport/grpc:grpc_feature_flag_idl",
"//src/mongo/util/net:network",
"//src/mongo/util/net:ssl_util",
"//src/mongo/util/options_parser",
"//src/third_party/grpc:grpc++_reflection",
],
)
mongo_cc_library(
name = "grpc_transport_mock",
srcs = [
"//src/mongo/transport/grpc:grpc_transport_layer_mock.cpp",
"//src/mongo/transport/grpc:mock_client_stream.cpp",
"//src/mongo/transport/grpc:mock_server_context.cpp",
"//src/mongo/transport/grpc:mock_server_stream.cpp",
],
deps = [
"//src/mongo:base",
"//src/mongo/db:service_context",
"//src/mongo/rpc:client_metadata",
"//src/mongo/transport/grpc:grpc_transport_layer",
"//src/third_party/grpc:grpc++_reflection",
],
)
mongo_cc_unit_test(
name = "grpc_transport_layer_test",
srcs = [
"//src/mongo/transport/grpc:channel_pool_test.cpp",
"//src/mongo/transport/grpc:client_cache_test.cpp",
"//src/mongo/transport/grpc:grpc_client_test.cpp",
"//src/mongo/transport/grpc:grpc_session_test.cpp",
"//src/mongo/transport/grpc:grpc_transport_layer_test.cpp",
"//src/mongo/transport/grpc:mock_client_test.cpp",
"//src/mongo/transport/grpc:mock_server_stream_test.cpp",
"//src/mongo/transport/grpc:mock_stub_test.cpp",
"//src/mongo/transport/grpc:server_test.cpp",
"//src/mongo/transport/grpc:service_test.cpp",
],
tags = ["mongo_unittest_second_group"],
deps = [
"//src/mongo:base",
"//src/mongo/db:service_context_non_d",
"//src/mongo/db:service_context_test_fixture",
"//src/mongo/db:wire_version",
"//src/mongo/rpc:message",
"//src/mongo/transport:service_executor",
"//src/mongo/transport/grpc:grpc_transport_layer",
"//src/mongo/transport/grpc:grpc_transport_mock",
"//src/mongo/util:clock_source_mock",
"//src/mongo/util:periodic_runner_factory",
"//src/third_party/grpc:grpc++_reflection",
],
)
mongo_cc_unit_test(
name = "grpc_core_test",
srcs = [
"core_test.cpp",
],
tags = ["mongo_unittest_first_group"],
target_compatible_with = select({
"//bazel/config:build_grpc_enabled": [],
"//conditions:default": ["@platforms//:incompatible"],
}),
deps = [
"//src/mongo:base",
"//src/third_party/grpc:grpc++_reflection",
],
)
mongo_idl_library(
name = "grpc_feature_flag_idl",
src = "grpc_feature_flag.idl",
idl_deps = [
"//src/mongo/db:basic_types_idl_gen",
],
deps = [
"//src/mongo/db:basic_types_idl",
],
)
mongo_idl_library(
name = "grpc_parameters_idl",
src = "grpc_parameters.idl",
target_compatible_with = select({
"//bazel/config:build_grpc_enabled": [],
"//conditions:default": ["@platforms//:incompatible"],
}),
)

View File

@ -1,155 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <boost/optional.hpp>
#include <string>
#include "mongo/util/interruptible.h"
#include "mongo/util/producer_consumer_queue.h"
#include "mongo/util/shared_buffer.h"
#include "mongo/util/time_support.h"
namespace mongo::transport::grpc {
/**
* A bidirectional channel with two ends. Each end constitutes a single producer and a single
* consumer.
*
* The "left" and "right" ends of the pipe have identical capabilities.
*/
class BidirectionalPipe {
public:
class End {
public:
/**
* Attempts to write a message to the pipe, returning true on success.
* If the pipe is closed before or during the write, this method returns false.
* If the write is interrupted by the provided Interruptible, this method will throw an
* exception.
*/
bool write(ConstSharedBuffer msg,
Interruptible* interruptible = Interruptible::notInterruptible()) {
try {
auto toWrite = SharedBuffer::allocate(msg.capacity());
memcpy(toWrite.get(), msg.get(), msg.capacity());
_sendHalf.push(std::move(toWrite), interruptible);
return true;
} catch (DBException& e) {
if (_isPipeClosedError(e)) {
return false;
}
throw;
}
}
/**
* Attempts to read a message to the pipe.
* If the pipe is closed before or during the read, this method returns
* boost::optional::none(). If the read is interrupted by the provided Interruptible, this
* method will throw an exception.
*/
boost::optional<SharedBuffer> read(
Interruptible* interruptible = Interruptible::notInterruptible()) {
try {
return _recvHalf.pop(interruptible);
} catch (DBException& e) {
if (_isPipeClosedError(e)) {
return boost::none;
}
throw;
}
}
/**
* Close both the read and write halves of this end of the pipe. In-progress reads and
* writes on this end and writes on the other end will be interrupted.
*
* Messages that have already been transmitted through this end of the pipe can still be
* read by the other end.
*/
void close() {
_sendHalf.close();
_recvHalf.close();
}
/**
* Close only the writing portion of this end of the pipe. This will cause any reads on
* the other end to fail once all the previously sent messages have been read. Messages can
* still be read from this end.
*/
void closeWriting() {
_sendHalf.close();
}
/**
* Returns true when at least one of the following conditions is met:
* - This end of the pipe is closed.
* - The other end of the pipe is closed and there are no more messages to be read.
*/
bool isConsumed() const {
auto stats = _recvHalfCtrl.getStats();
return stats.consumerEndClosed || (stats.queueDepth == 0 && stats.producerEndClosed);
}
private:
friend BidirectionalPipe;
explicit End(SingleProducerSingleConsumerQueue<SharedBuffer>::Producer send,
SingleProducerSingleConsumerQueue<SharedBuffer>::Consumer recv,
SingleProducerSingleConsumerQueue<SharedBuffer>::Controller recvCtrl)
: _sendHalf{std::move(send)},
_recvHalf{std::move(recv)},
_recvHalfCtrl(std::move(recvCtrl)) {}
bool _isPipeClosedError(const DBException& e) const {
return e.code() == ErrorCodes::ProducerConsumerQueueEndClosed ||
e.code() == ErrorCodes::ProducerConsumerQueueConsumed;
}
SingleProducerSingleConsumerQueue<SharedBuffer>::Producer _sendHalf;
SingleProducerSingleConsumerQueue<SharedBuffer>::Consumer _recvHalf;
SingleProducerSingleConsumerQueue<SharedBuffer>::Controller _recvHalfCtrl;
};
BidirectionalPipe() {
SingleProducerSingleConsumerQueue<SharedBuffer>::Pipe pipe1;
SingleProducerSingleConsumerQueue<SharedBuffer>::Pipe pipe2;
left = std::unique_ptr<End>(new End(
std::move(pipe1.producer), std::move(pipe2.consumer), std::move(pipe2.controller)));
right = std::unique_ptr<End>(new End(
std::move(pipe2.producer), std::move(pipe1.consumer), std::move(pipe1.controller)));
}
std::unique_ptr<End> left;
std::unique_ptr<End> right;
};
} // namespace mongo::transport::grpc

View File

@ -1,267 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <memory>
#include <vector>
#include "mongo/logv2/log.h"
#include "mongo/stdx/mutex.h"
#include "mongo/stdx/unordered_map.h"
#include "mongo/transport/transport_layer.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/clock_source.h"
#include "mongo/util/functional.h"
#include "mongo/util/future.h"
#include "mongo/util/net/hostandport.h"
#include "mongo/util/synchronized_value.h"
#include "mongo/util/time_support.h"
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kNetwork
namespace mongo::transport::grpc {
/**
* Allows maintaining a pool of `Channel` objects and using them to create instances of `Stub`.
* `Channel` and `Stub` are defined as template types to facilitate unit-testing.
* This type is oblivious to how gRPC channels and stubs are created, and relies on the factory
* functions (`ChannelFactory` and `StubFactory`) to handle that.
*/
template <class Channel, class Stub>
class ChannelPool : public std::enable_shared_from_this<ChannelPool<Channel, Stub>> {
public:
using SSLModeResolver = unique_function<bool(ConnectSSLMode)>;
using ChannelFactory = unique_function<Channel(const HostAndPort&, bool)>;
using StubFactory = unique_function<Stub(Channel&, Milliseconds)>;
/**
* Maintains state for an individual `Channel`: allows deferred creation of `Channel` as well as
* tracking its last-used-time.
* All public APIs for this type are thread-safe.
*/
class ChannelState {
public:
ChannelState(std::shared_ptr<ChannelPool> pool,
HostAndPort remote,
bool useSSL,
Future<Channel> channel)
: _pool(std::move(pool)),
_remote(std::move(remote)),
_useSSL(useSSL),
_channel(std::move(channel)) {}
ChannelState(const ChannelState&) = delete;
ChannelState& operator=(const ChannelState&) = delete;
Channel& channel() {
return _channel.get();
}
const HostAndPort& remote() const {
return _remote;
}
bool useSSL() const {
return _useSSL;
}
void updateLastUsed() {
auto now = _pool->_clockSource->now();
**_lastUsed = now;
}
Date_t lastUsed() const {
return **_lastUsed;
}
private:
std::shared_ptr<ChannelPool> _pool;
const HostAndPort _remote;
const bool _useSSL;
Future<Channel> _channel;
synchronized_value<Date_t> _lastUsed;
};
/**
* RAII type for `Stub` that helps with identifying idle channels.
* In terms of thread-safety, this type follows the semantics of `Stub`.
*/
class StubHandle {
public:
explicit StubHandle(std::shared_ptr<ChannelState> channelState, Stub stub)
: _channelState(std::move(channelState)), _stub(std::move(stub)) {}
~StubHandle() {
_channelState->updateLastUsed();
}
Stub& stub() {
return _stub;
}
private:
std::shared_ptr<ChannelState> _channelState;
Stub _stub;
};
/**
* Constructs a new instance of `ChannelPool` and accepts the following:
* - `clockSource` is used to record last-used-time for channels (doesn't need much accuracy).
* - `sslModeResolver` translates `ConnectSSLMode` into a boolean that decides if an encrypted
* channel should be used to create new stubs.
* - `channelFactory` is the factory for creating new channels.
* - `stubFactory` is the factory for creating new stubs.
*/
explicit ChannelPool(ClockSource* clockSource,
SSLModeResolver sslModeResolver,
ChannelFactory channelFactory,
StubFactory stubFactory)
: _clockSource(clockSource),
_sslModeResolver(std::move(sslModeResolver)),
_channelFactory(std::move(channelFactory)),
_stubFactory(std::move(stubFactory)) {}
/**
* Creates a new stub to `remote` that uses `sslMode`. Internally, an existing channel is used
* to create the new stub, if available. Otherwise, a new channel is created.
*/
std::unique_ptr<StubHandle> createStub(HostAndPort remote,
ConnectSSLMode sslMode,
Milliseconds timeout) {
std::shared_ptr<ChannelState> cs = [&] {
const auto useSSL = _sslModeResolver(sslMode);
ChannelMapKeyType key{remote, useSSL};
auto lk = stdx::unique_lock(_mutex);
if (auto iter = _channels.find(key); iter != _channels.end()) {
return iter->second;
} else {
auto pf = makePromiseFuture<Channel>();
auto state = std::make_shared<ChannelState>(
this->shared_from_this(), remote, useSSL, std::move(pf.future));
_channels.insert({key, state});
lk.unlock();
LOGV2_INFO(7401801,
"Creating a new gRPC channel",
"remote"_attr = remote,
"useSSL"_attr = useSSL);
pf.promise.setWith([&] { return _channelFactory(remote, useSSL); });
return state;
}
}();
auto stub = _stubFactory(cs->channel(), timeout);
return std::make_unique<StubHandle>(std::move(cs), std::move(stub));
}
/**
* Drops all idle channels that are not used for the past `sinceLastUsed` minutes. An idle
* channel is one that is not referenced by any instance of `StubHandle`. Returns the number of
* dropped channels.
* Internally, this will iterate through all channels in the pool. This should not have any
* performance implications since we drop idle channels infrequently (e.g., every 30 minutes)
* and expect the maximum number of open channels to be a two digit number.
*/
size_t dropIdleChannels(Minutes sinceLastUsed) {
auto keepIf = [threshold = _clockSource->now() - sinceLastUsed](const auto& cs) {
if (cs.use_count() > 1 || cs->lastUsed() > threshold)
// There are stubs referencing this channel, or it's recently used.
return true;
return false;
};
auto droppedChannels = _dropChannels(std::move(keepIf));
for (const auto& channel : droppedChannels) {
LOGV2_INFO(7401802,
"Dropping idle gRPC channel",
"remote"_attr = channel->remote(),
"useSSL"_attr = channel->useSSL(),
"lastUsed"_attr = channel->lastUsed());
}
return droppedChannels.size();
}
/**
* Drops all channels and returns the number of dropped channels. May only be called when all
* stub handles (i.e., instances of `StubHandle`) created by this pool are released. Otherwise,
* it will terminate the process.
*/
size_t dropAllChannels() {
auto droppedChannels = _dropChannels([](const auto&) { return false; });
for (const auto& channel : droppedChannels) {
LOGV2_INFO(7401803,
"Dropping gRPC channel as part of dropping all channels",
"remote"_attr = channel->remote(),
"useSSL"_attr = channel->useSSL());
}
return droppedChannels.size();
}
size_t size() const {
auto lk = stdx::lock_guard(_mutex);
return _channels.size();
}
private:
/**
* Iterates through all channels, calls into `shouldKeep` for each channel with a reference to
* its `ChannelState`, and decides if the channel should be dropped based on the return value.
* A channel cannot be dropped so long as it's being referenced by a `Stub`. Attempting to do
* so is a process fatal event.
* Returns a vector containing the only reference to the dropped channels.
*/
std::vector<std::shared_ptr<ChannelState>> _dropChannels(
std::function<bool(const std::shared_ptr<ChannelState>&)> shouldKeep) {
std::vector<std::shared_ptr<ChannelState>> droppedChannels;
auto lk = stdx::lock_guard(_mutex);
for (auto iter = _channels.begin(); iter != _channels.end();) {
auto prev = iter++;
const auto& cs = prev->second;
if (shouldKeep(cs))
continue;
invariant(cs.use_count() == 1, "Attempted to drop a channel with existing stubs");
droppedChannels.push_back(std::move(prev->second));
_channels.erase(prev);
}
return droppedChannels;
}
ClockSource* const _clockSource;
SSLModeResolver _sslModeResolver;
ChannelFactory _channelFactory;
StubFactory _stubFactory;
mutable stdx::mutex _mutex; // NOLINT
using ChannelMapKeyType = std::pair<HostAndPort, bool>;
stdx::unordered_map<ChannelMapKeyType, std::shared_ptr<ChannelState>> _channels;
};
} // namespace mongo::transport::grpc
#undef MONGO_LOGV2_DEFAULT_COMPONENT

View File

@ -1,248 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <memory>
#include "mongo/transport/grpc/channel_pool.h"
#include "mongo/platform/atomic_word.h"
#include "mongo/stdx/thread.h"
#include "mongo/unittest/barrier.h"
#include "mongo/unittest/death_test.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/clock_source_mock.h"
#include "mongo/util/fail_point.h"
#include "mongo/util/scopeguard.h"
#include "mongo/util/time_support.h"
namespace mongo::transport::grpc {
namespace {
MONGO_FAIL_POINT_DEFINE(blockBeforeCreatingNewChannel);
MONGO_FAIL_POINT_DEFINE(blockBeforeCreatingNewStub);
class ChannelPoolTest : public unittest::Test {
public:
class DummyChannel {};
class DummyStub {};
using PoolType = ChannelPool<DummyChannel, DummyStub>;
// The channel pool mock factory functions don't currently honor timeouts, so we use this to
// signal in tests that they should not expect timeout behavior.
static constexpr auto kNoTimeout = Milliseconds::max();
void setUp() override {
_clockSource = std::make_unique<ClockSourceMock>();
_pool = std::make_shared<PoolType>(
_clockSource.get(),
[this](ConnectSSLMode mode) { return _resolveSSLMode(mode); },
[this](const HostAndPort& remote, bool useSSL) { return _makeChannel(remote, useSSL); },
[this](DummyChannel& channel, Milliseconds) { return _makeStub(channel); });
}
void tearDown() override {
_pool.reset();
_clockSource.reset();
}
auto& clockSource() {
return *_clockSource;
}
void setSSLMode(bool enable) {
_sslMode.store(enable);
}
auto& pool() {
return *_pool;
}
private:
bool _resolveSSLMode(ConnectSSLMode mode) {
auto sslMode = _sslMode.load();
ASSERT_TRUE(mode == ConnectSSLMode::kDisableSSL || sslMode);
if (mode == ConnectSSLMode::kGlobalSSLMode)
return sslMode;
return mode == ConnectSSLMode::kEnableSSL;
}
DummyChannel _makeChannel(const HostAndPort&, bool) {
blockBeforeCreatingNewChannel.pauseWhileSet();
return {};
}
DummyStub _makeStub(DummyChannel&) {
blockBeforeCreatingNewStub.pauseWhileSet();
return {};
}
std::unique_ptr<ClockSourceMock> _clockSource;
std::shared_ptr<PoolType> _pool;
AtomicWord<bool> _sslMode{false};
};
TEST_F(ChannelPoolTest, StartsEmpty) {
ASSERT_EQ(pool().size(), 0);
}
TEST_F(ChannelPoolTest, CanReuseChannel) {
HostAndPort remote("FakeHost", 123);
auto s1 = pool().createStub(remote, ConnectSSLMode::kDisableSSL, kNoTimeout);
ASSERT_EQ(pool().size(), 1);
auto s2 = pool().createStub(remote, ConnectSSLMode::kDisableSSL, kNoTimeout);
ASSERT_EQ(pool().size(), 1);
}
TEST_F(ChannelPoolTest, ConsidersSSLMode) {
setSSLMode(true);
ON_BLOCK_EXIT([&] { setSSLMode(false); });
HostAndPort remote("FakeHost", 123);
auto s1 = pool().createStub(remote, ConnectSSLMode::kEnableSSL, kNoTimeout);
ASSERT_EQ(pool().size(), 1);
auto s2 = pool().createStub(remote, ConnectSSLMode::kDisableSSL, kNoTimeout);
ASSERT_EQ(pool().size(), 2);
}
TEST_F(ChannelPoolTest, DropUnusedChannel) {
{
// Create a new stub and immediately discard it. This should internally create a new
// channel to `SomeHost:123`.
pool().createStub({"SomeHost", 123}, ConnectSSLMode::kDisableSSL, kNoTimeout);
}
ASSERT_EQ(pool().size(), 1);
clockSource().advance(Minutes{5});
ASSERT_EQ(pool().dropIdleChannels(Minutes{5}), 1);
ASSERT_EQ(pool().size(), 0);
}
TEST_F(ChannelPoolTest, UpdatesLastUsed) {
{
auto stub = pool().createStub({"Mongo", 123}, ConnectSSLMode::kDisableSSL, kNoTimeout);
ASSERT_EQ(pool().size(), 1);
// Advance time before destroying `stub` to update the last-used-time for the channel. The
// stub, which is the only active user of its channel, is removed as we leave this scope.
clockSource().advance(Minutes{1});
}
clockSource().advance(Minutes{4});
ASSERT_EQ(pool().dropIdleChannels(Minutes{5}), 0);
ASSERT_EQ(pool().size(), 1);
}
TEST_F(ChannelPoolTest, DropNotRecentlyUsedChannelsWithoutStubs) {
HostAndPort remoteA("RemoteA", 123), remoteB("RemoteB", 123);
auto s1 = pool().createStub(remoteA, ConnectSSLMode::kDisableSSL, kNoTimeout);
{
// Create a new stub and immediately discard it. This creates a new channel to `remoteB`.
pool().createStub(remoteB, ConnectSSLMode::kDisableSSL, kNoTimeout);
}
ASSERT_EQ(pool().size(), 2);
clockSource().advance(Minutes{2});
ASSERT_EQ(pool().dropIdleChannels(Minutes{2}), 1);
// Verifying that remoteA's channel remains open.
auto s2 = pool().createStub(remoteA, ConnectSSLMode::kDisableSSL, kNoTimeout);
ASSERT_EQ(pool().size(), 1);
}
TEST_F(ChannelPoolTest, DropAllChannelsWithNoStubs) {
const auto kNumChannels = 10;
for (int i = 1; i <= kNumChannels; i++) {
// Each iteration results in creating a new channel, targeting "FakeHost:(123 + i)".
pool().createStub({"FakeHost", 123 + i}, ConnectSSLMode::kDisableSSL, kNoTimeout);
}
ASSERT_EQ(pool().size(), kNumChannels);
ASSERT_EQ(pool().dropAllChannels(), kNumChannels);
ASSERT_EQ(pool().size(), 0);
}
DEATH_TEST_F(ChannelPoolTest, DropAllChannelsWithStubs, "invariant") {
const auto kNumChannels = 10;
for (int i = 1; i <= kNumChannels; i++) {
auto stub =
pool().createStub({"FakeHost", 123 + i}, ConnectSSLMode::kDisableSSL, kNoTimeout);
if (i == kNumChannels) {
ASSERT_EQ(pool().size(), kNumChannels);
pool().dropAllChannels(); // Must be fatal.
}
}
}
TEST_F(ChannelPoolTest, CannotDropIdleChannelWhileCreatingNewStub) {
unittest::Barrier beforeCreatingStub(2);
stdx::thread worker([&] {
beforeCreatingStub.countDownAndWait();
auto stub = pool().createStub({"FakeHost", 123}, ConnectSSLMode::kDisableSSL, kNoTimeout);
});
ON_BLOCK_EXIT([&] { worker.join(); });
FailPointEnableBlock fp("blockBeforeCreatingNewChannel");
beforeCreatingStub.countDownAndWait();
fp->waitForTimesEntered(fp.initialTimesEntered() + 1);
// At this point, `worker` is blocked on the creation of a new channel, which should have
// already been added to the list of open channels.
ASSERT_EQ(pool().size(), 1);
clockSource().advance(Minutes{2});
ASSERT_EQ(pool().dropIdleChannels(Minutes{1}), 0);
}
TEST_F(ChannelPoolTest, OneChannelForMultipleStubs) {
const HostAndPort remote{"SomeHost", 1234};
unittest::Barrier beforeCreatingFirstStub(2);
unittest::Barrier beforeCreatingSecondStub(2);
stdx::thread channelCreator([&] {
beforeCreatingFirstStub.countDownAndWait();
// We create this one first, which should also create the underlying channel.
auto stub1 = pool().createStub(remote, ConnectSSLMode::kDisableSSL, kNoTimeout);
});
stdx::thread channelUser([&] {
beforeCreatingSecondStub.countDownAndWait();
// This one is created second, which should reuse the created channel.
auto stub2 = pool().createStub(remote, ConnectSSLMode::kDisableSSL, kNoTimeout);
});
ON_BLOCK_EXIT([&] {
channelCreator.join();
channelUser.join();
});
FailPointEnableBlock sFP("blockBeforeCreatingNewStub");
{
FailPointEnableBlock cFP("blockBeforeCreatingNewChannel");
beforeCreatingFirstStub.countDownAndWait();
cFP->waitForTimesEntered(cFP.initialTimesEntered() + 1);
// `channelCreator` is now blocked in the factory function for creating new channels.
beforeCreatingSecondStub.countDownAndWait();
// `channelUser` can now go ahead with creating `stub2`, but it should wait for
// `channelCreator` to return from creating the new channel.
}
sFP->waitForTimesEntered(sFP.initialTimesEntered() + 2);
ASSERT_EQ(pool().size(), 1);
}
} // namespace
} // namespace mongo::transport::grpc

View File

@ -1,383 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/transport/grpc/client.h"
#include <grpcpp/channel.h>
#include <grpcpp/create_channel.h>
#include <grpcpp/security/credentials.h>
#include <grpcpp/security/tls_certificate_provider.h>
#include <grpcpp/security/tls_certificate_verifier.h>
#include <grpcpp/security/tls_credentials_options.h>
#include "mongo/bson/bsonobj.h"
#include "mongo/db/service_context.h"
#include "mongo/stdx/mutex.h"
#include "mongo/transport/grpc/channel_pool.h"
#include "mongo/transport/grpc/client_stream.h"
#include "mongo/transport/grpc/grpc_client_context.h"
#include "mongo/transport/grpc/grpc_client_stream.h"
#include "mongo/transport/grpc/grpc_session.h"
#include "mongo/transport/grpc/util.h"
#include "mongo/transport/transport_layer.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/net/ssl_util.h"
#include "mongo/util/scopeguard.h"
#include "mongo/util/time_support.h"
#include "mongo/util/uuid.h"
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kNetwork
namespace mongo::transport::grpc {
namespace {
inline Status makeShutdownTerminationStatus() {
return {ErrorCodes::ShutdownInProgress, "gRPC client is shutting down"};
}
} // namespace
Client::Client(TransportLayer* tl, const BSONObj& clientMetadata)
: _tl(tl),
_id(UUID::gen()),
_clientMetadata(base64::encode(clientMetadata.objdata(), clientMetadata.objsize())),
_sharedState(std::make_shared<EgressSession::SharedState>()) {
_sharedState->clusterMaxWireVersion.store(util::constants::kMinimumWireVersion);
}
void Client::start(ServiceContext*) {
stdx::lock_guard lk(_mutex);
invariant(std::exchange(_state, ClientState::kStarted) == ClientState::kUninitialized,
"Cannot start a gRPC client more than once");
}
void Client::shutdown() {
decltype(_sessions) sessions;
{
stdx::lock_guard lk(_mutex);
invariant(std::exchange(_state, ClientState::kShutdown) != ClientState::kShutdown,
"Cannot shut down a gRPC client more than once");
sessions = _sessions;
}
size_t terminated = 0;
const auto shutdownStatus = makeShutdownTerminationStatus();
for (auto& ptr : sessions) {
if (auto session = ptr.lock()) {
terminated++;
session->cancel(shutdownStatus);
}
}
stdx::unique_lock lk(_mutex);
_shutdownCV.wait(lk, [&]() { return _isShutdownComplete_inlock(); });
LOGV2_DEBUG(7401601,
1,
"Shutting down gRPC client",
"id"_attr = id(),
"terminatedSessions"_attr = terminated);
}
int Client::getClusterMaxWireVersion() const {
return _sharedState->clusterMaxWireVersion.load();
}
void Client::setMetadataOnClientContext(ClientContext& ctx, const ConnectOptions& options) {
if (options.authToken) {
ctx.addMetadataEntry(util::constants::kAuthenticationTokenKey.toString(),
*options.authToken);
}
ctx.addMetadataEntry(util::constants::kClientMetadataKey.toString(), _clientMetadata);
ctx.addMetadataEntry(util::constants::kClientIdKey.toString(), _id.toString());
ctx.addMetadataEntry(util::constants::kWireVersionKey.toString(),
std::to_string(getClusterMaxWireVersion()));
}
bool Client::_isShutdownComplete_inlock() {
return _state == ClientState::kShutdown && _ongoingConnects == 0 && _sessions.empty();
}
std::shared_ptr<EgressSession> Client::connect(const HostAndPort& remote,
Milliseconds timeout,
ConnectOptions options) {
// TODO: this implementation currently acquires _mutex twice, which will have negative
// performance implications. Egress performance is not a priority at the moment, but we should
// revisit how lock contention can be reduced here in the future.
{
std::lock_guard lk(_mutex);
invariant(_state != ClientState::kUninitialized,
"Client must be started before connect can be called");
if (_state == ClientState::kShutdown) {
iasserted(makeShutdownTerminationStatus());
}
_ongoingConnects++;
}
ON_BLOCK_EXIT([&] {
stdx::lock_guard lk(_mutex);
_ongoingConnects--;
if (MONGO_unlikely(_isShutdownComplete_inlock())) {
_shutdownCV.notify_one();
}
});
auto [ctx, stream] = _streamFactory(remote, timeout, options);
auto session =
std::make_shared<EgressSession>(_tl, std::move(ctx), std::move(stream), _id, _sharedState);
stdx::lock_guard lk(_mutex);
if (MONGO_unlikely(_state == ClientState::kShutdown)) {
auto status = makeShutdownTerminationStatus();
session->cancel(status);
iasserted(status);
}
auto it = _sessions.insert(_sessions.begin(), session);
session->setCleanupCallback([this, client = weak_from_this(), it = std::move(it)](const auto&) {
if (auto anchor = client.lock()) {
stdx::lock_guard lk(_mutex);
_sessions.erase(it);
if (MONGO_unlikely(_isShutdownComplete_inlock())) {
_shutdownCV.notify_one();
}
}
});
return session;
}
namespace {
/**
* Periodically calls into a channel pool to drop idle channels. Internally, creates a periodic
* task that drops all channels that have been idle for `kDefaultChannelTimeout`. Not
* thread-safe.
*/
template <typename PoolType>
class ChannelPrunerService {
public:
void start(ServiceContext* svcCtx, PoolType pool) {
PeriodicRunner::PeriodicJob prunerJob(
"GRPCIdleChannelPrunerJob",
[pool](::mongo::Client*) { pool->dropIdleChannels(Client::kDefaultChannelTimeout); },
Client::kDefaultChannelTimeout,
// TODO(SERVER-74659): Please revisit if this periodic job could be made killable.
false /*isKillableByStepdown*/);
invariant(!_pruner);
invariant(svcCtx->getPeriodicRunner() != nullptr);
_pruner.emplace(svcCtx->getPeriodicRunner()->makeJob(std::move(prunerJob)));
_pruner->start();
}
void stop() {
if (_pruner) {
_pruner->stop();
}
}
private:
boost::optional<PeriodicRunner::JobAnchor> _pruner;
};
} // namespace
class StubFactoryImpl : public GRPCClient::StubFactory {
class Stub {
public:
using ReadMessageType = SharedBuffer;
using WriteMessageType = ConstSharedBuffer;
Stub(const std::shared_ptr<::grpc::Channel>& channel)
: _channel(channel),
_unauthenticatedCommandStreamMethod(
util::constants::kUnauthenticatedCommandStreamMethodName,
::grpc::internal::RpcMethod::BIDI_STREAMING,
channel),
_authenticatedCommandStreamMethod(
util::constants::kAuthenticatedCommandStreamMethodName,
::grpc::internal::RpcMethod::BIDI_STREAMING,
channel) {}
std::shared_ptr<ClientStream> authenticatedCommandStream(GRPCClientContext* context) {
return _makeStream(_authenticatedCommandStreamMethod, context);
}
std::shared_ptr<ClientStream> unauthenticatedCommandStream(GRPCClientContext* context) {
return _makeStream(_unauthenticatedCommandStreamMethod, context);
}
private:
std::shared_ptr<ClientStream> _makeStream(::grpc::internal::RpcMethod& method,
GRPCClientContext* context) {
auto readerWriter =
::grpc::internal::ClientReaderWriterFactory<WriteMessageType, ReadMessageType>::
Create(&*_channel, method, context->getGRPCClientContext());
return std::shared_ptr<ClientStream>(new GRPCClientStream(readerWriter));
}
std::shared_ptr<::grpc::Channel> _channel;
::grpc::internal::RpcMethod _unauthenticatedCommandStreamMethod;
::grpc::internal::RpcMethod _authenticatedCommandStreamMethod;
};
public:
explicit StubFactoryImpl(GRPCClient::Options options) : _options(std::move(options)) {}
void start(ServiceContext* const svcCtx) {
// The pool calls into `ClockSource` to record the last usage of gRPC channels. Since the
// pool is not concerned with sub-minute durations and this call happens as part of
// destroying gRPC stubs (i.e., on threads running user operations), it is important to
// use
// `FastClockSource` to minimize the performance implications of recording time on user
// operations.
_pool = std::make_shared<ChannelPool<std::shared_ptr<::grpc::Channel>, Stub>>(
svcCtx->getFastClockSource(),
// The SSL mode resolver callback always returns true here because the current
// implemention of Server requires the use of SSL. If that ever needs to change, this
// resolver will need to be updated.
[](auto) { return true; },
[&](const HostAndPort& remote, bool useSSL) {
invariant(useSSL, "SSL is required when using gRPC");
auto uri = util::toGRPCFormattedURI(remote);
auto credentials = util::isUnixSchemeGRPCFormattedURI(uri)
? ::grpc::InsecureChannelCredentials()
: ::grpc::experimental::TlsCredentials(_makeTlsOptions());
::grpc::ChannelArguments channel_args;
channel_args.SetMaxReceiveMessageSize(MaxMessageSizeBytes);
channel_args.SetMaxSendMessageSize(MaxMessageSizeBytes);
channel_args.SetCompressionAlgorithm(
::grpc_compression_algorithm::GRPC_COMPRESS_NONE);
return ::grpc::CreateCustomChannel(uri, credentials, channel_args);
},
[](std::shared_ptr<::grpc::Channel>& channel, Milliseconds connectTimeout) {
iassert(ErrorCodes::NetworkTimeout,
"Timed out waiting for gRPC channel to establish",
channel->WaitForConnected(
(Date_t::now() + connectTimeout).toSystemTimePoint()));
return Stub(channel);
});
_prunerService.start(svcCtx, _pool);
}
auto createStub(const HostAndPort& remote, Milliseconds connectTimeout) {
return _pool->createStub(std::move(remote), ConnectSSLMode::kEnableSSL, connectTimeout);
}
void stop() {
_prunerService.stop();
_pool->dropAllChannels();
}
private:
::grpc::experimental::TlsChannelCredentialsOptions _makeTlsOptions() {
::grpc::experimental::TlsChannelCredentialsOptions tlsOps;
std::vector<::grpc::experimental::IdentityKeyCertPair> certKeyPairTls;
if (_options.tlsCertificateKeyFile) {
auto certKeyPair = util::parsePEMKeyFile(_options.tlsCertificateKeyFile.get());
certKeyPairTls.push_back(
{std::move(certKeyPair.private_key), std::move(certKeyPair.cert_chain)});
tlsOps.watch_identity_key_cert_pairs();
}
if (_options.tlsCAFile) {
auto caCert = ssl_util::readPEMFile(_options.tlsCAFile.get()).getValue();
tlsOps.set_certificate_provider(
std::make_shared<::grpc::experimental::StaticDataCertificateProvider>(
caCert, certKeyPairTls));
tlsOps.watch_root_certs();
} else {
tlsOps.set_certificate_provider(
std::make_shared<::grpc::experimental::StaticDataCertificateProvider>(
certKeyPairTls));
}
if (_options.tlsAllowInvalidCertificates || _options.tlsAllowInvalidHostnames) {
// The CertificateVerifier handles extended attribute validation, and does not actually
// pertain to validating the whole certificate chain. Setting it to NoOp ensures that
// the default verifier, which verifies hostnames, is not used.
tlsOps.set_certificate_verifier(
std::make_shared<::grpc::experimental::NoOpCertificateVerifier>());
// libgrpc also performs per-call (as opposed to per-connection) hostname verification
// by default. This codepath is separate from the certificate verifier set above, so we
// also need to disable this.
tlsOps.set_check_call_host(false);
if (_options.tlsAllowInvalidCertificates) {
// This invocation ensures the certificate chain is not verified. The prior steps
// also need to be taken when tlsAllowInvalidCertificates is set even when this is
// called, since hostname verification and certificate chain verification are
// also separate codepaths within libgrpc.
tlsOps.set_verify_server_certs(false);
}
} else {
// This is the default certificate verifier used by libgrpc, but we set it explicitly
// here for clarity.
tlsOps.set_certificate_verifier(
std::make_shared<::grpc::experimental::HostNameCertificateVerifier>());
}
return tlsOps;
}
GRPCClient::Options _options;
std::shared_ptr<ChannelPool<std::shared_ptr<::grpc::Channel>, Stub>> _pool;
ChannelPrunerService<decltype(_pool)> _prunerService;
};
GRPCClient::GRPCClient(TransportLayer* tl, const BSONObj& clientMetadata, Options options)
: Client(tl, clientMetadata) {
_stubFactory = std::make_unique<StubFactoryImpl>(std::move(options));
}
void GRPCClient::start(ServiceContext* const svcCtx) {
Client::start(svcCtx);
static_cast<StubFactoryImpl&>(*_stubFactory).start(svcCtx);
}
void GRPCClient::shutdown() {
Client::shutdown();
static_cast<StubFactoryImpl&>(*_stubFactory).stop();
}
Client::CtxAndStream GRPCClient::_streamFactory(const HostAndPort& remote,
Milliseconds connectTimeout,
const ConnectOptions& options) {
auto stub =
static_cast<StubFactoryImpl&>(*_stubFactory).createStub(std::move(remote), connectTimeout);
auto ctx = std::make_shared<GRPCClientContext>();
setMetadataOnClientContext(*ctx, options);
if (options.authToken) {
return {ctx, stub->stub().authenticatedCommandStream(ctx.get())};
} else {
return {ctx, stub->stub().unauthenticatedCommandStream(ctx.get())};
}
}
} // namespace mongo::transport::grpc

View File

@ -1,147 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <memory>
#include <boost/optional.hpp>
#include "mongo/db/service_context.h"
#include "mongo/stdx/mutex.h"
#include "mongo/transport/grpc/channel_pool.h"
#include "mongo/transport/grpc/client_stream.h"
#include "mongo/transport/grpc/grpc_client_context.h"
#include "mongo/transport/grpc/grpc_client_stream.h"
#include "mongo/transport/grpc/grpc_session.h"
#include "mongo/transport/grpc/serialization.h"
#include "mongo/transport/transport_layer.h"
#include "mongo/util/duration.h"
#include "mongo/util/net/ssl_util.h"
#include "mongo/util/uuid.h"
namespace mongo::transport::grpc {
class Client : public std::enable_shared_from_this<Client> {
public:
static constexpr auto kDefaultChannelTimeout = Minutes(30);
using CtxAndStream = std::pair<std::shared_ptr<ClientContext>, std::shared_ptr<ClientStream>>;
explicit Client(TransportLayer* tl, const BSONObj& clientMetadata);
virtual ~Client() = default;
UUID id() const {
return _id;
}
virtual void start(ServiceContext*) = 0;
/**
* Cancels all outstanding sessions created from this client and blocks until they all have been
* terminated. Closes all channels to the server. This client cannot connect sessions again
* after this method returns.
*/
virtual void shutdown();
struct ConnectOptions {
boost::optional<std::string> authToken = {};
};
std::shared_ptr<EgressSession> connect(const HostAndPort& remote,
Milliseconds timeout,
ConnectOptions options);
/**
* Get this client's current idea of what the cluster's maxWireVersion is. This will be updated
* based on information received from the cluster via sessions created from this client.
*
* The initial value for this is the first wireversion that included gRPC support.
*/
int getClusterMaxWireVersion() const;
protected:
/**
* Adds entries to the provided `ClientContext's` metadata as defined in the MongoDB gRPC
* Protocol.
*/
void setMetadataOnClientContext(ClientContext& ctx, const ConnectOptions& options);
private:
enum class ClientState { kUninitialized, kStarted, kShutdown };
virtual CtxAndStream _streamFactory(const HostAndPort&,
Milliseconds,
const ConnectOptions&) = 0;
/**
* Returns whether all outstanding sessions created by this client have been destroyed and this
* client has halted establishing any new sessions.
*/
bool _isShutdownComplete_inlock();
TransportLayer* const _tl;
const UUID _id;
std::string _clientMetadata;
std::shared_ptr<EgressSession::SharedState> _sharedState;
mutable stdx::mutex _mutex; // NOLINT
stdx::condition_variable _shutdownCV;
ClientState _state = ClientState::kUninitialized;
size_t _ongoingConnects = 0;
std::list<std::weak_ptr<EgressSession>> _sessions;
};
class GRPCClient : public Client {
public:
class StubFactory {
public:
virtual ~StubFactory() = default;
};
struct Options {
boost::optional<StringData> tlsCAFile;
boost::optional<StringData> tlsCertificateKeyFile;
bool tlsAllowInvalidCertificates = false;
bool tlsAllowInvalidHostnames = false;
};
GRPCClient(TransportLayer* tl, const BSONObj& clientMetadata, Options options);
void start(ServiceContext* svcCtx) override;
void shutdown() override;
private:
CtxAndStream _streamFactory(const HostAndPort&, Milliseconds, const ConnectOptions&) override;
std::unique_ptr<StubFactory> _stubFactory;
};
} // namespace mongo::transport::grpc

View File

@ -1,48 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/transport/grpc/client_cache.h"
#include "mongo/platform/compiler.h"
namespace mongo::transport::grpc {
ClientCache::ClientCache(size_t maxSize) : _cache{LRUCache<UUID, Data, UUID::Hash>{maxSize}} {}
ClientCache::AddResult ClientCache::add(const UUID& clientId) {
auto syncCache = _cache.synchronize();
if (MONGO_likely(syncCache->find(clientId) != syncCache->end())) {
return AddResult::kRefreshed;
}
_uniqueClientsSeen.fetchAndAddRelaxed(+1);
(void)syncCache->add(clientId, {});
return AddResult::kCreated;
}
} // namespace mongo::transport::grpc

View File

@ -1,84 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <cstddef>
#include "mongo/platform/mutex.h"
#include "mongo/util/lru_cache.h"
#include "mongo/util/synchronized_value.h"
#include "mongo/util/uuid.h"
namespace mongo::transport::grpc {
/**
* An LRU cache that is used to track UUIDs provided by clients when creating gRPC streams. It can
* be used to determine if the server has communicated with a particular client before (e.g. to
* determine whether its metadata should be logged or not).
*/
class ClientCache {
public:
enum class AddResult {
kRefreshed,
kCreated,
};
/**
* The default maximum number of entries in the cache, which roughly constitutes a few MBs of
* memory usage when the cache is full (rough calculation: (sizeof(UUID)*2 + a few pointers) *
* (1 << 16)).
*/
static constexpr size_t kDefaultCacheSize = 1 << 16;
explicit ClientCache(size_t maxSize);
ClientCache() : ClientCache{kDefaultCacheSize} {}
/**
* Adds `clientId` to the LRU cache if it doesn't exist, otherwise marks it as the
* most-recently-used. It may evict the least-recently-used `clientId`.
*/
AddResult add(const UUID& clientId);
std::size_t getUniqueClientsSeen() const {
return _uniqueClientsSeen.load();
}
private:
// We only care about whether an ID has been seen, so the cached value is irrelevant.
struct Data {};
synchronized_value<LRUCache<UUID, Data, UUID::Hash>> _cache;
// An APPROXIMATION of unique clients seen over time.
// As clients fall out of the LRU, reconnects will cause them to be counted again.
AtomicWord<std::size_t> _uniqueClientsSeen{0};
};
} // namespace mongo::transport::grpc

View File

@ -1,124 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <vector>
#include "mongo/stdx/thread.h"
#include "mongo/transport/grpc/client_cache.h"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/thread_assertion_monitor.h"
#include "mongo/unittest/unittest.h"
namespace mongo::transport::grpc {
class ClientCacheTest : public unittest::Test {
public:
static constexpr size_t kCacheSize = 5;
void setUp() override {
_cache = std::make_unique<ClientCache>(kCacheSize);
}
void tearDown() override {
_cache.reset();
}
ClientCache& cache() {
return *_cache;
}
private:
std::unique_ptr<ClientCache> _cache;
};
TEST_F(ClientCacheTest, BasicAdd) {
ASSERT_EQUALS(cache().add(UUID::gen()), ClientCache::AddResult::kCreated);
ASSERT_EQUALS(cache().add(UUID::gen()), ClientCache::AddResult::kCreated);
ASSERT_EQUALS(cache().add(UUID::gen()), ClientCache::AddResult::kCreated);
ASSERT_EQUALS(cache().add(UUID::gen()), ClientCache::AddResult::kCreated);
auto uuid = UUID::gen();
ASSERT_EQUALS(cache().add(uuid), ClientCache::AddResult::kCreated);
ASSERT_EQUALS(cache().add(uuid), ClientCache::AddResult::kRefreshed);
ASSERT_EQUALS(cache().add(UUID::gen()), ClientCache::AddResult::kCreated);
}
TEST_F(ClientCacheTest, Eviction) {
// Generate 1-too-many UUIDs to fit in the cache.
std::vector<UUID> uuids = {};
for (size_t i = 0; i < kCacheSize + 1; i++) {
uuids.push_back(UUID::gen());
}
// Add them in order twice, asserting that an id is always evicted before it is readded to the
// cache.
for (size_t i = 0; i < uuids.size() * 2; i++) {
ASSERT_EQUALS(cache().add(uuids[i % uuids.size()]), ClientCache::AddResult::kCreated)
<< "clientId should have been evicted before it was added back to the cache";
}
}
TEST_F(ClientCacheTest, RepeatedAddRefreshesUsage) {
auto uuid = UUID::gen();
ASSERT_EQUALS(cache().add(uuid), ClientCache::AddResult::kCreated);
for (size_t i = 0; i < kCacheSize - 1; i++) {
ASSERT_EQUALS(cache().add(UUID::gen()), ClientCache::AddResult::kCreated);
}
ASSERT_EQUALS(cache().add(uuid), ClientCache::AddResult::kRefreshed);
ASSERT_EQUALS(cache().add(UUID::gen()), ClientCache::AddResult::kCreated);
ASSERT_EQUALS(cache().add(uuid), ClientCache::AddResult::kRefreshed)
<< "the previous add of clientId should have caused it to not be evicted when a new entry "
"was created";
}
TEST_F(ClientCacheTest, CacheIsThreadSafe) {
// Perform a bunch of concurrent operations with the cache and ensure nothing crashes and
// TSAN doesn't complain.
unittest::threadAssertionMonitoredTest([&](unittest::ThreadAssertionMonitor& monitor) {
std::vector<stdx::thread> threads;
for (auto i = 0; i < 5; i++) {
auto thread = monitor.spawn([&]() {
for (auto j = 0; j < 20; j++) {
ASSERT_EQUALS(cache().add(UUID::gen()), ClientCache::AddResult::kCreated);
}
});
threads.push_back(std::move(thread));
}
for (auto&& thread : threads) {
thread.join();
}
});
}
} // namespace mongo::transport::grpc

View File

@ -1,96 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <map>
#include <string>
#include "mongo/transport/grpc/metadata.h"
#include "mongo/util/net/hostandport.h"
#include "mongo/util/time_support.h"
namespace mongo::transport::grpc {
/**
* Base class modeling a gRPC ClientContext.
* See: https://grpc.github.io/grpc/cpp/classgrpc_1_1_client_context.html
*/
class ClientContext {
public:
virtual ~ClientContext() = default;
/**
* Add an entry to the metadata associated with the RPC.
*
* This must only be called before invoking the RPC.
*/
virtual void addMetadataEntry(const std::string& key, const std::string& value) = 0;
/**
* Retrieve the server's initial metadata.
*
* This must only be called after the first message has been received on the ClientStream
* created from the RPC that this context is associated with.
*/
virtual MetadataView getServerInitialMetadata() const = 0;
/**
* Set the deadline for the RPC to be executed using this context.
*
* This must only be called before invoking the RPC.
*/
virtual void setDeadline(Date_t deadline) = 0;
virtual Date_t getDeadline() const = 0;
/**
* Get the address of the remote server.
* This must only be called after the RPC associated with this context has been invoked.
*/
virtual HostAndPort getRemote() const = 0;
/**
* Send a best-effort out-of-band cancel on the call associated with this ClientContext. There
* is no guarantee the call will be cancelled (e.g. if the call has already finished by the time
* the cancellation is received).
*
* Note that tryCancel() will not impede the execution of any already scheduled work (e.g.
* messages already queued to be sent on a stream will still be sent), though the reported
* sucess or failure of such work may reflect the cancellation.
*
* This method is thread-safe, and can be called multiple times from any thread. It should not
* be called before this ClientContext has been used to invoke an RPC.
*
* See:
* https://grpc.github.io/grpc/cpp/classgrpc_1_1_client_context.html#abd0f6715c30287b75288015eee628984
*/
virtual void tryCancel() = 0;
};
} // namespace mongo::transport::grpc

View File

@ -1,79 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <boost/optional.hpp>
#include <grpcpp/grpcpp.h>
#include "mongo/util/shared_buffer.h"
namespace mongo::transport::grpc {
/**
* Base class modeling a synchronous client side of a gRPC stream.
* See: https://grpc.github.io/grpc/cpp/classgrpc_1_1_client_reader_writer.html
*
* ClientStream::read() is thread safe with respect to ClientStream::write(), but neither method
* should be called concurrently with another invocation of itself on the same stream.
*
* ClientStream::finish() is thread safe with respect to ClientStream::read().
*/
class ClientStream {
public:
virtual ~ClientStream() = default;
/**
* Block to read a message from the stream.
*
* Returns boost::none if the stream is closed, either cleanly or due to an underlying
* connection failure.
*/
virtual boost::optional<SharedBuffer> read() = 0;
/**
* Block to write a message to the stream.
*
* Returns true if the write was successful or false if it failed due to the stream being
* closed, either explicitly or due to an underlying connection failure.
*/
virtual bool write(ConstSharedBuffer msg) = 0;
/**
* Block waiting until all received messages have been read and the stream has been closed.
* This indicates to the server side that the client will not be sending any further messages.
*
* Returns the final status of the RPC associated with this stream.
*
* This method should only be called once.
*/
virtual ::grpc::Status finish() = 0;
};
} // namespace mongo::transport::grpc

View File

@ -1,113 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include "mongo/logv2/log.h"
#include "mongo/stdx/thread.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/future.h"
#include "mongo/transport/grpc/core_test.grpc.pb.h"
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kNetwork
namespace mongo::transport {
namespace test {
class GreeterClient {
public:
GreeterClient(std::shared_ptr<grpc::Channel> channel) : _stub(Greeter::NewStub(channel)) {}
std::string sayHello(const std::string& user) {
grpc::ClientContext context;
HelloReply reply;
HelloRequest request;
request.set_name(user);
if (auto status = _stub->sayHello(&context, request, &reply); status.ok()) {
return reply.message();
} else {
LOGV2_ERROR(7516103,
"RPC failed",
"code"_attr = status.error_code(),
"message"_attr = status.error_message());
return "RPC failed";
}
}
private:
std::unique_ptr<Greeter::Stub> _stub;
};
std::string runClient(std::string serverAddress) {
LOGV2(7516102, "Client is connecting to the server");
GreeterClient greeter(grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()));
return greeter.sayHello("world");
}
class GreeterServiceImpl final : public Greeter::Service {
grpc::Status sayHello(grpc::ServerContext*,
const HelloRequest* request,
HelloReply* reply) override {
reply->set_message("Hello " + request->name());
return grpc::Status::OK;
}
};
TEST(GRPCCore, HelloWorld) {
constexpr auto kServerAddress = "localhost:50051";
GreeterServiceImpl service;
grpc::EnableDefaultHealthCheckService(true);
grpc::ServerBuilder builder;
builder.AddListeningPort(kServerAddress, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
stdx::thread serverThread([&] {
LOGV2(7516101, "Server is listening for connections", "address"_attr = kServerAddress);
server->Wait();
});
auto pf = makePromiseFuture<std::string>();
stdx::thread clientThread(
[&] { pf.promise.setWith([&] { return runClient(kServerAddress); }); });
ASSERT_EQ(pf.future.get(), "Hello world");
server->Shutdown();
clientThread.join();
serverThread.join();
}
} // namespace test
} // namespace mongo::transport

View File

@ -1,15 +0,0 @@
syntax = "proto3";
package mongo.transport.test;
service Greeter {
rpc sayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}

View File

@ -1,97 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include "mongo/transport/grpc/client_context.h"
#include <string>
#include <grpcpp/grpcpp.h>
#include "mongo/transport/grpc/metadata.h"
#include "mongo/transport/grpc/util.h"
namespace mongo::transport::grpc {
class GRPCClientContext : public ClientContext {
public:
GRPCClientContext() = default;
~GRPCClientContext() = default;
void addMetadataEntry(const std::string& key, const std::string& value) override {
_ctx.AddMetadata(key, value);
}
// Because gRPC's GetServerInitialMetadata returns a reference to a map of gRPC reference types,
// there's no easy way for us to return a map of our wrapper types without constructing such a
// map ourselves. We construct one in each call here rather than doing it once and caching it to
// avoid the need for locking or for making this method not thread-safe. This method only needs
// to be invoked a single time anyways, so the perf concern is minimal.
//
// Note that the map cannot be created in the constructor, since the RPC might not have begun at
// that point.
MetadataView getServerInitialMetadata() const override {
MetadataView mv;
for (auto& kvp : _ctx.GetServerInitialMetadata()) {
mv.insert({StringData{kvp.first.data(), kvp.first.length()},
StringData{kvp.second.data(), kvp.second.length()}});
}
return mv;
}
Date_t getDeadline() const override {
return Date_t{_ctx.deadline()};
}
void setDeadline(Date_t deadline) override {
_ctx.set_deadline(deadline.toSystemTimePoint());
}
// Similar to getServerInitialMetadata(), we parse the URI in each invocation of this method to
// not violate const here or require any locking. This too should only need to be called once,
// so again the performance impact should be negligible.
HostAndPort getRemote() const override {
return util::parseGRPCFormattedURI(_ctx.peer());
}
void tryCancel() override {
_ctx.TryCancel();
}
::grpc::ClientContext* getGRPCClientContext() {
return &_ctx;
}
private:
::grpc::ClientContext _ctx;
};
} // namespace mongo::transport::grpc

View File

@ -1,66 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <grpcpp/support/sync_stream.h>
#include "mongo/transport/grpc/client_stream.h"
namespace mongo::transport::grpc {
class GRPCClientStream : public ClientStream {
public:
explicit GRPCClientStream(::grpc::ClientReaderWriter<ConstSharedBuffer, SharedBuffer>* stream)
: _stream{stream} {}
~GRPCClientStream() = default;
boost::optional<SharedBuffer> read() override {
SharedBuffer msg;
if (_stream->Read(&msg)) {
return std::move(msg);
} else {
return boost::none;
}
};
bool write(ConstSharedBuffer msg) override {
return _stream->Write(msg);
}
::grpc::Status finish() override {
_stream->WritesDone();
return _stream->Finish();
}
private:
::grpc::ClientReaderWriter<ConstSharedBuffer, SharedBuffer>* _stream;
};
} // namespace mongo::transport::grpc

View File

@ -1,393 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <string>
#include <vector>
#include "mongo/db/service_context_test_fixture.h"
#include "mongo/logv2/log.h"
#include "mongo/rpc/message.h"
#include "mongo/rpc/op_msg.h"
#include "mongo/transport/grpc/mock_client.h"
#include "mongo/transport/grpc/mock_wire_version_provider.h"
#include "mongo/transport/grpc/test_fixtures.h"
#include "mongo/transport/test_fixtures.h"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/concurrency/notification.h"
#include "mongo/util/net/hostandport.h"
#include "mongo/util/net/socket_utils.h"
#include "mongo/util/periodic_runner_factory.h"
#include "mongo/util/scopeguard.h"
#include "mongo/util/time_support.h"
#include "mongo/util/uuid.h"
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest
namespace mongo::transport::grpc {
class GRPCClientTest : public ServiceContextTest {
public:
virtual void setUp() override {
getServiceContext()->setPeriodicRunner(makePeriodicRunner(getServiceContext()));
}
std::shared_ptr<GRPCClient> makeClient(
GRPCClient::Options options = CommandServiceTestFixtures::makeClientOptions()) {
return std::make_shared<GRPCClient>(
nullptr /* transport layer */, makeClientMetadataDocument(), std::move(options));
}
/**
* Tests that a client with the given options validates different server certificate
* configurations as expected.
*
* - validServerCertSucceeds determines whether we should expect the client to successfully
* connect to a server with a valid TLS certificate.
*
* - mismatchedServerNameSucceeds determines whether we should expect the client to
* successfully connect to a server with a hostname not included in its certificate.
*
* - differentCAServerCertSucceeds determines whether we should expect the client to
* successfully connect to a server whose certificate is signed by a separate CA.
*
* - bothSucceeds determines whether we should expect the client to successfully connect to a
* server whose certificate does not include its hostname and is signed by a different CA.
*/
void runCertificateValidationTest(GRPCClient::Options options,
bool validServerCertSucceeds,
bool mismatchedServerNameSucceeds,
bool differentCAServerCertSucceeds,
bool bothSucceeds) {
struct CertificateValidationTestCase {
StringData description;
Server::Options serverOptions;
bool shouldSucceed;
};
std::vector<CertificateValidationTestCase> cases = {
{"Valid server certificate",
CommandServiceTestFixtures::makeServerOptions(),
validServerCertSucceeds},
{
"Mismatched server name",
[]() {
auto options = CommandServiceTestFixtures::makeServerOptions();
options.tlsCertificateKeyFile = "jstests/libs/server.pem";
// ::1 is not included as a server name in server.pem.
options.addresses = {HostAndPort("::1", test::kLetKernelChoosePort)};
return options;
}(),
mismatchedServerNameSucceeds,
},
{
"Different CAs",
[]() {
auto options = CommandServiceTestFixtures::makeServerOptions();
// The client uses jstests/libs/ca.pem by default.
options.tlsCAFile = "jstests/libs/ecdsa-ca.pem";
options.tlsCertificateKeyFile = "jstests/libs/ecdsa-server.pem";
return options;
}(),
differentCAServerCertSucceeds,
},
{
"Mismatched server name and different CAs",
[]() {
auto options = CommandServiceTestFixtures::makeServerOptions();
options.tlsCertificateKeyFile = "jstests/libs/ecdsa-server.pem";
options.tlsCAFile = "jstests/libs/ecdsa-ca.pem";
options.addresses = {HostAndPort("::1", test::kLetKernelChoosePort)};
return options;
}(),
bothSucceeds,
}};
auto makeClientThreadBody = [&](bool shouldSucceed) {
return [&, shouldSucceed](auto& server, auto& monitor) {
auto client = makeClient(options);
client->start(getServiceContext());
auto makeSession = [&](Milliseconds timeout) {
auto session =
client->connect(server.getListeningAddresses().at(0), timeout, {});
ASSERT_OK(session->finish());
};
if (shouldSucceed) {
ASSERT_DOES_NOT_THROW(
makeSession(CommandServiceTestFixtures::kDefaultConnectTimeout));
} else {
// Use a shorter timeout for connections that are intended to fail.
ASSERT_THROWS(makeSession(Milliseconds(50)), DBException);
}
};
};
for (auto& testCase : cases) {
LOGV2(8471201,
"Running certificate validation test case",
"description"_attr = testCase.description);
testCase.serverOptions.tlsAllowConnectionsWithoutCertificates = true;
CommandServiceTestFixtures::runWithServer(
[](auto) {}, makeClientThreadBody(testCase.shouldSucceed), testCase.serverOptions);
}
}
};
TEST_F(GRPCClientTest, GRPCClientConnect) {
std::vector<HostAndPort> addresses = {HostAndPort("localhost", test::kLetKernelChoosePort),
HostAndPort("localhost", test::kLetKernelChoosePort),
HostAndPort(makeUnixSockPath(1234))};
std::vector<Server::Options> serverOptions;
for (auto& addr : addresses) {
auto options = CommandServiceTestFixtures::makeServerOptions();
options.addresses = {addr};
serverOptions.push_back(std::move(options));
}
auto serverHandler = [&](const auto& options, std::shared_ptr<IngressSession> session) {
auto swClientMsg = session->sourceMessage();
auto parsedClientMsg = OpMsg::parse(uassertStatusOK(swClientMsg));
HostAndPort targetedServer{parsedClientMsg.body.getStringField("remote")};
const auto& addrs = options.addresses;
ASSERT_NE(std::find(addrs.begin(), addrs.end(), targetedServer), addrs.end());
ASSERT_OK(session->sinkMessage(swClientMsg.getValue()));
};
auto clientThreadBody = [&](auto& servers, auto& monitor) {
auto client = makeClient();
client->start(getServiceContext());
for (auto& server : servers) {
for (auto& addr : server->getListeningAddresses()) {
auto session =
client->connect(addr, CommandServiceTestFixtures::kDefaultConnectTimeout, {});
ASSERT_TRUE(session->isConnected());
OpMsg msg;
msg.body = BSON("remote" << addr.toString());
auto serialized = msg.serialize();
ASSERT_OK(session->sinkMessage(serialized));
auto serverResponse = session->sourceMessage();
ASSERT_OK(serverResponse) << "could not read response from " << addr.toString()
<< ": " << session->finish().toString();
ASSERT_EQ_MSG(serverResponse.getValue(), serialized);
ASSERT_OK(session->finish());
}
}
};
CommandServiceTestFixtures::runWithServers(serverOptions, serverHandler, clientThreadBody);
}
TEST_F(GRPCClientTest, GRPCClientConnectNoClientCertificate) {
auto options = CommandServiceTestFixtures::makeServerOptions();
options.tlsAllowConnectionsWithoutCertificates = true;
auto clientThreadBody = [&](auto& server, auto& monitor) {
GRPCClient::Options options;
options.tlsCAFile = CommandServiceTestFixtures::kCAFile;
auto client = makeClient(std::move(options));
client->start(getServiceContext());
auto session = client->connect(server.getListeningAddresses().at(0),
CommandServiceTestFixtures::kDefaultConnectTimeout,
{});
assertEchoSucceeds(*session);
ASSERT_OK(session->finish());
};
CommandServiceTestFixtures::runWithServer(
CommandServiceTestFixtures::makeEchoHandler(), clientThreadBody, std::move(options));
}
TEST_F(GRPCClientTest, CertificateValidationDefault) {
GRPCClient::Options options{};
options.tlsCAFile = CommandServiceTestFixtures::kCAFile;
runCertificateValidationTest(options,
/* validServerCertSucceeds= */ true,
/* mismatchedServerNameSucceeds= */ false,
/* differentCAServerCertSucceeds= */ false,
/* bothSucceeds= */ false);
}
TEST_F(GRPCClientTest, CertificateValidationAllowInvalidCertificates) {
GRPCClient::Options options{};
options.tlsCAFile = CommandServiceTestFixtures::kCAFile;
options.tlsAllowInvalidCertificates = true;
runCertificateValidationTest(options,
/* validServerCertSucceeds= */ true,
/* mismatchedServerNameSucceeds= */ true,
/* differentCAServerCertSucceeds= */ true,
/* bothSucceeds= */ true);
}
TEST_F(GRPCClientTest, CertificateValidationAllowInvalidHostnames) {
GRPCClient::Options options{};
options.tlsCAFile = CommandServiceTestFixtures::kCAFile;
options.tlsAllowInvalidHostnames = true;
runCertificateValidationTest(options,
/* validServerCertSucceeds= */ true,
/* mismatchedServerNameSucceeds= */ true,
/* differentCAServerCertSucceeds= */ false,
/* bothSucceeds= */ false);
}
TEST_F(GRPCClientTest, GRPCClientConnectAuthToken) {
const std::string kAuthToken = "my-auth-token";
auto serverHandler = [&](std::shared_ptr<IngressSession> session) {
ASSERT_EQ(session->authToken(), kAuthToken);
};
auto clientThreadBody = [&](auto& server, auto&) {
auto client = makeClient();
client->start(getServiceContext());
Client::ConnectOptions options;
options.authToken = kAuthToken;
auto session = client->connect(server.getListeningAddresses().at(0),
CommandServiceTestFixtures::kDefaultConnectTimeout,
options);
ASSERT_OK(session->finish());
};
CommandServiceTestFixtures::runWithServer(serverHandler, clientThreadBody);
}
TEST_F(GRPCClientTest, GRPCClientConnectNoAuthToken) {
auto serverHandler = [&](std::shared_ptr<IngressSession> session) {
ASSERT_FALSE(session->authToken());
};
auto clientThreadBody = [&](auto& server, auto&) {
auto client = makeClient();
client->start(getServiceContext());
auto session = client->connect(server.getListeningAddresses().at(0),
CommandServiceTestFixtures::kDefaultConnectTimeout,
{});
ASSERT_OK(session->finish());
};
CommandServiceTestFixtures::runWithServer(serverHandler, clientThreadBody);
}
TEST_F(GRPCClientTest, GRPCClientMetadata) {
boost::optional<UUID> clientId;
auto serverHandler = [&](std::shared_ptr<IngressSession> session) {
ASSERT_TRUE(session->getClientMetadata());
ASSERT_BSONOBJ_EQ(session->getClientMetadata()->getDocument(),
makeClientMetadataDocument());
ASSERT_TRUE(session->getRemoteClientId());
ASSERT_EQ(session->getRemoteClientId(), clientId);
};
auto clientThreadBody = [&](auto& server, auto&) {
auto client = makeClient();
client->start(getServiceContext());
clientId = client->id();
auto session = client->connect(server.getListeningAddresses().at(0),
CommandServiceTestFixtures::kDefaultConnectTimeout,
{});
ASSERT_OK(session->finish());
};
CommandServiceTestFixtures::runWithServer(serverHandler, clientThreadBody);
}
TEST_F(GRPCClientTest, GRPCClientShutdown) {
const int kNumRpcs = 10;
AtomicWord<int> numRpcsRemaining(kNumRpcs);
Notification<void> rpcsFinished;
auto serverHandler = [&](std::shared_ptr<IngressSession> session) {
const auto status = session->sourceMessage().getStatus().code();
ASSERT_EQ(status, ErrorCodes::CallbackCanceled);
ASSERT_TRUE(session->terminationStatus().has_value());
ASSERT_EQ(*session->terminationStatus(), status);
ASSERT_FALSE(session->isConnected());
if (numRpcsRemaining.subtractAndFetch(1) == 0) {
rpcsFinished.set();
}
};
auto clientThreadBody = [&](auto& server, auto& monitor) {
mongo::Client::initThread("GRPCClientShutdown", getGlobalServiceContext()->getService());
auto client = makeClient();
client->start(getServiceContext());
std::vector<std::shared_ptr<EgressSession>> sessions;
for (int i = 0; i < kNumRpcs; i++) {
sessions.push_back(client->connect(server.getListeningAddresses().at(0),
CommandServiceTestFixtures::kDefaultConnectTimeout,
{}));
}
Notification<void> shutdownFinished;
auto th = monitor.spawn([&] {
client->shutdown();
shutdownFinished.set();
});
rpcsFinished.get();
for (auto& session : sessions) {
ASSERT_TRUE(session->terminationStatus());
ASSERT_EQ(session->terminationStatus()->code(), ErrorCodes::ShutdownInProgress);
ASSERT_EQ(session->finish().code(), ErrorCodes::ShutdownInProgress);
}
ASSERT_THROWS_CODE(client->connect(server.getListeningAddresses().at(0),
CommandServiceTestFixtures::kDefaultConnectTimeout,
{}),
DBException,
ErrorCodes::ShutdownInProgress);
auto opCtx = makeOperationContext();
ASSERT_FALSE(!!shutdownFinished)
<< "shutdown should not return until all sessions have been destroyed";
sessions.clear();
ASSERT_TRUE(shutdownFinished.waitFor(opCtx.get(), Seconds(2)));
th.join();
};
CommandServiceTestFixtures::runWithServer(serverHandler, clientThreadBody);
}
} // namespace mongo::transport::grpc

View File

@ -1,42 +0,0 @@
# Copyright (C) 2024-present MongoDB, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the Server Side Public License, version 1,
# as published by MongoDB, Inc.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Server Side Public License for more details.
#
# You should have received a copy of the Server Side Public License
# along with this program. If not, see
# <http://www.mongodb.com/licensing/server-side-public-license>.
#
# As a special exception, the copyright holders give permission to link the
# code of portions of this program with the OpenSSL library under certain
# conditions as described in each individual source file and distribute
# linked combinations including the program with the OpenSSL library. You
# must comply with the Server Side Public License in all respects for
# all of the code used other than as permitted herein. If you modify file(s)
# with this exception, you may extend this exception to your version of the
# file(s), but you are not obligated to do so. If you do not wish to do so,
# delete this exception statement from your version. If you delete this
# exception statement from all source files in the program, then also delete
# it in the license file.
#
# Feature flag for gRPC.
global:
cpp_namespace: "mongo::feature_flags"
imports:
- "mongo/db/basic_types.idl"
feature_flags:
featureFlagGRPC:
description: "Feature flag to enable listening for ingress gRPC connections"
cpp_varname: gFeatureFlagGRPC
default: false
shouldBeFCVGated: false

View File

@ -1,53 +0,0 @@
# Copyright (C) 2023-present MongoDB, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the Server Side Public License, version 1,
# as published by MongoDB, Inc.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Server Side Public License for more details.
#
# You should have received a copy of the Server Side Public License
# along with this program. If not, see
# <http://www.mongodb.com/licensing/server-side-public-license>.
#
# As a special exception, the copyright holders give permission to link the
# code of portions of this program with the OpenSSL library under certain
# conditions as described in each individual source file and distribute
# linked combinations including the program with the OpenSSL library. You
# must comply with the Server Side Public License in all respects for
# all of the code used other than as permitted herein. If you modify file(s)
# with this exception, you may extend this exception to your version of the
# file(s), but you are not obligated to do so. If you do not wish to do so,
# delete this exception statement from your version. If you delete this
# exception statement from all source files in the program, then also delete
# it in the license file.
#
global:
cpp_namespace: "mongo"
cpp_includes:
- "mongo/db/server_options.h"
configs:
section: GRPC Options
source: [ cli, ini, yaml ]
configs:
"net.grpc.port":
description: gRPC listener port
short_name: grpcPort
arg_vartype: Int
cpp_varname: serverGlobalParams.grpcPort
validator: { gte: 1 }
# Default is defined in mongo/db/server_options.h
# default: 27021
"net.grpc.serverMaxThreads":
description: Limit of maximum number of gRPC session threads
short_name: grpcServerMaxThreads
arg_vartype: Int
cpp_varname: serverGlobalParams.grpcServerMaxThreads
validator: { gte: 1 }
# Default is defined in mongo/db/server_options.h
# default: 1000

View File

@ -1,84 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <string>
#include <grpcpp/grpcpp.h>
#include "mongo/transport/grpc/metadata.h"
#include "mongo/transport/grpc/server_context.h"
#include "mongo/transport/grpc/util.h"
namespace mongo::transport::grpc {
class GRPCServerContext : public ServerContext {
public:
explicit GRPCServerContext(::grpc::ServerContext* ctx)
: _ctx{ctx}, _remote{util::parseGRPCFormattedURI(_ctx->peer())} {
for (auto& kvp : _ctx->client_metadata()) {
_clientMetadata.insert({StringData{kvp.first.data(), kvp.first.length()},
StringData{kvp.second.data(), kvp.second.length()}});
}
}
~GRPCServerContext() = default;
void addInitialMetadataEntry(const std::string& key, const std::string& value) override {
_ctx->AddInitialMetadata(key, value);
}
const MetadataView& getClientMetadata() const override {
return _clientMetadata;
}
Date_t getDeadline() const override {
return Date_t{_ctx->deadline()};
}
HostAndPort getRemote() const override {
return _remote;
}
bool isCancelled() const override {
return _ctx->IsCancelled();
}
void tryCancel() override {
_ctx->TryCancel();
}
private:
::grpc::ServerContext* _ctx;
MetadataView _clientMetadata;
HostAndPort _remote;
};
} // namespace mongo::transport::grpc

View File

@ -1,61 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <grpcpp/support/sync_stream.h>
#include "mongo/transport/grpc/server_stream.h"
namespace mongo::transport::grpc {
class GRPCServerStream : public ServerStream {
public:
explicit GRPCServerStream(::grpc::ServerReaderWriter<ConstSharedBuffer, SharedBuffer>* stream)
: _stream{stream} {}
~GRPCServerStream() = default;
boost::optional<SharedBuffer> read() override {
SharedBuffer msg;
if (_stream->Read(&msg)) {
return std::move(msg);
} else {
return boost::none;
}
};
bool write(ConstSharedBuffer msg) override {
return _stream->Write(msg);
}
private:
::grpc::ServerReaderWriter<ConstSharedBuffer, SharedBuffer>* _stream;
};
} // namespace mongo::transport::grpc

View File

@ -1,704 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <charconv>
#include <memory>
#include <string>
#include <boost/optional.hpp>
#include "mongo/base/error_codes.h"
#include "mongo/base/status.h"
#include "mongo/config.h"
#include "mongo/logv2/log.h"
#include "mongo/platform/compiler.h"
#include "mongo/rpc/metadata/client_metadata.h"
#include "mongo/transport/grpc/client_context.h"
#include "mongo/transport/grpc/client_stream.h"
#include "mongo/transport/grpc/server_context.h"
#include "mongo/transport/grpc/server_stream.h"
#include "mongo/transport/grpc/util.h"
#include "mongo/transport/session.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/base64.h"
#include "mongo/util/future.h"
#include "mongo/util/shared_buffer.h"
#include "mongo/util/synchronized_value.h"
#include "mongo/util/uuid.h"
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kNetwork
namespace mongo::transport::grpc {
/**
* Captures the common semantics for ingress and egress gRPC sessions.
*
* Each GRPCSession corresponds to a single bidirectional gRPC stream, and each stream is created
* by an invocation of a gRPC method (a gRPC "call" or RPC). Each gRPC call ultimately terminates
* with a status, typically returned by the server-side if the call completes normally, which is a
* tuple of a gRPC status code and optionally a message if the status code is not OK. `GRPCSession`
* has a notion of a "termination status", which maps to this final status of the call, and it can
* be set in a few different ways depending on whether the session is an ingress or egress session.
*
* At any time, the call associated with the session may be cancelled, in which case the termination
* status will be set to a status that reflects this cancelled state.
*
* Invoking `GRPCSession::end()` will cancel the call, interrupting any in-progress reads and
* writes, unless a termination status had already been set, in which case `GRPCSession::end()` will
* have no effect. If a session does not have a termination status when it is destructed,
* `GRPCSession::end()` will be invoked in the destructor.
*
* If a session has been terminated, attempting to sink or source a message will return that
* termination status. If the session was terminated with an OK status, then
* `ErrorCodes::StreamTerminated` will be returned.
*
* See the documentation for `IngressSession` and `EgressSession` for more information on the
* termination semantics of each type of session.
*
* For more information on the lifecycle of gRPC calls, see
* https://grpc.io/docs/what-is-grpc/core-concepts/#bidirectional-streaming-rpc.
*/
class GRPCSession : public Session {
public:
explicit GRPCSession(TransportLayer* tl, HostAndPort remote)
: _tl(tl), _remote(std::move(remote)) {
SockAddr remoteAddr;
try {
remoteAddr = SockAddr::create(_remote.host(), _remote.port(), AF_UNSPEC);
} catch (const DBException& ex) {
// If {remote} fails to parse for any reason, allow the session to continue anyway.
// {_restrictionEnvironment} will end up with an AF_UNSPEC remote address
// and will fail closed, rejecting any AddressRestriction present for the user/role.
LOGV2_DEBUG(8128400,
2,
"Unable to parse peer name",
"host"_attr = _remote.host(),
"port"_attr = _remote.port(),
"error"_attr = ex.toStatus());
}
// libgrpc does not expose local socket name for us.
// This means that any attempt to use a {serverAddress} authentication restriction
// with the GRPC protocol will fail to permit login.
_restrictionEnvironment = RestrictionEnvironment(std::move(remoteAddr), SockAddr());
}
virtual ~GRPCSession() {
if (_cleanupCallback)
(*_cleanupCallback)(*this);
}
const HostAndPort& remote() const override {
return _remote;
}
const HostAndPort& local() const override {
return _local;
}
StatusWith<Message> sourceMessage() noexcept override {
if (auto s = _verifyNotTerminated(); !s.isOK()) {
return s.withContext("Could not read from gRPC stream");
}
return _readFromStream();
}
Status sinkMessage(Message m) noexcept override {
if (auto s = _verifyNotTerminated(); !s.isOK()) {
return s.withContext("Could not write to gRPC stream");
}
return _writeToStream(std::move(m));
}
/**
* Cancels the RPC associated with this session's stream.
* If the session had already been terminated, this has no effect.
*/
void end() override {
cancel(Status(ErrorCodes::CallbackCanceled, "gRPC call was cancelled"));
}
/**
* Cancels the RPC associated with the underlying gRPC stream and updates the termination status
* of the session to include the provided reason.
*
* In-progress reads and writes to this session will be interrupted, and future reads and writes
* will fail with an error.
*
* If this session is already terminated, this has no effect.
*
* The provided reason MUST be a cancellation error.
*/
void cancel(Status reason) {
invariant(ErrorCodes::isCancellationError(reason));
// Need to update terminationStatus before cancelling so that when the RPC caller/handler is
// interrupted, it will be guaranteed to have access to the reason for cancellation.
if (_setTerminationStatus(std::move(reason))) {
_tryCancel();
}
}
/**
* Returns the reason for which this session was terminated, if any. "Termination" includes
* cancellation events (e.g. network interruption, explicit cancellation, or
* exceeding the deadline) as well as explicit setting of the status via setTerminationStatus().
*
* Remains unset until termination.
*/
boost::optional<Status> terminationStatus() const {
auto cancelled = _isCancelled();
auto status = _terminationStatus.synchronize();
// If the RPC was cancelled, return a status reflecting that, including in the case
// where the RPC was cancelled after the session was already locally ended (i.e. after
// the termination status was set to OK).
if (cancelled && (!status->has_value() || (*status)->isOK())) {
return Status(ErrorCodes::CallbackCanceled,
"gRPC session was terminated due to the associated RPC being cancelled");
}
return *status;
}
TransportLayer* getTransportLayer() const final {
return _tl;
}
/**
* The following inspects the logical state of the underlying stream: the session is considered
* not connected when the user has terminated the session (either with or without an error) or
* if the RPC had been cancelled (either remotely/externally or locally).
*/
bool isConnected() final {
return !_terminationStatus->has_value() && !_isCancelled();
}
/**
* For ingress sessions, we do not distinguish between load-balanced and non-load-balanced
* streams. Egress sessions never originate from load-balancers.
*/
bool isConnectedToLoadBalancerPort() const final {
return false;
}
bool isLoadBalancerPeer() const final {
return false;
}
void setisLoadBalancerPeer(bool helloHasLoadBalancedOption) final {
tassert(ErrorCodes::OperationFailed,
"Unable to set loadBalancer option on GRPC connection",
!helloHasLoadBalancedOption);
}
/**
* All gRPC sessions are considered bound to the operation state.
*/
bool bindsToOperationState() const final {
return true;
}
/**
* Runs the provided callback when destroying the session.
* Not synchronized, thus not safe to call once the session is visible to other threads.
*/
void setCleanupCallback(std::function<void(const GRPCSession&)> callback) {
_cleanupCallback.emplace(std::move(callback));
}
/**
* The following APIs are not implemented for both ingress and egress gRPC sessions.
*/
Future<Message> asyncSourceMessage(const BatonHandle&) noexcept final {
MONGO_UNIMPLEMENTED;
}
Status waitForData() noexcept final {
MONGO_UNIMPLEMENTED;
}
Future<void> asyncWaitForData() noexcept final {
MONGO_UNIMPLEMENTED;
}
Future<void> asyncSinkMessage(Message, const BatonHandle&) noexcept final {
MONGO_UNIMPLEMENTED;
}
void cancelAsyncOperations(const BatonHandle&) final {
MONGO_UNIMPLEMENTED;
}
void setTimeout(boost::optional<Milliseconds>) final {
MONGO_UNIMPLEMENTED;
}
#ifdef MONGO_CONFIG_SSL
const std::shared_ptr<SSLManagerInterface>& getSSLManager() const final {
MONGO_UNIMPLEMENTED;
}
#endif
bool isExemptedByCIDRList(
const std::vector<std::variant<CIDR, std::string>>& exemptions) const override {
return false;
}
const RestrictionEnvironment& getAuthEnvironment() const override {
return _restrictionEnvironment;
}
protected:
/**
* Sets the termination status if it hasn't been set already.
* Returns whether the termination status was updated or not.
*/
bool _setTerminationStatus(Status status) {
auto ts = _terminationStatus.synchronize();
if (MONGO_unlikely(ts->has_value() || _isCancelled()))
return false;
ts->emplace(std::move(status));
return true;
}
synchronized_value<boost::optional<Status>> _terminationStatus;
private:
virtual void _tryCancel() = 0;
virtual bool _isCancelled() const = 0;
virtual StatusWith<Message> _readFromStream() = 0;
virtual Status _writeToStream(Message m) = 0;
/**
* Perform all the pre-read/write checks on the underlying stream, returning a status that the
* read/write should fail with if the session had already been terminated.
*
* Note that this does not check to see if the call had been externally cancelled when
* determining whether to return OK or not, since doing so on every read/write would be
* expensive. If a termination status had been set locally, this method will check for
* cancellation before returning that status, however.
*/
Status _verifyNotTerminated() const {
// Check the cached _terminationStatus directly rather than invoking terminationStatus() to
// avoid the overhead of checking if the RPC has been cancelled. If it has been cancelled
// then reading from or writing to the stream will fail anyways.
if (_terminationStatus->has_value()) {
if (auto ts = terminationStatus(); !ts->isOK()) {
return *ts;
}
return Status(ErrorCodes::StreamTerminated, "gRPC stream is terminated");
}
return Status::OK();
}
TransportLayer* const _tl;
const HostAndPort _remote;
HostAndPort _local;
RestrictionEnvironment _restrictionEnvironment;
boost::optional<std::function<void(const GRPCSession&)>> _cleanupCallback;
};
/**
* Represents an ingress gRPC session (the server side of the stream).
*
* Calling sinkMessage() or sourceMessage() is only allowed from the thread that owns the underlying
* gRPC stream. This is necessary to ensure accessing _ctx and _stream does not result in
* use-after-free.
*
* If reading from a non-terminated session fails but the associated gRPC call was not cancelled, it
* indicates that the client side of the stream stopped writing gracefully, and that the call can
* terminate cleanly. In such cases, the termination status of the session will be set to OK.
*
* If writing to a non-terminated session fails for any reason, it indicates that the client side
* will not be able to consume the response of the call or its final status, and thus the
* termination status will be set to CANCELLED.
*
* The termination status may also be set to a specific status manually (e.g. when an improperly
* formatted metadata entry has been received) via `IngressSession::setTerminationStatus`. This will
* prevent any future reads or writes from being performed, but it will not interrupt any
* in-progress reads or writes. If the stream had already been terminated, this will have no effect.
*
* If, at any time, the call is cancelled (e.g. explicitly by the client, via a network
* event, or by a server thread), the termination status will be set to CANCELLED, regardless of
* whether it had been set to some other value via setTerminationStatus.
*
* The following state diagram is an overview of the various ways an ingress session's termination
* status can be set.
*
* +----------------------------------------------------------
* | |
* Read fails, client +------------------+ |
* done writing +----->|Other termination | |
* + | | status | |
* | | +------------------+ |
* +------------------------+ | +----+ |
* | Non-terminated session | ---- setTerminationStatus---> | OK | <------+
* +------------------------+ | +----+
* | | | +---------+
* Write fails cancellation +------> |CANCELLED|
* | event +---------+
* | | ^ ^
* | | | |
* | +--------------------------------- |
* +-------------------------------------------------------
*/
class IngressSession final : public GRPCSession {
public:
IngressSession(TransportLayer* tl,
ServerContext* ctx,
ServerStream* stream,
boost::optional<UUID> clientId,
boost::optional<std::string> authToken,
boost::optional<StringData> encodedClientMetadata)
: GRPCSession(tl, ctx->getRemote()),
_ctx(ctx),
_stream(stream),
_authToken(std::move(authToken)),
_remoteClientId(clientId),
_encodedClientMetadata(std::move(encodedClientMetadata)) {
LOGV2_DEBUG(
7401101, 2, "Constructed a new gRPC ingress session", "session"_attr = toBSON());
}
~IngressSession() {
end();
LOGV2_DEBUG(7401402,
2,
"Finished cleaning up a gRPC ingress session",
"session"_attr = toBSON(),
"status"_attr = terminationStatus());
}
StatusWith<Message> _readFromStream() noexcept override {
if (auto maybeBuffer = _stream->read()) {
return Message(std::move(*maybeBuffer));
}
if (auto ts = terminationStatus()) {
return *ts;
} else {
// If the client gracefully terminated, set the RPC's final status to OK.
_setTerminationStatus(Status::OK());
return Status(ErrorCodes::StreamTerminated,
"Could not read from gRPC server stream: remote done writing");
}
}
Status _writeToStream(Message message) noexcept override {
if (_stream->write(message.sharedBuffer())) {
return Status::OK();
}
if (auto ts = terminationStatus()) {
return *ts;
} else {
// If the client closed the stream before we had a chance to return our response, mark
// the RPC as cancelled.
auto status = Status(ErrorCodes::CallbackCanceled,
"Could not write to gRPC server stream: remote done reading");
_setTerminationStatus(status);
return status;
}
}
boost::optional<UUID> getRemoteClientId() const {
return _remoteClientId;
}
std::string remoteClientIdToString() const {
return _remoteClientId ? _remoteClientId->toString() : "N/A";
}
/**
* Mark the session as logically terminated with the provided status. In-progress reads and
* writes to this session will not be interrupted, but future attempts to read or write to this
* session will fail.
*
* This has no effect if the session is already terminated.
*/
void setTerminationStatus(Status status) {
_setTerminationStatus(std::move(status));
}
/**
* The client-provided authentication token, if any.
*
* This will only return a value if the underlying stream was created via
* AuthenticatedCommandStream. Any authentication token provided by the client to
* UnauthenticatedCommandStream will be ignored.
*/
const boost::optional<std::string>& authToken() const {
return _authToken;
}
/**
* Retrieve the ClientMetadata, if any.
* The first time this method is called, the metadata document will be decoded and parsed, which
* can be expensive. It will be cached for future invocations.
*
* Throws an exception if the client provided improperly formatted metadata.
*/
boost::optional<const ClientMetadata&> getClientMetadata() const {
if (!_encodedClientMetadata) {
return boost::none;
}
auto decoded = _decodedClientMetadata.synchronize();
if (decoded->has_value()) {
return **decoded;
}
fmt::memory_buffer buffer{};
base64::decode(buffer, *_encodedClientMetadata);
BSONObj metadataDocument(buffer.data());
decoded->emplace(std::move(metadataDocument));
return **decoded;
}
void appendToBSON(BSONObjBuilder& bb) const override {
// No uint64_t BSON type
bb.append("id", static_cast<long>(id()));
bb.append("remoteClientId", remoteClientIdToString());
bb.append("remote", remote().toString());
}
private:
void _tryCancel() override {
_ctx->tryCancel();
}
bool _isCancelled() const override {
return _ctx->isCancelled();
}
// _stream is only valid while the RPC handler is still running. It should not be
// accessed after the stream has been terminated.
ServerContext* const _ctx;
ServerStream* const _stream;
boost::optional<std::string> _authToken;
boost::optional<UUID> _remoteClientId;
boost::optional<StringData> _encodedClientMetadata;
mutable synchronized_value<boost::optional<ClientMetadata>> _decodedClientMetadata;
};
/**
* Represents the client side of a gRPC stream.
*
* If reading from or writing to a non-terminated session fails, it indicates that the call has been
* terminated and that the final termination status can be retrieved (most likely as determined by
* the server-side). In such cases, the session will retrieve this status, set its termination
* status to that value, and then return it from the read/write method that failed.
*
* If, at any time, the call is cancelled (e.g. explicitly by the server, via a network
* event, or explicitly by a client thread), the termination status will be set to CANCELLED.
*
* A caller may explicitly indicate that no more client writes are forthcoming and block until the
* a final termination status has been determined by calling EgressSession::finish(). Note that this
* will block until any outstanding messages sent by the server have been read, if any.
*
* The following state diagram is an overview of the various ways an egress session's termination
* status can be set.
*
* +------------- read/write fails
* | |
* +------------------------+ V +------------------+
* | Non-terminated session | ----- finish() ------> | RPC's final |
* +------------------------+ |termination status|
* | | +------------------+
* | +---- external cancellation event
* | |
* | V
* | +---------+
* +--- cancel(), end() -----> |CANCELLED|
* ~GRPCSession +---------+
*/
class EgressSession final : public GRPCSession {
public:
/**
* Holds the state shared between multiple instances of egress session.
* This state is currently limited to the cluster's maxWireVersion.
* No alignment is needed as the shared state is not expected to be modified frequently.
*/
struct SharedState {
AtomicWord<int> clusterMaxWireVersion;
};
EgressSession(TransportLayer* tl,
std::shared_ptr<ClientContext> ctx,
std::shared_ptr<ClientStream> stream,
UUID clientId,
std::shared_ptr<SharedState> sharedState)
: GRPCSession(tl, ctx->getRemote()),
_ctx(std::move(ctx)),
_stream(std::move(stream)),
_clientId(clientId),
_sharedState(std::move(sharedState)) {
LOGV2_DEBUG(7401401, 2, "Constructed a new gRPC egress session", "session"_attr = toBSON());
}
~EgressSession() {
end();
LOGV2_DEBUG(7401403,
2,
"Finished cleaning up a gRPC egress session",
"session"_attr = toBSON(),
"status"_attr = terminationStatus());
}
StatusWith<Message> _readFromStream() noexcept override {
if (auto maybeBuffer = _stream->read()) {
_updateWireVersion();
return Message(std::move(*maybeBuffer));
}
// If _stream->read() fails, then the server has no more messages to send and a final RPC
// status should be available. Set the termination status to that and return it here.
return finish();
}
Status _writeToStream(Message message) noexcept override {
if (_stream->write(message.sharedBuffer())) {
return Status::OK();
}
// If _stream->write() fails, then the RPC has been terminated and a final RPC
// status should be available. Set the termination status to that and return it here.
return finish();
}
/**
* Get this session's current idea of what the cluster's maxWireVersion is.
*
* The initial value for this is the first wire version that included gRPC support.
*/
int getClusterMaxWireVersion() const {
return _sharedState->clusterMaxWireVersion.load();
};
/**
* Indicates to the server side that the client will not be sending any further messages, then
* blocks until all messages from the server have been read and the server has returned a final
* status. Once a status has been received, this session's termination status is updated
* accordingly.
*
* Returns the termination status.
*
* This method should be used instead of end() in most cases, since it retrieves the server's
* return status for the RPC.
*/
Status finish() {
auto status = _terminationStatus.synchronize();
if (!status->has_value()) {
*status = util::convertStatus(_stream->finish());
}
return **status;
}
UUID getClientId() const {
return _clientId;
}
void appendToBSON(BSONObjBuilder& bb) const override {
// No uint64_t BSON type
bb.append("id", static_cast<long>(id()));
bb.append("clientId", _clientId.toString());
bb.append("remote", remote().toString());
}
private:
void _tryCancel() override {
_ctx->tryCancel();
}
bool _isCancelled() const override {
// There is no way of determining this client-side outside of finish().
return false;
}
void _updateWireVersion() {
// The cluster's wire version is communicated via the initial metadata, so only need to
// check it after reading the first message.
if (_checkedWireVersion.load() || _checkedWireVersion.swap(true)) {
return;
}
auto metadata = _ctx->getServerInitialMetadata();
auto log = [](auto error) {
LOGV2_WARNING(
7401602, "Failed to parse cluster's maxWireVersion", "error"_attr = error);
};
auto clusterWireVersionEntry = metadata.find(util::constants::kClusterMaxWireVersionKey);
if (MONGO_unlikely(clusterWireVersionEntry == metadata.end())) {
log(fmt::format("cannot find metadata field: {}",
util::constants::kClusterMaxWireVersionKey));
return;
}
int wireVersion = 0;
if (std::from_chars(clusterWireVersionEntry->second.begin(),
clusterWireVersionEntry->second.end(),
wireVersion)
.ec != std::errc{}) {
log(fmt::format("invalid cluster maxWireVersion value: \"{}\"",
clusterWireVersionEntry->second));
return;
}
// The following tries to avoid modifying the cache-line that holds the
// `clusterMaxWireVersion` when possible. Considering this code-path runs once for each
// outgoing stream, and the cluster maxWireVersion is not expected to change frequently,
// this avoids unnecessary cache evictions and is considered a performance optimization.
if (_sharedState->clusterMaxWireVersion.load() != wireVersion) {
_sharedState->clusterMaxWireVersion.store(wireVersion);
}
}
AtomicWord<bool> _checkedWireVersion;
const std::shared_ptr<ClientContext> _ctx;
const std::shared_ptr<ClientStream> _stream;
UUID _clientId;
std::shared_ptr<SharedState> _sharedState;
};
} // namespace mongo::transport::grpc
#undef MONGO_LOGV2_DEFAULT_COMPONENT

View File

@ -1,148 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/transport/grpc/grpc_session_manager.h"
#include "mongo/db/commands/server_status.h"
#include "mongo/db/server_options.h"
#include "mongo/logv2/log.h"
#include "mongo/transport/grpc/grpc_feature_flag_gen.h"
#include "mongo/transport/grpc/grpc_session.h"
#include "mongo/transport/hello_metrics.h"
#include "mongo/transport/service_executor.h"
#include "mongo/transport/transport_layer_manager.h"
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kNetwork
namespace mongo::transport::grpc {
namespace {
class GRPCSection : public ServerStatusSection {
public:
using ServerStatusSection::ServerStatusSection;
bool includeByDefault() const override {
return feature_flags::gFeatureFlagGRPC.isEnabled(
serverGlobalParams.featureCompatibility.acquireFCVSnapshot());
}
BSONObj generateSection(OperationContext* opCtx, const BSONElement&) const override {
bool grpcSeen = false;
BSONObjBuilder bb;
if (auto tlm = opCtx->getServiceContext()->getTransportLayerManager()) {
tlm->forEach([&](TransportLayer* tl) {
if (auto sm = dynamic_cast<GRPCSessionManager*>(tl->getSessionManager())) {
massert(8076901, "Multiple GRPCSessionManagers", !grpcSeen);
grpcSeen = true;
sm->appendStats(&bb);
}
});
}
return bb.obj();
}
};
auto& grpcSection = *ServerStatusSectionBuilder<GRPCSection>("gRPC");
void appendI64(BSONObjBuilder* b, StringData n, auto v) {
b->append(n, static_cast<std::int64_t>(v));
}
GRPCSessionManager* getSessionManager(Client* client) {
if (!client || !client->session())
return nullptr;
auto* tl = client->session()->getTransportLayer();
if (!tl || !tl->getSessionManager())
return nullptr;
return dynamic_cast<GRPCSessionManager*>(tl->getSessionManager());
}
} // namespace
std::string GRPCSessionManager::getClientThreadName(const Session& session) const {
using namespace fmt::literals;
const auto* s = checked_cast<const IngressSession*>(&session);
if (auto id = s->getRemoteClientId()) {
return "grpc-{}-{}"_format(id->toString(), session.id());
} else {
return "grpc-{}"_format(session.id());
}
}
void GRPCSessionManager::configureServiceExecutorContext(mongo::Client* client,
bool isPrivilegedSession) const {
auto seCtx = std::make_unique<ServiceExecutorContext>();
seCtx->setThreadModel(seCtx->kInline);
stdx::lock_guard lk(*client);
ServiceExecutorContext::set(client, std::move(seCtx));
}
void GRPCSessionManager::appendStats(BSONObjBuilder* bob) const {
{
BSONObjBuilder streams(bob->subobjStart("streams"_sd));
const auto current = numOpenSessions();
appendI64(&streams, "current"_sd, current);
appendI64(&streams, "available"_sd, maxOpenSessions() - current);
appendI64(&streams, "total"_sd, numCreatedSessions());
appendI64(&streams, "successful"_sd, _successfulSessions.load());
helloMetrics.serialize(&streams);
streams.doneFast();
}
{
const auto totalOps = getTotalOperations();
const auto completedOps = getCompletedOperations();
BSONObjBuilder ops(bob->subobjStart("operations"_sd));
appendI64(&ops, "active"_sd, totalOps - completedOps);
appendI64(&ops, "total"_sd, totalOps);
ops.doneFast();
}
appendI64(bob, "uniqueClientsSeen"_sd, _clientCache->getUniqueClientsSeen());
}
void GRPCSessionManager::endSessionByClient(mongo::Client* client) {
SessionManagerCommon::endSessionByClient(client);
auto session = dynamic_cast<IngressSession*>(client->session().get());
massert(8076902,
"GRPCSessionManager::endSessionByClient handling non grpc::IngressSession instance",
session);
auto optStatus = session->terminationStatus();
invariant(optStatus);
if (optStatus->isOK())
_successfulSessions.fetchAndAddRelaxed(1);
}
} // namespace mongo::transport::grpc

View File

@ -1,63 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <memory>
#include "mongo/transport/client_transport_observer.h"
#include "mongo/transport/grpc/client_cache.h"
#include "mongo/transport/session_manager_common.h"
namespace mongo::transport::grpc {
/**
* GRPC specialization of SessionManagerCommon.
*/
class GRPCSessionManager : public SessionManagerCommon {
public:
GRPCSessionManager(ServiceContext* svcCtx,
std::shared_ptr<ClientCache> clientCache,
std::vector<std::shared_ptr<ClientTransportObserver>> observers)
: SessionManagerCommon(svcCtx, std::move(observers)),
_clientCache(std::move(clientCache)) {}
void appendStats(BSONObjBuilder* bob) const;
void endSessionByClient(mongo::Client* client) override;
protected:
std::string getClientThreadName(const Session&) const override;
void configureServiceExecutorContext(mongo::Client* client,
bool isPrivilegedSession) const override;
AtomicWord<std::size_t> _successfulSessions{0};
std::shared_ptr<ClientCache> _clientCache;
};
} // namespace mongo::transport::grpc

View File

@ -1,336 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <memory>
#include "mongo/base/error_codes.h"
#include "mongo/logv2/log.h"
#include "mongo/rpc/message.h"
#include "mongo/rpc/op_msg.h"
#include "mongo/transport/grpc/grpc_session.h"
#include "mongo/transport/grpc/test_fixtures.h"
#include "mongo/transport/grpc/util.h"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/net/hostandport.h"
#include "mongo/util/scopeguard.h"
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest
namespace mongo::transport::grpc {
namespace {
class GRPCSessionTest : public ServiceContextWithClockSourceMockTest {
public:
static constexpr auto kStreamTimeout = Seconds(1);
static constexpr auto kClientId = "c08663ac-2f6c-408d-8829-97e67eef9f23";
void setUp() override {
ServiceContextWithClockSourceMockTest::setUp();
_streamFixtures = _makeStreamFixtures();
}
void tearDown() override {
_streamFixtures.reset();
ServiceContextWithClockSourceMockTest::tearDown();
}
std::unique_ptr<IngressSession> makeIngressSession(boost::optional<UUID> remoteClientId) {
return std::make_unique<IngressSession>(nullptr,
_streamFixtures->rpc->serverCtx.get(),
_streamFixtures->rpc->serverStream.get(),
std::move(remoteClientId),
/* auth token */ boost::none,
/* client metadata */ boost::none);
}
std::unique_ptr<EgressSession> makeEgressSession(UUID clientId) {
return std::make_unique<EgressSession>(nullptr,
_streamFixtures->clientCtx,
_streamFixtures->clientStream,
std::move(clientId),
/* shared state */ nullptr);
}
template <class SessionType>
auto makeSession(UUID clientId) {
if constexpr (std::is_same<SessionType, IngressSession>::value) {
return makeIngressSession(clientId);
} else {
static_assert(std::is_same<SessionType, EgressSession>::value == true);
return makeEgressSession(clientId);
}
}
template <class SessionType>
auto makeSession() {
return makeSession<SessionType>(uassertStatusOK(UUID::parse(kClientId)));
}
/**
* Runs the provided callback twice, each time with a new fixture:
* - First with an instance of `EgressSession`.
* - Then with an instance of `IngressSession`.
* Upon completion, `_streamFixtures` is restored to its original state.
*/
using CallbackType = std::function<void(MockRPC&, GRPCSession&)>;
void runWithBoth(CallbackType cb) {
_runCallback<EgressSession>(cb);
_runCallback<IngressSession>(cb);
}
auto& fixtures() const {
return *_streamFixtures;
}
/**
* Tests that the effects of cancellation are properly reported both locally and remotely.
* The provided lambda should return a pair of the two sessions: the first being the session
* that will be cancelled.
*/
void runCancellationTest(
std::function<std::pair<GRPCSession&, GRPCSession&>(IngressSession&, EgressSession&)>
whichSession) {
auto ingressSession = makeSession<IngressSession>();
auto egressSession = makeSession<EgressSession>();
auto cancellationReason = Status(ErrorCodes::ShutdownInProgress, "shutdown error");
ASSERT_TRUE(ingressSession->isConnected());
ASSERT_TRUE(egressSession->isConnected());
auto [sessionToCancel, other] = whichSession(*ingressSession, *egressSession);
sessionToCancel.cancel(cancellationReason);
ASSERT_EQ(sessionToCancel.terminationStatus(), cancellationReason);
ASSERT_FALSE(ingressSession->isConnected());
ASSERT_NOT_OK(ingressSession->terminationStatus());
ASSERT_TRUE(fixtures().rpc->serverCtx->isCancelled());
ASSERT_NOT_OK(egressSession->finish());
ASSERT_TRUE(egressSession->terminationStatus());
ASSERT_FALSE(egressSession->isConnected());
ASSERT_TRUE(ErrorCodes::isCancellationError(*other.terminationStatus()));
}
private:
std::unique_ptr<MockStreamTestFixtures> _makeStreamFixtures() {
// The MockStreamTestFixtures created here doesn't contain any references to the channel or
// server, so it's okay to let stubFixture go out of scope.
MockStubTestFixtures stubFixture;
MetadataView metadata = {{"foo", "bar"}};
return stubFixture.makeStreamTestFixtures(
getServiceContext()->getFastClockSource()->now() + kStreamTimeout, std::move(metadata));
}
template <class SessionType>
void _runCallback(CallbackType& cb) {
// Install a new fixture for the duration of this call.
auto localFixtures = _makeStreamFixtures();
_streamFixtures.swap(localFixtures);
ON_BLOCK_EXIT([&] { _streamFixtures.swap(localFixtures); });
auto session = makeSession<SessionType>();
ON_BLOCK_EXIT([&] { session->end(); });
LOGV2(7401431,
"Running test with gRPC session",
"type"_attr = std::is_same<SessionType, EgressSession>::value ? "egress" : "ingress");
cb(*_streamFixtures->rpc, *session);
}
std::unique_ptr<MockStreamTestFixtures> _streamFixtures;
};
TEST_F(GRPCSessionTest, NoClientId) {
auto session = makeIngressSession(boost::none);
ASSERT_FALSE(session->getRemoteClientId());
session->end();
}
TEST_F(GRPCSessionTest, GetClientId) {
{
auto session = makeSession<IngressSession>();
ASSERT_TRUE(session->getRemoteClientId());
ASSERT_EQ(session->getRemoteClientId()->toString(), kClientId);
}
{
auto session = makeSession<EgressSession>();
ASSERT_EQ(session->getClientId().toString(), kClientId);
}
}
TEST_F(GRPCSessionTest, GetRemote) {
runWithBoth([&](auto&, auto& session) {
auto expectedRemote = dynamic_cast<EgressSession*>(&session)
? MockStubTestFixtures::kBindAddress
: MockStubTestFixtures::kClientAddress;
ASSERT_EQ(session.remote(), HostAndPort(expectedRemote));
});
}
TEST_F(GRPCSessionTest, CancelIngress) {
runCancellationTest([](IngressSession& ingress, EgressSession& egress) {
return std::pair<GRPCSession&, GRPCSession&>(ingress, egress);
});
}
TEST_F(GRPCSessionTest, CancelEgress) {
runCancellationTest([](IngressSession& ingress, EgressSession& egress) {
return std::pair<GRPCSession&, GRPCSession&>(egress, ingress);
});
}
TEST_F(GRPCSessionTest, End) {
runWithBoth([&](auto& rpc, auto& session) {
session.end();
ASSERT_FALSE(session.isConnected());
ASSERT_TRUE(session.terminationStatus());
ASSERT_EQ(session.terminationStatus()->code(), ErrorCodes::CallbackCanceled);
ASSERT_TRUE(rpc.serverCtx->isCancelled());
});
}
TEST_F(GRPCSessionTest, CancelWithReason) {
Status kExpectedReason = Status(ErrorCodes::ShutdownInProgress, "Some error condition");
runWithBoth([&](auto& rpc, auto& session) {
session.cancel(kExpectedReason);
ASSERT_FALSE(session.isConnected());
ASSERT_TRUE(session.terminationStatus());
ASSERT_EQ(session.terminationStatus(), kExpectedReason);
ASSERT_EQ(session.sourceMessage().getStatus(), kExpectedReason.code());
ASSERT_EQ(session.sinkMessage(makeUniqueMessage()), kExpectedReason.code());
ASSERT_TRUE(rpc.serverCtx->isCancelled());
});
}
TEST_F(GRPCSessionTest, TerminationStatusIsNotOverridden) {
Status kExpectedReason = Status(ErrorCodes::ShutdownInProgress, "Some error condition");
runWithBoth([&](auto&, auto& session) {
session.cancel(kExpectedReason);
// Cancelling the session again should have no effect on the reason.
session.cancel(Status(ErrorCodes::CallbackCanceled, "second reason"));
ASSERT_EQ(session.terminationStatus(), kExpectedReason);
// Ending the session again should have no effect either.
session.end();
ASSERT_EQ(session.terminationStatus(), kExpectedReason);
if (auto egressSession = dynamic_cast<EgressSession*>(&session)) {
// EgressSession::finish() should report the proper status.
auto finishStatus = egressSession->finish();
ASSERT_EQ(finishStatus, kExpectedReason);
} else if (auto ingressSession = dynamic_cast<IngressSession*>(&session)) {
// Recording the status should not overwrite the prior cancellation status.
ingressSession->setTerminationStatus(Status::OK());
ASSERT_EQ(session.terminationStatus(), kExpectedReason);
}
});
}
TEST_F(GRPCSessionTest, ReadAndWrite) {
auto ingressSession = makeSession<IngressSession>();
auto egressSession = makeSession<EgressSession>();
ON_BLOCK_EXIT([&] {
ingressSession->end();
egressSession->end();
});
auto sendMessage = [&](Session& sender, Session& receiver) {
auto msg = makeUniqueMessage();
ASSERT_OK(sender.sinkMessage(msg));
auto swReceived = receiver.sourceMessage();
ASSERT_OK(swReceived.getStatus());
ASSERT_EQ_MSG(swReceived.getValue(), msg);
};
sendMessage(*egressSession, *ingressSession);
sendMessage(*ingressSession, *egressSession);
}
enum class Operation { kSink, kSource };
Status runDummyOperationOnSession(Session& session, Operation op) {
if (op == Operation::kSink) {
return session.sinkMessage({});
} else {
return session.sourceMessage().getStatus();
}
}
TEST_F(GRPCSessionTest, ReadAndWriteFromClosedStream) {
for (auto op : {Operation::kSink, Operation::kSource}) {
runWithBoth([&](auto&, auto& session) {
session.end();
ASSERT_EQ(runDummyOperationOnSession(session, op), ErrorCodes::CallbackCanceled);
});
}
}
TEST_F(GRPCSessionTest, ReadAndWriteTimesOut) {
for (auto op : {Operation::kSink, Operation::kSource}) {
runWithBoth([&](auto&, auto& session) {
clockSource().advance(2 * kStreamTimeout);
if (auto egressSession = dynamic_cast<EgressSession*>(&session)) {
// Verify that the right `ErrorCode` is delivered on the client-side.
ASSERT_EQ(runDummyOperationOnSession(session, op), ErrorCodes::ExceededTimeLimit);
ASSERT_TRUE(ErrorCodes::isExceededTimeLimitError(egressSession->finish()));
} else {
ASSERT_EQ(runDummyOperationOnSession(session, op), ErrorCodes::CallbackCanceled);
}
});
}
}
TEST_F(GRPCSessionTest, FinishOK) {
auto session = makeSession<EgressSession>();
fixtures().rpc->sendReturnStatus(::grpc::Status::OK);
ASSERT_OK(session->finish());
}
TEST_F(GRPCSessionTest, FinishError) {
auto session = makeSession<EgressSession>();
::grpc::Status error{::grpc::UNAVAILABLE, "connection error"};
fixtures().rpc->sendReturnStatus(error);
ASSERT_EQ(session->finish(), util::convertStatus(error));
}
TEST_F(GRPCSessionTest, FinishCancelled) {
auto ingressSession = makeSession<IngressSession>();
auto egressSession = makeSession<EgressSession>();
auto cancellationReason = Status(ErrorCodes::ShutdownInProgress, "some reason");
ingressSession->cancel(cancellationReason);
auto finishStatus = egressSession->finish();
ASSERT_TRUE(ErrorCodes::isCancellationError(finishStatus));
ASSERT_EQ(ingressSession->terminationStatus(), cancellationReason);
}
} // namespace
} // namespace mongo::transport::grpc

View File

@ -1,134 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <boost/optional.hpp>
#include "mongo/transport/grpc/server.h"
#include "mongo/transport/transport_layer.h"
namespace mongo::transport::grpc {
/**
* Wraps the gRPC Server and Client implementations. This abstraction layer aims to hide
* gRPC-specific details from `SessionWorkflow`, `ServiceEntryPoint`, and the remainder of the
* command execution path.
*
* The egress portion of this TransportLayer always communicates via the mongodb.CommandService,
* but other arbitrary gRPC services can be used in the ingress portion. Such services can be
* registered with this transport layer via registerService() before setup() is called.
*
* On shutdown, it cancels all outstanding RPCs (both ingress and egress) and blocks until they have
* completed. If egress mode is enabled, this entails waiting for all sessions to be destructed. If
* ingress mode is enabled, this entails waiting for all RPC handlers to return.
*/
class GRPCTransportLayer : public TransportLayer {
protected:
GRPCTransportLayer() = default;
public:
struct Options {
explicit Options(const ServerGlobalParams& params);
Options() : Options(ServerGlobalParams()) {}
bool enableEgress = false;
std::vector<std::string> bindIpList;
/**
* If set to 0, the transport layer will bind to an arbitrary unused port.
* This port can be accessed via getListeningAddresses() after the transport layer has been
* started.
*/
int bindPort;
bool useUnixDomainSockets;
int unixDomainSocketPermissions;
int maxServerThreads;
boost::optional<BSONObj> clientMetadata;
};
virtual ~GRPCTransportLayer() {}
/**
* Add the service to the list that will be served once this transport layer has been started.
* If no services have been registered at the time when setup() is invoked, no server will be
* created. All services must be registered before setup() is invoked.
*/
virtual Status registerService(std::unique_ptr<Service> svc) = 0;
virtual StatusWith<std::shared_ptr<Session>> connectWithAuthToken(
HostAndPort peer,
Milliseconds timeout,
boost::optional<std::string> authToken = boost::none) = 0;
/**
* The server's current gRPC integration doesn't support async networking, so this is
* left unimplemented.
*/
Future<std::shared_ptr<Session>> asyncConnect(
HostAndPort peer,
ConnectSSLMode sslMode,
const ReactorHandle& reactor,
Milliseconds timeout,
std::shared_ptr<ConnectionMetrics> connectionMetrics,
std::shared_ptr<const SSLConnectionContext> transientSSLContext) override {
MONGO_UNIMPLEMENTED;
}
StringData getNameForLogging() const override {
return "gRPC"_sd;
}
/**
* Not applicable to gRPC networking.
*/
ReactorHandle getReactor(WhichReactor) override {
MONGO_UNIMPLEMENTED;
}
/**
* The addresses the gRPC server is listening to, if any.
*
* This must only be invoked after the transport layer has been started.
*/
virtual const std::vector<HostAndPort>& getListeningAddresses() const = 0;
#ifdef MONGO_CONFIG_SSL
/**
* The gRPC integration doesn't support the use of transient SSL contexts, so this always
* returns an error.
*/
StatusWith<std::shared_ptr<const transport::SSLConnectionContext>> createTransientSSLContext(
const TransientSSLParams& transientSSLParams) override {
return Status(ErrorCodes::InvalidSSLConfiguration,
"Transient SSL contexts are not supported when using gRPC.");
}
#endif
};
} // namespace mongo::transport::grpc

View File

@ -1,278 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/transport/grpc/grpc_transport_layer_impl.h"
#include "mongo/db/server_options.h"
#include "mongo/stdx/mutex.h"
#include "mongo/transport/grpc/client.h"
#include "mongo/transport/grpc/grpc_session_manager.h"
#include "mongo/transport/grpc/service.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/net/socket_utils.h"
#include "mongo/util/net/ssl_options.h"
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kNetwork
namespace mongo::transport::grpc {
namespace {
const Seconds kSessionManagerShutdownTimeout{10};
inline std::string makeGRPCUnixSockPath(int port) {
return makeUnixSockPath(port, "grpc");
}
} // namespace
GRPCTransportLayerImpl::Options::Options(const ServerGlobalParams& params) {
bindIpList = params.bind_ips;
bindPort = params.grpcPort;
useUnixDomainSockets = !params.noUnixSocket;
unixDomainSocketPermissions = params.unixSocketPermissions;
maxServerThreads = params.grpcServerMaxThreads;
}
GRPCTransportLayerImpl::GRPCTransportLayerImpl(ServiceContext* svcCtx,
Options options,
std::unique_ptr<SessionManager> sm)
: _svcCtx{svcCtx}, _options{std::move(options)}, _sessionManager(std::move(sm)) {}
std::unique_ptr<GRPCTransportLayerImpl> GRPCTransportLayerImpl::createWithConfig(
ServiceContext* svcCtx,
Options options,
std::vector<std::shared_ptr<ClientTransportObserver>> observers) {
auto clientCache = std::make_shared<ClientCache>();
auto tl = std::make_unique<GRPCTransportLayerImpl>(
svcCtx,
std::move(options),
std::make_unique<GRPCSessionManager>(svcCtx, clientCache, std::move(observers)));
uassertStatusOK(tl->registerService(std::make_unique<CommandService>(
tl.get(),
[tlPtr = tl.get()](auto session) {
invariant(session->getTransportLayer() == tlPtr);
tlPtr->getSessionManager()->startSession(std::move(session));
},
std::make_shared<grpc::WireVersionProvider>(),
std::move(clientCache))));
return tl;
}
Status GRPCTransportLayerImpl::registerService(std::unique_ptr<Service> svc) {
try {
stdx::lock_guard lk(_mutex);
invariant(
!_server,
"Cannot register gRPC services after GRPCTransportLayer::setup() has been invoked");
iassert(TransportLayer::ShutdownStatus.code(),
"Cannot register gRPC services after the GRPCTransportLayer has been shut down",
!_isShutdown);
_services.push_back(std::move(svc));
return Status::OK();
} catch (const DBException& e) {
return e.toStatus();
}
}
Status GRPCTransportLayerImpl::setup() {
try {
stdx::lock_guard lk(_mutex);
iassert(TransportLayer::ShutdownStatus.code(),
"Cannot set up GRPCTransportLayer after it has been shut down",
!_isShutdown);
if (!_services.empty()) {
std::vector<std::unique_ptr<Service>> services;
services.swap(_services);
Server::Options serverOptions;
if (_options.bindIpList.empty()) {
_options.bindIpList.push_back("127.0.0.1");
}
std::vector<HostAndPort> addresses;
for (auto& ip : _options.bindIpList) {
addresses.push_back(HostAndPort(ip, _options.bindPort));
}
if (_options.useUnixDomainSockets) {
addresses.push_back(HostAndPort(makeGRPCUnixSockPath(_options.bindPort)));
}
serverOptions.addresses = std::move(addresses);
serverOptions.maxThreads = _options.maxServerThreads;
uassert(ErrorCodes::InvalidOptions,
"Unable to start GRPC transport for ingress without tlsCertificateKeyFile",
!sslGlobalParams.sslPEMKeyFile.empty());
serverOptions.tlsCertificateKeyFile = sslGlobalParams.sslPEMKeyFile;
if (!sslGlobalParams.sslCAFile.empty()) {
serverOptions.tlsCAFile = sslGlobalParams.sslCAFile;
}
serverOptions.tlsAllowInvalidCertificates = sslGlobalParams.sslAllowInvalidCertificates;
serverOptions.tlsAllowConnectionsWithoutCertificates =
sslGlobalParams.sslWeakCertificateValidation;
uassert(ErrorCodes::InvalidOptions,
"Unable to start GRPCTransportLayerImpl for ingress without SessionManager",
_sessionManager);
_server = std::make_unique<Server>(std::move(services), serverOptions);
}
if (_options.enableEgress) {
GRPCClient::Options clientOptions{};
if (!sslGlobalParams.sslCAFile.empty()) {
clientOptions.tlsCAFile = sslGlobalParams.sslCAFile;
}
if (!sslGlobalParams.sslPEMKeyFile.empty()) {
clientOptions.tlsCertificateKeyFile = sslGlobalParams.sslPEMKeyFile;
}
clientOptions.tlsAllowInvalidHostnames = sslGlobalParams.sslAllowInvalidHostnames;
clientOptions.tlsAllowInvalidCertificates = sslGlobalParams.sslAllowInvalidCertificates;
iassert(ErrorCodes::InvalidOptions,
"gRPC egress networking enabled but no client metadata document was provided",
_options.clientMetadata.has_value());
_client = std::make_shared<GRPCClient>(
this, *_options.clientMetadata, std::move(clientOptions));
}
return Status::OK();
} catch (const DBException& e) {
return e.toStatus();
}
}
Status GRPCTransportLayerImpl::start() {
try {
stdx::lock_guard lk(_mutex);
iassert(TransportLayer::ShutdownStatus.code(),
"Cannot start GRPCTransportLayer after it has been shut down",
!_isShutdown);
// We can't distinguish between the default list of disabled protocols and one specified by
// via options, so we just log that the list is being ignored when using gRPC.
if (!sslGlobalParams.sslDisabledProtocols.empty()) {
LOGV2_DEBUG(8000811,
3,
"Ignoring tlsDisabledProtocols for gRPC-based connections",
"tlsDisabledProtocols"_attr = sslGlobalParams.sslDisabledProtocols);
}
uassert(ErrorCodes::InvalidSSLConfiguration,
"Specifying a CRL file is not supported when gRPC mode is enabled",
sslGlobalParams.sslCRLFile.empty());
uassert(ErrorCodes::InvalidSSLConfiguration,
"Certificate passwords are not supported when gRPC mode is enabled",
sslGlobalParams.sslPEMKeyPassword.empty());
uassert(ErrorCodes::InvalidSSLConfiguration,
"tlsFIPSMode is not supported when gRPC mode is enabled",
!sslGlobalParams.sslFIPSMode);
if (_server) {
invariant(_sessionManager);
_server->start();
if (_options.useUnixDomainSockets) {
setUnixDomainSocketPermissions(makeGRPCUnixSockPath(_options.bindPort),
_options.unixDomainSocketPermissions);
}
}
if (_client) {
_client->start(_svcCtx);
}
return Status::OK();
} catch (const DBException& ex) {
return ex.toStatus();
}
}
StatusWith<std::shared_ptr<Session>> GRPCTransportLayerImpl::connectWithAuthToken(
HostAndPort peer, Milliseconds timeout, boost::optional<std::string> authToken) {
try {
invariant(_client);
return _client->connect(std::move(peer), timeout, {std::move(authToken)});
} catch (const DBException& e) {
return e.toStatus();
}
}
StatusWith<std::shared_ptr<Session>> GRPCTransportLayerImpl::connect(
HostAndPort peer,
ConnectSSLMode sslMode,
Milliseconds timeout,
boost::optional<TransientSSLParams> transientSSLParams) {
try {
iassert(ErrorCodes::InvalidSSLConfiguration,
"SSL must be enabled when using gRPC",
sslMode == ConnectSSLMode::kEnableSSL ||
(sslMode == transport::ConnectSSLMode::kGlobalSSLMode &&
sslGlobalParams.sslMode.load() != SSLParams::SSLModes::SSLMode_disabled));
iassert(ErrorCodes::InvalidSSLConfiguration,
"Transient SSL parameters are not supported when using gRPC",
!transientSSLParams);
return connectWithAuthToken(std::move(peer), timeout);
} catch (const DBException& e) {
return e.toStatus();
}
}
void GRPCTransportLayerImpl::shutdown() {
stdx::lock_guard lk(_mutex);
if (std::exchange(_isShutdown, true)) {
return;
}
if (_server) {
_server->shutdown();
}
if (_client) {
_client->shutdown();
}
if (_sessionManager) {
_sessionManager->shutdown(kSessionManagerShutdownTimeout);
}
}
void GRPCTransportLayerImpl::stopAcceptingSessions() {
if (_server) {
_server->stopAcceptingRequests();
}
}
#ifdef MONGO_CONFIG_SSL
Status GRPCTransportLayerImpl::rotateCertificates(std::shared_ptr<SSLManagerInterface> manager,
bool asyncOCSPStaple) {
return _server->rotateCertificates();
}
#endif
const std::vector<HostAndPort>& GRPCTransportLayerImpl::getListeningAddresses() const {
invariant(_server);
return _server->getListeningAddresses();
}
} // namespace mongo::transport::grpc

View File

@ -1,114 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <boost/optional.hpp>
#include <memory>
#include "mongo/transport/client_transport_observer.h"
#include "mongo/transport/grpc/grpc_transport_layer.h"
#include "mongo/transport/session_manager.h"
#include "mongo/util/duration.h"
namespace mongo::transport::grpc {
class Client;
class Server;
class GRPCTransportLayerImpl : public GRPCTransportLayer {
public:
// Note that passing `nullptr` for {sessionManager} will disallow ingress usage.
GRPCTransportLayerImpl(ServiceContext* svcCtx,
Options options,
std::unique_ptr<SessionManager> sessionManager);
/**
* Create a GRPCTransportLayerImpl instance suitable for ingress (and optionally egress).
* The instantiated TL will have CommandService pre-attached to route requests via
* sessionManager->startSession().
*
* Note that this TransportLayer will throw during `setup()`
* if no tlsCertificateKeyFile is available.
*/
static std::unique_ptr<GRPCTransportLayerImpl> createWithConfig(
ServiceContext*,
Options options,
std::vector<std::shared_ptr<ClientTransportObserver>> observers);
Status registerService(std::unique_ptr<Service> svc) override;
Status setup() override;
Status start() override;
void shutdown() override;
void stopAcceptingSessions() override;
StatusWith<std::shared_ptr<Session>> connectWithAuthToken(
HostAndPort peer,
Milliseconds timeout,
boost::optional<std::string> authToken = boost::none) override;
StatusWith<std::shared_ptr<Session>> connect(
HostAndPort peer,
ConnectSSLMode sslMode,
Milliseconds timeout,
boost::optional<TransientSSLParams> transientSSLParams = boost::none) override;
#ifdef MONGO_CONFIG_SSL
Status rotateCertificates(std::shared_ptr<SSLManagerInterface> manager,
bool asyncOCSPStaple) override;
#endif
const std::vector<HostAndPort>& getListeningAddresses() const override;
SessionManager* getSessionManager() const override {
return _sessionManager.get();
}
std::shared_ptr<SessionManager> getSharedSessionManager() const override {
return _sessionManager;
}
private:
mutable stdx::mutex _mutex;
bool _isShutdown = false;
std::shared_ptr<Client> _client;
std::unique_ptr<Server> _server;
ServiceContext* const _svcCtx;
// Invalidated after setup().
std::vector<std::unique_ptr<Service>> _services;
Options _options;
std::shared_ptr<SessionManager> _sessionManager;
};
} // namespace mongo::transport::grpc

View File

@ -1,146 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/transport/grpc/grpc_transport_layer_mock.h"
#include "mongo/base/error_codes.h"
#include "mongo/platform/random.h"
#include "mongo/transport/grpc/test_fixtures.h"
#include "mongo/util/duration.h"
namespace mongo::transport::grpc {
GRPCTransportLayerMock::GRPCTransportLayerMock(ServiceContext* svcCtx,
GRPCTransportLayer::Options options,
MockClient::MockResolver resolver,
const HostAndPort& mockClientAddress)
: _svcCtx{std::move(svcCtx)},
_options{std::move(options)},
_resolver{std::move(resolver)},
_mockClientAddress{std::move(mockClientAddress)} {}
Status GRPCTransportLayerMock::registerService(std::unique_ptr<Service> svc) {
if (_startupState.load() != StartupState::kNotStarted) {
return {ErrorCodes::AlreadyInitialized,
"registerService can only be called before setup()"};
}
return Status::OK();
}
Status GRPCTransportLayerMock::setup() {
auto oldState = StartupState::kNotStarted;
if (!_startupState.compareAndSwap(&oldState, StartupState::kSetup)) {
switch (oldState) {
case StartupState::kShutDown:
return TransportLayer::ShutdownStatus;
case StartupState::kSetup:
case StartupState::kStarted:
return {ErrorCodes::AlreadyInitialized,
"setup() must be called only once and before start()"};
case StartupState::kNotStarted:
MONGO_UNREACHABLE
}
}
if (_options.enableEgress) {
_client = std::make_shared<MockClient>(this,
std::move(_mockClientAddress),
std::move(_resolver),
makeClientMetadataDocument());
}
if (_options.bindIpList.empty()) {
_options.bindIpList.push_back("127.0.0.1");
}
return Status::OK();
}
Status GRPCTransportLayerMock::start() {
auto oldState = StartupState::kSetup;
if (!_startupState.compareAndSwap(&oldState, StartupState::kStarted)) {
switch (oldState) {
case StartupState::kShutDown:
return TransportLayer::ShutdownStatus;
case StartupState::kNotStarted:
return {ErrorCodes::IllegalOperation, "Must call setup() before start()"};
case StartupState::kStarted:
return {ErrorCodes::AlreadyInitialized, "start() can only be invoked once"};
case StartupState::kSetup:
MONGO_UNREACHABLE
}
}
if (_client) {
_client->start(_svcCtx);
}
PseudoRandom _random(12);
for (const auto& ip : _options.bindIpList) {
auto port = _options.bindPort == 0 ? _random.nextInt32() : _options.bindPort;
_listenAddresses.push_back(HostAndPort(ip, port));
}
return Status::OK();
}
void GRPCTransportLayerMock::shutdown() {
if (_startupState.swap(StartupState::kShutDown) == StartupState::kShutDown) {
return;
}
if (_client) {
_client->shutdown();
}
}
StatusWith<std::shared_ptr<Session>> GRPCTransportLayerMock::connectWithAuthToken(
HostAndPort peer, Milliseconds timeout, boost::optional<std::string> authToken) {
if (!_client) {
return Status(
ErrorCodes::IllegalOperation,
"start() must have been called with useEgress = true before attempting to connect");
}
return _client->connect(std::move(peer), std::move(timeout), {std::move(authToken)});
}
StatusWith<std::shared_ptr<Session>> GRPCTransportLayerMock::connect(
HostAndPort peer,
ConnectSSLMode sslMode,
Milliseconds timeout,
boost::optional<TransientSSLParams> transientSSLParams) {
return connectWithAuthToken(std::move(peer), std::move(timeout));
}
const std::vector<HostAndPort>& GRPCTransportLayerMock::getListeningAddresses() const {
auto state = _startupState.load();
invariant(state != StartupState::kNotStarted && state != StartupState::kSetup);
return _listenAddresses;
}
} // namespace mongo::transport::grpc

View File

@ -1,110 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <memory>
#include "mongo/db/service_context.h"
#include "mongo/platform/atomic_word.h"
#include "mongo/transport/grpc/grpc_transport_layer.h"
#include "mongo/transport/grpc/mock_client.h"
namespace mongo::transport::grpc {
class Service;
/**
* Currently only mocks the egress portion of GRPCTransportLayer.
*
* setup() must be called exactly once before start(), which also can only be called exactly once.
* Neither of these methods are thread-safe.
*/
class GRPCTransportLayerMock : public GRPCTransportLayer {
public:
GRPCTransportLayerMock(ServiceContext* svcCtx,
Options options,
MockClient::MockResolver resolver,
const HostAndPort& mockClientAddress);
Status registerService(std::unique_ptr<Service> svc) override;
Status setup() override;
Status start() override;
void shutdown() override;
void stopAcceptingSessions() override {
MONGO_UNIMPLEMENTED;
}
StatusWith<std::shared_ptr<Session>> connectWithAuthToken(
HostAndPort peer,
Milliseconds timeout,
boost::optional<std::string> authToken = boost::none) override;
StatusWith<std::shared_ptr<Session>> connect(
HostAndPort peer,
ConnectSSLMode sslMode,
Milliseconds timeout,
boost::optional<TransientSSLParams> transientSSLParams) override;
#ifdef MONGO_CONFIG_SSL
Status rotateCertificates(std::shared_ptr<SSLManagerInterface> manager, bool asyncOCSPStaple) {
MONGO_UNIMPLEMENTED;
};
#endif
const std::vector<HostAndPort>& getListeningAddresses() const override;
SessionManager* getSessionManager() const override {
return nullptr;
}
std::shared_ptr<SessionManager> getSharedSessionManager() const override {
return {};
}
private:
enum class StartupState { kNotStarted, kSetup, kStarted, kShutDown };
AtomicWord<StartupState> _startupState;
std::vector<HostAndPort> _listenAddresses;
std::shared_ptr<Client> _client;
ServiceContext* const _svcCtx;
Options _options;
// Invalidated after setup().
MockClient::MockResolver _resolver;
const HostAndPort& _mockClientAddress;
};
} // namespace mongo::transport::grpc

View File

@ -1,699 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <fcntl.h>
#include <memory>
#include <sys/stat.h>
#include <vector>
#include <boost/filesystem.hpp>
#include "mongo/db/server_options.h"
#include "mongo/logv2/log.h"
#include "mongo/transport/grpc/client_cache.h"
#include "mongo/transport/grpc/grpc_session.h"
#include "mongo/transport/grpc/grpc_session_manager.h"
#include "mongo/transport/grpc/grpc_transport_layer_impl.h"
#include "mongo/transport/grpc/test_fixtures.h"
#include "mongo/transport/grpc/wire_version_provider.h"
#include "mongo/transport/service_executor.h"
#include "mongo/transport/session_workflow_test_util.h"
#include "mongo/transport/test_fixtures.h"
#include "mongo/transport/transport_layer.h"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/thread_assertion_monitor.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/errno_util.h"
#include "mongo/util/net/hostandport.h"
#include "mongo/util/net/socket_utils.h"
#include "mongo/util/net/ssl_options.h"
#include "mongo/util/periodic_runner_factory.h"
#include "mongo/util/scopeguard.h"
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest
namespace mongo::transport::grpc {
namespace {
class GRPCTransportLayerTest : public ServiceContextWithClockSourceMockTest {
public:
void setUp() override {
ServiceContextWithClockSourceMockTest::setUp();
auto svcCtx = getServiceContext();
// Default SEP behavior is to fail.
// Tests utilizing SEP workflows must provide an implementation
// for serviceEntryPoint.handleRequestCb.
auto sep = std::make_unique<MockServiceEntryPoint>();
sep->handleRequestCb = [](OperationContext*, const Message&) -> Future<DbResponse> {
MONGO_UNIMPLEMENTED;
};
serviceEntryPoint = sep.get();
svcCtx->getService(ClusterRole::ShardServer)->setServiceEntryPoint(std::move(sep));
svcCtx->setPeriodicRunner(newPeriodicRunner());
ServiceExecutor::startupAll(svcCtx);
sslGlobalParams.sslCAFile = CommandServiceTestFixtures::kCAFile;
sslGlobalParams.sslPEMKeyFile = CommandServiceTestFixtures::kServerCertificateKeyFile;
sslGlobalParams.sslMode.store(SSLParams::SSLModes::SSLMode_requireSSL);
}
void tearDown() override {
ServiceContextWithClockSourceMockTest::tearDown();
ServiceExecutor::shutdownAll(getServiceContext(), Seconds{10});
}
virtual std::unique_ptr<PeriodicRunner> newPeriodicRunner() {
return makePeriodicRunner(getServiceContext());
}
std::unique_ptr<GRPCTransportLayer> makeTL(
CommandService::RPCHandler serverCb = makeNoopRPCHandler(),
GRPCTransportLayer::Options options = CommandServiceTestFixtures::makeTLOptions()) {
auto* svcCtx = getServiceContext();
auto clientCache = std::make_shared<ClientCache>();
std::vector<std::shared_ptr<ClientTransportObserver>> observers;
auto sm = std::make_unique<GRPCSessionManager>(svcCtx, clientCache, std::move(observers));
auto tl =
std::make_unique<GRPCTransportLayerImpl>(svcCtx, std::move(options), std::move(sm));
uassertStatusOK(tl->registerService(
std::make_unique<CommandService>(tl.get(),
std::move(serverCb),
std::make_unique<WireVersionProvider>(),
std::move(clientCache))));
return tl;
}
static CommandService::RPCHandler makeNoopRPCHandler() {
return [](auto session) {
session->setTerminationStatus(Status::OK());
};
}
static CommandService::RPCHandler makeActiveRPCHandler() {
return [](auto session) {
session->getTransportLayer()->getSessionManager()->startSession(std::move(session));
};
}
/**
* Creates a GRPCTransportLayer using the provided RPCHandler and options, sets it up, starts
* it, and then passes it to the provided callback, automatically shutting it down after the
* callback completes.
*
* The server handler will be run in a thread spawned from a ThreadAssertionMonitor to ensure
* that test assertions fail the test. As a result, exceptions thrown by the handler will fail
* the test, rather than being handled by CommandService.
*/
void runWithTL(CommandService::RPCHandler serverCb,
std::function<void(GRPCTransportLayer&)> cb,
GRPCTransportLayer::Options options) {
unittest::threadAssertionMonitoredTest([&](auto& monitor) {
auto tl = makeTL(
[&](auto session) {
monitor.spawn([&] { ASSERT_DOES_NOT_THROW(serverCb(session)); }).join();
},
std::move(options));
uassertStatusOK(tl->setup());
uassertStatusOK(tl->start());
ON_BLOCK_EXIT([&] { tl->shutdown(); });
ASSERT_DOES_NOT_THROW(cb(*tl));
});
}
void assertConnectSucceeds(GRPCTransportLayer& tl, const HostAndPort& addr) {
auto session = makeEgressSession(tl, addr);
ASSERT_OK(session->finish());
}
static Message makeMessage(BSONObj body) {
OpMsgBuilder builder;
builder.setBody(body);
return builder.finish();
}
static BSONObj getMessageBody(const Message& message) {
return OpMsg::parse(message).body.getOwned();
}
/**
* Exercises the session manager and service entry point codepath through sending the specified
* message from the client to the server, which responds back to the client with the same
* message.
*/
void runCommandThroughServiceEntryPoint(StringData message) {
constexpr auto kCommandName = "mockCommand"_sd;
constexpr auto kReplyField = "mockReply"_sd;
serviceEntryPoint->handleRequestCb = [&](OperationContext*,
const Message& request) -> Future<DbResponse> {
ASSERT_EQ(OpMsg::parse(request).body.firstElement().fieldName(), kCommandName);
OpMsgBuilder reply;
reply.setBody(BSON(kReplyField << message));
return DbResponse{.response = reply.finish()};
};
auto cb = [&](GRPCTransportLayer& tl) {
auto client = std::make_shared<GRPCClient>(
&tl, makeClientMetadataDocument(), CommandServiceTestFixtures::makeClientOptions());
client->start(getServiceContext());
ON_BLOCK_EXIT([&] { client->shutdown(); });
auto session = client->connect(tl.getListeningAddresses().at(0),
CommandServiceTestFixtures::kDefaultConnectTimeout,
{});
ASSERT_OK(session->sinkMessage(makeMessage(BSON(kCommandName << message))));
auto replyMessage = uassertStatusOK(session->sourceMessage());
auto replyBody = getMessageBody(replyMessage);
ASSERT_EQ(replyBody.firstElement().fieldName(), kReplyField);
ASSERT_OK(session->finish());
};
runWithTL(makeActiveRPCHandler(), cb, CommandServiceTestFixtures::makeTLOptions());
}
MockServiceEntryPoint* serviceEntryPoint;
test::SSLGlobalParamsGuard _sslGlobalParamsGuard;
};
TEST_F(GRPCTransportLayerTest, RunCommand) {
runCommandThroughServiceEntryPoint("x");
}
TEST_F(GRPCTransportLayerTest, RunLargeCommand) {
std::string largeMessage(5 * 1024 * 1024, 'x');
runCommandThroughServiceEntryPoint(largeMessage);
}
/**
* Modifies the `ServiceContext` with `PeriodicRunnerMock`, a custom `PeriodicRunner` that maintains
* a list of all instances of `PeriodicJob` and allows monitoring their internal state. We use this
* modified runner to test proper initialization and teardown of the idle channel pruner.
*/
class IdleChannelPrunerTest : public GRPCTransportLayerTest {
public:
class PeriodicRunnerMock : public PeriodicRunner {
public:
/**
* Owns and monitors a `PeriodicJob` by maintaining its observable state (e.g., `kPause`).
*/
class ControllableJobMock : public ControllableJob {
public:
enum class State { kNotSet, kStart, kPause, kResume, kStop };
explicit ControllableJobMock(PeriodicJob job) : _job(std::move(job)) {}
void start() override {
_setState(State::kStart);
}
void pause() override {
_setState(State::kPause);
}
void resume() override {
_setState(State::kResume);
}
void stop() override {
_setState(State::kStop);
}
Milliseconds getPeriod() const override {
return _job.interval;
}
void setPeriod(Milliseconds ms) override {
_job.interval = ms;
}
bool isStarted() const {
return _state == State::kStart;
}
bool isStopped() const {
return _state == State::kStop;
}
private:
void _setState(State newState) {
LOGV2(7401901,
"Updating state for a `PeriodicJob`",
"jobName"_attr = _job.name,
"oldState"_attr = _state,
"newState"_attr = newState);
_state = newState;
}
State _state = State::kNotSet;
PeriodicJob _job;
};
PeriodicJobAnchor makeJob(PeriodicJob job) override {
auto handle = std::make_shared<ControllableJobMock>(std::move(job));
jobs.push_back(handle);
return PeriodicJobAnchor{std::move(handle)};
}
std::vector<std::shared_ptr<ControllableJobMock>> jobs;
};
void setUp() override {
GRPCTransportLayerTest::setUp();
auto* svcCtx = getServiceContext();
std::vector<std::shared_ptr<ClientTransportObserver>> observers;
auto sm = std::make_unique<GRPCSessionManager>(
svcCtx, std::make_shared<ClientCache>(), std::move(observers));
_tl = std::make_unique<GRPCTransportLayerImpl>(
getServiceContext(), CommandServiceTestFixtures::makeTLOptions(), std::move(sm));
uassertStatusOK(_tl->setup());
}
void tearDown() override {
_tl.reset();
ServiceContextWithClockSourceMockTest::tearDown();
}
GRPCTransportLayer& transportLayer() {
return *_tl;
}
std::unique_ptr<PeriodicRunner> newPeriodicRunner() override {
return std::make_unique<PeriodicRunnerMock>();
}
PeriodicRunnerMock* getPeriodicRunnerMock() {
return static_cast<PeriodicRunnerMock*>(getServiceContext()->getPeriodicRunner());
}
private:
std::unique_ptr<GRPCTransportLayer> _tl;
};
TEST_F(IdleChannelPrunerTest, StartsWithTransportLayer) {
ASSERT_TRUE(getPeriodicRunnerMock()->jobs.empty());
ASSERT_OK(transportLayer().start());
ON_BLOCK_EXIT([&] { transportLayer().shutdown(); });
ASSERT_EQ(getPeriodicRunnerMock()->jobs.size(), 1);
auto& prunerJob = getPeriodicRunnerMock()->jobs[0];
ASSERT_EQ(prunerJob->getPeriod(), Client::kDefaultChannelTimeout);
ASSERT_TRUE(prunerJob->isStarted());
}
TEST_F(IdleChannelPrunerTest, StopsWithTransportLayer) {
ASSERT_OK(transportLayer().start());
transportLayer().shutdown();
ASSERT_EQ(getPeriodicRunnerMock()->jobs.size(), 1);
auto& prunerJob = getPeriodicRunnerMock()->jobs[0];
ASSERT_TRUE(prunerJob->isStopped());
}
TEST_F(GRPCTransportLayerTest, ConnectAndListen) {
unittest::threadAssertionMonitoredTest([&](unittest::ThreadAssertionMonitor& monitor) {
auto options = CommandServiceTestFixtures::makeTLOptions();
options.bindIpList = {"localhost", "127.0.0.1", "::1"};
options.useUnixDomainSockets = true;
runWithTL(
makeNoopRPCHandler(),
[&](auto& tl) {
auto addrs = tl.getListeningAddresses();
std::vector<stdx::thread> threads;
for (size_t i = 0; i < addrs.size() * 5; i++) {
threads.push_back(monitor.spawn([&, i] {
ASSERT_DOES_NOT_THROW(assertConnectSucceeds(tl, addrs[i % addrs.size()]));
}));
}
for (auto& thread : threads) {
thread.join();
}
},
std::move(options));
});
}
TEST_F(GRPCTransportLayerTest, UnixDomainSocketPermissions) {
auto options = CommandServiceTestFixtures::makeTLOptions();
auto permissions = S_IRWXO & S_IRWXG & S_IRWXU;
options.useUnixDomainSockets = true;
options.unixDomainSocketPermissions = permissions;
runWithTL(
makeNoopRPCHandler(),
[&](GRPCTransportLayer& tl) {
auto addrs = tl.getListeningAddresses();
auto socketPath = std::find_if(addrs.begin(), addrs.end(), [](const HostAndPort& hp) {
return isUnixDomainSocket(hp.host());
});
ASSERT_NE(socketPath, addrs.end());
struct stat st;
ASSERT_EQ(::stat(socketPath->host().c_str(), &st), 0) << errorMessage(lastPosixError());
ASSERT_EQ(st.st_mode & permissions, permissions);
},
std::move(options));
}
// Verifies that when provided an empty list of binding addresses, the TL starts a server that
// listens in all the required places, including Unix domain sockets.
TEST_F(GRPCTransportLayerTest, DefaultIPList) {
GRPCTransportLayer::Options noIPListOptions;
noIPListOptions.enableEgress = true;
noIPListOptions.clientMetadata = makeClientMetadataDocument();
noIPListOptions.bindIpList = {};
noIPListOptions.useUnixDomainSockets = true;
noIPListOptions.bindPort = 0;
runWithTL(
makeNoopRPCHandler(),
[&](GRPCTransportLayer& tl) {
for (auto& addr : tl.getListeningAddresses()) {
ASSERT(addr.isLocalHost() || isUnixDomainSocket(addr.host()))
<< "unexpected default address: " << addr;
assertConnectSucceeds(tl, addr);
}
},
std::move(noIPListOptions));
}
TEST_F(GRPCTransportLayerTest, ConnectionError) {
runWithTL(
makeNoopRPCHandler(),
[&](auto& tl) {
auto tryConnect = [&] {
auto status = tl.connect(HostAndPort("localhost", 1235),
ConnectSSLMode::kGlobalSSLMode,
Milliseconds(50));
ASSERT_NOT_OK(status);
ASSERT_TRUE(ErrorCodes::isNetworkError(status.getStatus()));
};
tryConnect();
// Ensure second attempt on already created channel object also gracefully fails.
tryConnect();
},
CommandServiceTestFixtures::makeTLOptions());
}
TEST_F(GRPCTransportLayerTest, GRPCTransportLayerShutdown) {
auto tl = makeTL();
auto client = std::make_shared<GRPCClient>(
tl.get(), makeClientMetadataDocument(), CommandServiceTestFixtures::makeClientOptions());
client->start(getServiceContext());
ON_BLOCK_EXIT([&] { client->shutdown(); });
HostAndPort addr;
{
uassertStatusOK(tl->setup());
uassertStatusOK(tl->start());
ON_BLOCK_EXIT([&] { tl->shutdown(); });
addr = tl->getListeningAddresses().at(0);
auto session =
client->connect(addr, CommandServiceTestFixtures::kDefaultConnectTimeout, {});
ASSERT_OK(session->finish());
session.reset();
}
ASSERT_THROWS_CODE(
client->connect(addr, Milliseconds(50), {}), DBException, ErrorCodes::NetworkTimeout);
ASSERT_NOT_OK(tl->connect(addr, ConnectSSLMode::kGlobalSSLMode, Milliseconds(50)));
}
TEST_F(GRPCTransportLayerTest, Unary) {
runWithTL(
CommandServiceTestFixtures::makeEchoHandler(),
[&](auto& tl) {
auto session = makeEgressSession(tl, tl.getListeningAddresses().at(0));
assertEchoSucceeds(*session);
ASSERT_OK(session->finish());
},
CommandServiceTestFixtures::makeTLOptions());
}
TEST_F(GRPCTransportLayerTest, Exhaust) {
// In this test, the client side stops reading after kMessageCount exhaust replies and then
// cancels the RPC. The server will verify that it was able to successfully transmit at least
// kMessageCount messages before it observed the RPC being cancelled.
constexpr auto kMessageCount = 5;
Notification<void> serverHandlerDone;
auto streamingHandler = [&](std::shared_ptr<IngressSession> session) {
ON_BLOCK_EXIT([&] { serverHandlerDone.set(); });
auto swMsg = session->sourceMessage();
ASSERT_OK(swMsg);
for (auto i = 0;; i++) {
OpMsg response;
response.body = BSON("i" << i);
auto serialized = response.serialize();
OpMsg::setFlag(&serialized, OpMsg::kMoreToCome);
if (auto sinkStatus = session->sinkMessage(serialized); !sinkStatus.isOK()) {
ASSERT_EQ(sinkStatus.code(), ErrorCodes::CallbackCanceled);
ASSERT_GTE(i, kMessageCount);
break;
}
sleepFor(Microseconds(500));
}
ASSERT_FALSE(session->isConnected());
ASSERT_EQ(session->terminationStatus()->code(), ErrorCodes::CallbackCanceled);
};
runWithTL(
streamingHandler,
[&](auto& tl) {
auto session = makeEgressSession(tl, tl.getListeningAddresses().at(0));
ASSERT_OK(session->sinkMessage(makeUniqueMessage()));
for (auto i = 0; i < kMessageCount; i++) {
auto swMsg = session->sourceMessage();
ASSERT_OK(swMsg);
auto responseMsg = OpMsg::parse(swMsg.getValue());
int iReceived = responseMsg.body.getIntField("i");
ASSERT_EQ(iReceived, i);
}
session->end();
// Wait here before exiting to ensure that server handler has a chance to receive the
// cancellation.
serverHandlerDone.get();
},
CommandServiceTestFixtures::makeTLOptions());
}
TEST_F(GRPCTransportLayerTest, Unacknowledged) {
auto serverHandler = [](std::shared_ptr<IngressSession> session) {
while (true) {
try {
auto swMsg = session->sourceMessage();
uassertStatusOK(swMsg);
if (OpMsg::isFlagSet(swMsg.getValue(), OpMsg::kMoreToCome)) {
continue;
}
ASSERT_OK(session->sinkMessage(swMsg.getValue()));
} catch (ExceptionFor<ErrorCodes::StreamTerminated>&) {
session->end();
return;
}
}
};
runWithTL(
serverHandler,
[&](auto& tl) {
auto session = makeEgressSession(tl, tl.getListeningAddresses().at(0));
assertEchoSucceeds(*session);
auto unacknowledgedMsg = makeUniqueMessage();
OpMsg::setFlag(&unacknowledgedMsg, OpMsg::kMoreToCome);
ASSERT_OK(session->sinkMessage(unacknowledgedMsg));
assertEchoSucceeds(*session);
ASSERT_OK(session->finish());
},
CommandServiceTestFixtures::makeTLOptions());
}
class RotateCertificatesGRPCTransportLayerTest : public GRPCTransportLayerTest {
public:
void setUp() override {
GRPCTransportLayerTest::setUp();
_tempDir =
test::copyCertsToTempDir(grpc::CommandServiceTestFixtures::kCAFile,
grpc::CommandServiceTestFixtures::kServerCertificateKeyFile,
"grpc");
sslGlobalParams.sslCAFile = _tempDir->getCAFile().toString();
sslGlobalParams.sslPEMKeyFile = _tempDir->getPEMKeyFile().toString();
}
StringData getFilePathCA() {
return _tempDir->getCAFile();
}
StringData getFilePathPEM() {
return _tempDir->getPEMKeyFile();
}
private:
std::unique_ptr<test::TempCertificatesDir> _tempDir;
};
TEST_F(RotateCertificatesGRPCTransportLayerTest, RotateCertificatesSucceeds) {
// Ceritificates that we wil rotate to.
const std::string kTrustedCAFile = "jstests/libs/trusted-ca.pem";
const std::string kTrustedPEMFile = "jstests/libs/trusted-server.pem";
const std::string kTrustedClientFile = "jstests/libs/trusted-client.pem";
runWithTL(
makeNoopRPCHandler(),
[&](auto& tl) {
auto addr = tl.getListeningAddresses().at(0);
auto initialGoodStub = CommandServiceTestFixtures::makeStubWithCerts(
addr,
CommandServiceTestFixtures::kCAFile,
CommandServiceTestFixtures::kClientCertificateKeyFile);
auto initialBadStub = CommandServiceTestFixtures::makeStubWithCerts(
addr, kTrustedCAFile, kTrustedClientFile);
ASSERT_GRPC_STUB_CONNECTED(initialGoodStub);
ASSERT_GRPC_STUB_NOT_CONNECTED(initialBadStub);
// Overwrite the tmp files to hold new certs.
boost::filesystem::copy_file(kTrustedCAFile,
getFilePathCA().toString(),
boost::filesystem::copy_options::overwrite_existing);
boost::filesystem::copy_file(kTrustedPEMFile,
getFilePathPEM().toString(),
boost::filesystem::copy_options::overwrite_existing);
ASSERT_OK(tl.rotateCertificates(SSLManagerCoordinator::get()->getSSLManager(), false));
ASSERT_GRPC_STUB_CONNECTED(initialGoodStub);
ASSERT_GRPC_STUB_CONNECTED(initialBadStub);
},
CommandServiceTestFixtures::makeTLOptions());
}
TEST_F(RotateCertificatesGRPCTransportLayerTest, RotateCertificatesSucceedsWhenUnchanged) {
runWithTL(
makeNoopRPCHandler(),
[&](auto& tl) {
auto addr = tl.getListeningAddresses().at(0);
// Connect using the existing certs.
auto stub = CommandServiceTestFixtures::makeStubWithCerts(
addr,
CommandServiceTestFixtures::kCAFile,
CommandServiceTestFixtures::kClientCertificateKeyFile);
ASSERT_GRPC_STUB_CONNECTED(stub);
ASSERT_OK(tl.rotateCertificates(SSLManagerCoordinator::get()->getSSLManager(), false));
auto stub2 = CommandServiceTestFixtures::makeStubWithCerts(
addr,
CommandServiceTestFixtures::kCAFile,
CommandServiceTestFixtures::kClientCertificateKeyFile);
ASSERT_GRPC_STUB_CONNECTED(stub2);
},
CommandServiceTestFixtures::makeTLOptions());
}
TEST_F(RotateCertificatesGRPCTransportLayerTest, RotateCertificatesThrowsAndUsesOldCertsWhenEmpty) {
runWithTL(
makeNoopRPCHandler(),
[&](auto& tl) {
auto addr = tl.getListeningAddresses().at(0);
// Connect using the existing certs.
auto stub = CommandServiceTestFixtures::makeStubWithCerts(
addr,
CommandServiceTestFixtures::kCAFile,
CommandServiceTestFixtures::kClientCertificateKeyFile);
ASSERT_GRPC_STUB_CONNECTED(stub);
boost::filesystem::resize_file(getFilePathCA().toString(), 0);
ASSERT_EQ(
tl.rotateCertificates(SSLManagerCoordinator::get()->getSSLManager(), false).code(),
ErrorCodes::InvalidSSLConfiguration);
auto stub2 = CommandServiceTestFixtures::makeStubWithCerts(
addr,
CommandServiceTestFixtures::kCAFile,
CommandServiceTestFixtures::kClientCertificateKeyFile);
ASSERT_GRPC_STUB_CONNECTED(stub2);
},
CommandServiceTestFixtures::makeTLOptions());
}
TEST_F(RotateCertificatesGRPCTransportLayerTest,
RotateCertificatesUsesOldCertsWithNewInvalidCerts) {
const std::string kInvalidPEMFile = "jstests/libs/ecdsa-ca-ocsp.crt";
runWithTL(
makeNoopRPCHandler(),
[&](auto& tl) {
auto addr = tl.getListeningAddresses().at(0);
// Connect using the existing certs.
auto stub = CommandServiceTestFixtures::makeStubWithCerts(
addr,
CommandServiceTestFixtures::kCAFile,
CommandServiceTestFixtures::kClientCertificateKeyFile);
ASSERT_GRPC_STUB_CONNECTED(stub);
// Overwrite the tmp files to hold new, invalid certs.
boost::filesystem::copy_file(kInvalidPEMFile,
getFilePathPEM().toString(),
boost::filesystem::copy_options::overwrite_existing);
ASSERT_EQ(
tl.rotateCertificates(SSLManagerCoordinator::get()->getSSLManager(), false).code(),
ErrorCodes::InvalidSSLConfiguration);
// Make sure we can still connect with the initial certs used before the bad rotation.
auto stub2 = CommandServiceTestFixtures::makeStubWithCerts(
addr,
CommandServiceTestFixtures::kCAFile,
CommandServiceTestFixtures::kClientCertificateKeyFile);
ASSERT_GRPC_STUB_CONNECTED(stub2);
},
CommandServiceTestFixtures::makeTLOptions());
}
} // namespace
} // namespace mongo::transport::grpc

View File

@ -1,49 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <map>
#include <string>
#include "mongo/base/string_data.h"
namespace mongo::transport::grpc {
/**
* A gRPC metadata map that owns its keys and values.
*/
using MetadataContainer = std::multimap<std::string, std::string>;
/**
* A gRPC metadata map that references its keys and values but does not own them.
*/
using MetadataView = std::multimap<StringData, StringData>;
} // namespace mongo::transport::grpc

View File

@ -1,84 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <memory>
#include "mongo/db/service_context.h"
#include "mongo/transport/grpc/channel_pool.h"
#include "mongo/transport/grpc/client.h"
#include "mongo/transport/grpc/mock_stub.h"
#include "mongo/transport/grpc/util.h"
#include "mongo/transport/transport_layer.h"
namespace mongo::transport::grpc {
class MockClient : public Client {
public:
using MockChannelPool = ChannelPool<std::shared_ptr<MockChannel>, MockStub>;
using MockResolver = std::function<MockRPCQueue::Producer(const HostAndPort&)>;
MockClient(TransportLayer* tl,
HostAndPort local,
MockResolver resolver,
const BSONObj& metadata)
: Client(tl, metadata), _local(std::move(local)), _resolver(std::move(resolver)) {}
void start(ServiceContext* svcCtx) override {
_pool = std::make_shared<MockChannelPool>(
svcCtx->getFastClockSource(),
[](auto) { return true; },
[resolver = _resolver, local = _local](const HostAndPort& remote, bool) {
return std::make_shared<MockChannel>(local, remote, resolver(remote));
},
[](std::shared_ptr<MockChannel>& channel, Milliseconds) { return MockStub(channel); });
Client::start(svcCtx);
}
private:
CtxAndStream _streamFactory(const HostAndPort& remote,
Milliseconds timeout,
const ConnectOptions& options) override {
auto stub = _pool->createStub(remote, ConnectSSLMode::kEnableSSL, timeout);
auto ctx = std::make_shared<MockClientContext>();
setMetadataOnClientContext(*ctx, options);
if (options.authToken) {
return {ctx, stub->stub().authenticatedCommandStream(ctx.get())};
} else {
return {ctx, stub->stub().unauthenticatedCommandStream(ctx.get())};
}
}
const HostAndPort _local;
MockResolver _resolver;
std::shared_ptr<MockChannelPool> _pool;
};
} // namespace mongo::transport::grpc

View File

@ -1,86 +0,0 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include "mongo/transport/grpc/client_context.h"
#include "mongo/transport/grpc/mock_client_stream.h"
namespace mongo::transport::grpc {
class MockClientContext : public ClientContext {
public:
MockClientContext() : _deadline{Date_t::max()}, _stream{nullptr} {}
void addMetadataEntry(const std::string& key, const std::string& value) override {
invariant(!_stream);
_metadata.insert({key, value});
};
MetadataView getServerInitialMetadata() const override {
invariant(_stream);
invariant(_stream->_serverInitialMetadata.isReady());
MetadataView mv;
for (auto& kvp : _stream->_serverInitialMetadata.get()) {
mv.insert({kvp.first, kvp.second});
}
return mv;
}
Date_t getDeadline() const override {
return _deadline;
}
void setDeadline(Date_t deadline) override {
invariant(!_stream);
_deadline = deadline;
}
HostAndPort getRemote() const override {
invariant(_stream);
return _stream->_remote;
}
void tryCancel() override {
invariant(_stream);
_stream->_cancel();
}
private:
friend class MockStub;
friend struct MockStreamTestFixtures;
Date_t _deadline;
MetadataContainer _metadata;
MockClientStream* _stream;
};
} // namespace mongo::transport::grpc

Some files were not shown because too many files have changed in this diff Show More