SERVER-114627 remove grpc option (#44601)
GitOrigin-RevId: 9406004781bf1aee9793a542142f8672982b4599
This commit is contained in:
parent
2f1b8fdaa1
commit
ad27dbb8da
@ -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
|
||||
|
||||
1
.bazelrc
1
.bazelrc
@ -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
|
||||
|
||||
12
MODULE.bazel
12
MODULE.bazel
@ -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
50
MODULE.bazel.lock
generated
@ -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=",
|
||||
|
||||
@ -19,9 +19,6 @@ filters:
|
||||
- ".clang-format":
|
||||
approvers:
|
||||
- devprod-build
|
||||
- ".eslint-ignore":
|
||||
approvers:
|
||||
- devprod-correctness
|
||||
- ".eslintrc.yml":
|
||||
approvers:
|
||||
- devprod-correctness
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
# --------------------------------------
|
||||
|
||||
@ -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
|
||||
# =========
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
- featureFlagAlwaysCreateConfigTransactionsPartialIndexOnStepUp
|
||||
- featureFlagUpdateDocumentShardKeyUsingTransactionApi
|
||||
- featureFlagAllMongodsAreSharded
|
||||
- featureFlagGRPC
|
||||
- featureFlagReplicaSetEndpoint
|
||||
- featureFlagCreateCollectionInPreparedTransactions
|
||||
- featureFlagMongodProxyProcolSupport
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.")
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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,
|
||||
* ]
|
||||
*/
|
||||
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
* ]
|
||||
*/
|
||||
|
||||
|
||||
@ -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,
|
||||
* ]
|
||||
*/
|
||||
|
||||
|
||||
@ -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,
|
||||
* ]
|
||||
*/
|
||||
|
||||
|
||||
@ -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,
|
||||
* ]
|
||||
*/
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
@ -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);
|
||||
@ -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()) {
|
||||
|
||||
@ -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,
|
||||
* ]
|
||||
*/
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
* @tags: [
|
||||
* requires_fcv_80,
|
||||
* featureFlagMongodProxyProcolSupport,
|
||||
* grpc_incompatible,
|
||||
* ]
|
||||
*/
|
||||
|
||||
|
||||
@ -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,
|
||||
* ]
|
||||
*/
|
||||
|
||||
|
||||
@ -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,
|
||||
* ]
|
||||
*/
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
/**
|
||||
* @tags: [
|
||||
* requires_fcv_80,
|
||||
* grpc_incompatible,
|
||||
* ]
|
||||
*/
|
||||
|
||||
|
||||
@ -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
232
sbom.json
@ -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": []
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -679,10 +679,6 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
virtual bool isGRPC() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ClientAPIVersionParameters& getApiParameters() const {
|
||||
return _apiParameters;
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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@
|
||||
|
||||
|
||||
@ -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"],
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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": [],
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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});
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
@ -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"],
|
||||
|
||||
@ -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"],
|
||||
}),
|
||||
)
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
Loading…
Reference in New Issue
Block a user