[xDS] refactor BUILD targets for resource types (#37130)

This moves more code out of the monolithic `grpc_xds_client` BUILD target.  We still need more work to split it up completely, but this is a nice step in the right direction -- and it unblocks a subsequent PR that I'm working on for xDS authority rewriting.

Closes #37130

COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/37130 from markdroth:xds_resource_type_build_refactoring a021d9773c
PiperOrigin-RevId: 648742472
pull/37156/head
Mark D. Roth 5 months ago committed by Copybara-Service
parent 7091890ae7
commit 62826f5f6e
  1. 8
      CMakeLists.txt
  2. 8
      Makefile
  3. 17
      Package.swift
  4. 17
      build_autogenerated.yaml
  5. 8
      config.m4
  6. 8
      config.w32
  7. 18
      gRPC-C++.podspec
  8. 26
      gRPC-Core.podspec
  9. 17
      grpc.gemspec
  10. 17
      package.xml
  11. 253
      src/core/BUILD
  12. 4
      src/core/resolver/xds/xds_dependency_manager.cc
  13. 2
      src/core/resolver/xds/xds_resolver.cc
  14. 5
      src/core/server/xds_server_config_fetcher.cc
  15. 1
      src/core/xds/grpc/xds_audit_logger_registry.cc
  16. 125
      src/core/xds/grpc/xds_bootstrap_grpc.cc
  17. 29
      src/core/xds/grpc/xds_bootstrap_grpc.h
  18. 722
      src/core/xds/grpc/xds_cluster.cc
  19. 39
      src/core/xds/grpc/xds_cluster.h
  20. 730
      src/core/xds/grpc/xds_cluster_parser.cc
  21. 57
      src/core/xds/grpc/xds_cluster_parser.h
  22. 420
      src/core/xds/grpc/xds_common_types.cc
  23. 21
      src/core/xds/grpc/xds_common_types.h
  24. 449
      src/core/xds/grpc/xds_common_types_parser.cc
  25. 47
      src/core/xds/grpc/xds_common_types_parser.h
  26. 419
      src/core/xds/grpc/xds_endpoint.cc
  27. 24
      src/core/xds/grpc/xds_endpoint.h
  28. 433
      src/core/xds/grpc/xds_endpoint_parser.cc
  29. 48
      src/core/xds/grpc/xds_endpoint_parser.h
  30. 2
      src/core/xds/grpc/xds_health_status.cc
  31. 2
      src/core/xds/grpc/xds_health_status.h
  32. 3
      src/core/xds/grpc/xds_http_fault_filter.cc
  33. 2
      src/core/xds/grpc/xds_http_fault_filter.h
  34. 67
      src/core/xds/grpc/xds_http_filter.h
  35. 3
      src/core/xds/grpc/xds_http_filter_registry.cc
  36. 98
      src/core/xds/grpc/xds_http_filter_registry.h
  37. 2
      src/core/xds/grpc/xds_http_rbac_filter.h
  38. 3
      src/core/xds/grpc/xds_http_stateful_session_filter.cc
  39. 2
      src/core/xds/grpc/xds_http_stateful_session_filter.h
  40. 1
      src/core/xds/grpc/xds_lb_policy_registry.cc
  41. 998
      src/core/xds/grpc/xds_listener.cc
  42. 34
      src/core/xds/grpc/xds_listener.h
  43. 991
      src/core/xds/grpc/xds_listener_parser.cc
  44. 60
      src/core/xds/grpc/xds_listener_parser.h
  45. 915
      src/core/xds/grpc/xds_route_config.cc
  46. 38
      src/core/xds/grpc/xds_route_config.h
  47. 956
      src/core/xds/grpc/xds_route_config_parser.cc
  48. 80
      src/core/xds/grpc/xds_route_config_parser.h
  49. 2
      src/core/xds/grpc/xds_routing.cc
  50. 2
      src/core/xds/grpc/xds_routing.h
  51. 152
      src/core/xds/grpc/xds_server_grpc.cc
  52. 61
      src/core/xds/grpc/xds_server_grpc.h
  53. 9
      src/core/xds/grpc/xds_transport_grpc.cc
  54. 8
      src/python/grpcio/grpc_core_dependencies.py
  55. 1
      test/core/load_balancing/BUILD
  56. 3
      test/core/xds/BUILD
  57. 5
      test/core/xds/xds_audit_logger_registry_test.cc
  58. 8
      test/core/xds/xds_bootstrap_test.cc
  59. 4
      test/core/xds/xds_client_fuzzer.cc
  60. 1
      test/core/xds/xds_cluster_resource_type_test.cc
  61. 3
      test/core/xds/xds_common_types_test.cc
  62. 1
      test/core/xds/xds_endpoint_resource_type_test.cc
  63. 6
      test/core/xds/xds_http_filters_test.cc
  64. 5
      test/core/xds/xds_lb_policy_registry_test.cc
  65. 1
      test/core/xds/xds_listener_resource_type_test.cc
  66. 1
      test/core/xds/xds_route_config_resource_type_test.cc
  67. 17
      tools/doxygen/Doxyfile.c++.internal
  68. 17
      tools/doxygen/Doxyfile.core.internal

8
CMakeLists.txt generated

@ -2657,18 +2657,24 @@ add_library(grpc
src/core/xds/grpc/xds_certificate_provider.cc
src/core/xds/grpc/xds_client_grpc.cc
src/core/xds/grpc/xds_cluster.cc
src/core/xds/grpc/xds_cluster_parser.cc
src/core/xds/grpc/xds_cluster_specifier_plugin.cc
src/core/xds/grpc/xds_common_types.cc
src/core/xds/grpc/xds_common_types_parser.cc
src/core/xds/grpc/xds_endpoint.cc
src/core/xds/grpc/xds_endpoint_parser.cc
src/core/xds/grpc/xds_health_status.cc
src/core/xds/grpc/xds_http_fault_filter.cc
src/core/xds/grpc/xds_http_filters.cc
src/core/xds/grpc/xds_http_filter_registry.cc
src/core/xds/grpc/xds_http_rbac_filter.cc
src/core/xds/grpc/xds_http_stateful_session_filter.cc
src/core/xds/grpc/xds_lb_policy_registry.cc
src/core/xds/grpc/xds_listener.cc
src/core/xds/grpc/xds_listener_parser.cc
src/core/xds/grpc/xds_route_config.cc
src/core/xds/grpc/xds_route_config_parser.cc
src/core/xds/grpc/xds_routing.cc
src/core/xds/grpc/xds_server_grpc.cc
src/core/xds/grpc/xds_transport_grpc.cc
src/core/xds/xds_client/xds_api.cc
src/core/xds/xds_client/xds_bootstrap.cc

8
Makefile generated

@ -1491,18 +1491,24 @@ LIBGRPC_SRC = \
src/core/xds/grpc/xds_certificate_provider.cc \
src/core/xds/grpc/xds_client_grpc.cc \
src/core/xds/grpc/xds_cluster.cc \
src/core/xds/grpc/xds_cluster_parser.cc \
src/core/xds/grpc/xds_cluster_specifier_plugin.cc \
src/core/xds/grpc/xds_common_types.cc \
src/core/xds/grpc/xds_common_types_parser.cc \
src/core/xds/grpc/xds_endpoint.cc \
src/core/xds/grpc/xds_endpoint_parser.cc \
src/core/xds/grpc/xds_health_status.cc \
src/core/xds/grpc/xds_http_fault_filter.cc \
src/core/xds/grpc/xds_http_filters.cc \
src/core/xds/grpc/xds_http_filter_registry.cc \
src/core/xds/grpc/xds_http_rbac_filter.cc \
src/core/xds/grpc/xds_http_stateful_session_filter.cc \
src/core/xds/grpc/xds_lb_policy_registry.cc \
src/core/xds/grpc/xds_listener.cc \
src/core/xds/grpc/xds_listener_parser.cc \
src/core/xds/grpc/xds_route_config.cc \
src/core/xds/grpc/xds_route_config_parser.cc \
src/core/xds/grpc/xds_routing.cc \
src/core/xds/grpc/xds_server_grpc.cc \
src/core/xds/grpc/xds_transport_grpc.cc \
src/core/xds/xds_client/xds_api.cc \
src/core/xds/xds_client/xds_bootstrap.cc \

17
Package.swift generated

@ -1975,18 +1975,25 @@ let package = Package(
"src/core/xds/grpc/xds_client_grpc.h",
"src/core/xds/grpc/xds_cluster.cc",
"src/core/xds/grpc/xds_cluster.h",
"src/core/xds/grpc/xds_cluster_parser.cc",
"src/core/xds/grpc/xds_cluster_parser.h",
"src/core/xds/grpc/xds_cluster_specifier_plugin.cc",
"src/core/xds/grpc/xds_cluster_specifier_plugin.h",
"src/core/xds/grpc/xds_common_types.cc",
"src/core/xds/grpc/xds_common_types.h",
"src/core/xds/grpc/xds_common_types_parser.cc",
"src/core/xds/grpc/xds_common_types_parser.h",
"src/core/xds/grpc/xds_endpoint.cc",
"src/core/xds/grpc/xds_endpoint.h",
"src/core/xds/grpc/xds_endpoint_parser.cc",
"src/core/xds/grpc/xds_endpoint_parser.h",
"src/core/xds/grpc/xds_health_status.cc",
"src/core/xds/grpc/xds_health_status.h",
"src/core/xds/grpc/xds_http_fault_filter.cc",
"src/core/xds/grpc/xds_http_fault_filter.h",
"src/core/xds/grpc/xds_http_filters.cc",
"src/core/xds/grpc/xds_http_filters.h",
"src/core/xds/grpc/xds_http_filter.h",
"src/core/xds/grpc/xds_http_filter_registry.cc",
"src/core/xds/grpc/xds_http_filter_registry.h",
"src/core/xds/grpc/xds_http_rbac_filter.cc",
"src/core/xds/grpc/xds_http_rbac_filter.h",
"src/core/xds/grpc/xds_http_stateful_session_filter.cc",
@ -1995,10 +2002,16 @@ let package = Package(
"src/core/xds/grpc/xds_lb_policy_registry.h",
"src/core/xds/grpc/xds_listener.cc",
"src/core/xds/grpc/xds_listener.h",
"src/core/xds/grpc/xds_listener_parser.cc",
"src/core/xds/grpc/xds_listener_parser.h",
"src/core/xds/grpc/xds_route_config.cc",
"src/core/xds/grpc/xds_route_config.h",
"src/core/xds/grpc/xds_route_config_parser.cc",
"src/core/xds/grpc/xds_route_config_parser.h",
"src/core/xds/grpc/xds_routing.cc",
"src/core/xds/grpc/xds_routing.h",
"src/core/xds/grpc/xds_server_grpc.cc",
"src/core/xds/grpc/xds_server_grpc.h",
"src/core/xds/grpc/xds_transport_grpc.cc",
"src/core/xds/grpc/xds_transport_grpc.h",
"src/core/xds/xds_client/xds_api.cc",

@ -1228,18 +1228,25 @@ libs:
- src/core/xds/grpc/xds_certificate_provider.h
- src/core/xds/grpc/xds_client_grpc.h
- src/core/xds/grpc/xds_cluster.h
- src/core/xds/grpc/xds_cluster_parser.h
- src/core/xds/grpc/xds_cluster_specifier_plugin.h
- src/core/xds/grpc/xds_common_types.h
- src/core/xds/grpc/xds_common_types_parser.h
- src/core/xds/grpc/xds_endpoint.h
- src/core/xds/grpc/xds_endpoint_parser.h
- src/core/xds/grpc/xds_health_status.h
- src/core/xds/grpc/xds_http_fault_filter.h
- src/core/xds/grpc/xds_http_filters.h
- src/core/xds/grpc/xds_http_filter.h
- src/core/xds/grpc/xds_http_filter_registry.h
- src/core/xds/grpc/xds_http_rbac_filter.h
- src/core/xds/grpc/xds_http_stateful_session_filter.h
- src/core/xds/grpc/xds_lb_policy_registry.h
- src/core/xds/grpc/xds_listener.h
- src/core/xds/grpc/xds_listener_parser.h
- src/core/xds/grpc/xds_route_config.h
- src/core/xds/grpc/xds_route_config_parser.h
- src/core/xds/grpc/xds_routing.h
- src/core/xds/grpc/xds_server_grpc.h
- src/core/xds/grpc/xds_transport_grpc.h
- src/core/xds/xds_client/xds_api.h
- src/core/xds/xds_client/xds_bootstrap.h
@ -2033,18 +2040,24 @@ libs:
- src/core/xds/grpc/xds_certificate_provider.cc
- src/core/xds/grpc/xds_client_grpc.cc
- src/core/xds/grpc/xds_cluster.cc
- src/core/xds/grpc/xds_cluster_parser.cc
- src/core/xds/grpc/xds_cluster_specifier_plugin.cc
- src/core/xds/grpc/xds_common_types.cc
- src/core/xds/grpc/xds_common_types_parser.cc
- src/core/xds/grpc/xds_endpoint.cc
- src/core/xds/grpc/xds_endpoint_parser.cc
- src/core/xds/grpc/xds_health_status.cc
- src/core/xds/grpc/xds_http_fault_filter.cc
- src/core/xds/grpc/xds_http_filters.cc
- src/core/xds/grpc/xds_http_filter_registry.cc
- src/core/xds/grpc/xds_http_rbac_filter.cc
- src/core/xds/grpc/xds_http_stateful_session_filter.cc
- src/core/xds/grpc/xds_lb_policy_registry.cc
- src/core/xds/grpc/xds_listener.cc
- src/core/xds/grpc/xds_listener_parser.cc
- src/core/xds/grpc/xds_route_config.cc
- src/core/xds/grpc/xds_route_config_parser.cc
- src/core/xds/grpc/xds_routing.cc
- src/core/xds/grpc/xds_server_grpc.cc
- src/core/xds/grpc/xds_transport_grpc.cc
- src/core/xds/xds_client/xds_api.cc
- src/core/xds/xds_client/xds_bootstrap.cc

8
config.m4 generated

@ -866,18 +866,24 @@ if test "$PHP_GRPC" != "no"; then
src/core/xds/grpc/xds_certificate_provider.cc \
src/core/xds/grpc/xds_client_grpc.cc \
src/core/xds/grpc/xds_cluster.cc \
src/core/xds/grpc/xds_cluster_parser.cc \
src/core/xds/grpc/xds_cluster_specifier_plugin.cc \
src/core/xds/grpc/xds_common_types.cc \
src/core/xds/grpc/xds_common_types_parser.cc \
src/core/xds/grpc/xds_endpoint.cc \
src/core/xds/grpc/xds_endpoint_parser.cc \
src/core/xds/grpc/xds_health_status.cc \
src/core/xds/grpc/xds_http_fault_filter.cc \
src/core/xds/grpc/xds_http_filters.cc \
src/core/xds/grpc/xds_http_filter_registry.cc \
src/core/xds/grpc/xds_http_rbac_filter.cc \
src/core/xds/grpc/xds_http_stateful_session_filter.cc \
src/core/xds/grpc/xds_lb_policy_registry.cc \
src/core/xds/grpc/xds_listener.cc \
src/core/xds/grpc/xds_listener_parser.cc \
src/core/xds/grpc/xds_route_config.cc \
src/core/xds/grpc/xds_route_config_parser.cc \
src/core/xds/grpc/xds_routing.cc \
src/core/xds/grpc/xds_server_grpc.cc \
src/core/xds/grpc/xds_transport_grpc.cc \
src/core/xds/xds_client/xds_api.cc \
src/core/xds/xds_client/xds_bootstrap.cc \

8
config.w32 generated

@ -831,18 +831,24 @@ if (PHP_GRPC != "no") {
"src\\core\\xds\\grpc\\xds_certificate_provider.cc " +
"src\\core\\xds\\grpc\\xds_client_grpc.cc " +
"src\\core\\xds\\grpc\\xds_cluster.cc " +
"src\\core\\xds\\grpc\\xds_cluster_parser.cc " +
"src\\core\\xds\\grpc\\xds_cluster_specifier_plugin.cc " +
"src\\core\\xds\\grpc\\xds_common_types.cc " +
"src\\core\\xds\\grpc\\xds_common_types_parser.cc " +
"src\\core\\xds\\grpc\\xds_endpoint.cc " +
"src\\core\\xds\\grpc\\xds_endpoint_parser.cc " +
"src\\core\\xds\\grpc\\xds_health_status.cc " +
"src\\core\\xds\\grpc\\xds_http_fault_filter.cc " +
"src\\core\\xds\\grpc\\xds_http_filters.cc " +
"src\\core\\xds\\grpc\\xds_http_filter_registry.cc " +
"src\\core\\xds\\grpc\\xds_http_rbac_filter.cc " +
"src\\core\\xds\\grpc\\xds_http_stateful_session_filter.cc " +
"src\\core\\xds\\grpc\\xds_lb_policy_registry.cc " +
"src\\core\\xds\\grpc\\xds_listener.cc " +
"src\\core\\xds\\grpc\\xds_listener_parser.cc " +
"src\\core\\xds\\grpc\\xds_route_config.cc " +
"src\\core\\xds\\grpc\\xds_route_config_parser.cc " +
"src\\core\\xds\\grpc\\xds_routing.cc " +
"src\\core\\xds\\grpc\\xds_server_grpc.cc " +
"src\\core\\xds\\grpc\\xds_transport_grpc.cc " +
"src\\core\\xds\\xds_client\\xds_api.cc " +
"src\\core\\xds\\xds_client\\xds_bootstrap.cc " +

18
gRPC-C++.podspec generated

@ -1336,19 +1336,26 @@ Pod::Spec.new do |s|
'src/core/xds/grpc/xds_certificate_provider.h',
'src/core/xds/grpc/xds_client_grpc.h',
'src/core/xds/grpc/xds_cluster.h',
'src/core/xds/grpc/xds_cluster_parser.h',
'src/core/xds/grpc/xds_cluster_specifier_plugin.h',
'src/core/xds/grpc/xds_common_types.h',
'src/core/xds/grpc/xds_common_types_parser.h',
'src/core/xds/grpc/xds_enabled_server.h',
'src/core/xds/grpc/xds_endpoint.h',
'src/core/xds/grpc/xds_endpoint_parser.h',
'src/core/xds/grpc/xds_health_status.h',
'src/core/xds/grpc/xds_http_fault_filter.h',
'src/core/xds/grpc/xds_http_filters.h',
'src/core/xds/grpc/xds_http_filter.h',
'src/core/xds/grpc/xds_http_filter_registry.h',
'src/core/xds/grpc/xds_http_rbac_filter.h',
'src/core/xds/grpc/xds_http_stateful_session_filter.h',
'src/core/xds/grpc/xds_lb_policy_registry.h',
'src/core/xds/grpc/xds_listener.h',
'src/core/xds/grpc/xds_listener_parser.h',
'src/core/xds/grpc/xds_route_config.h',
'src/core/xds/grpc/xds_route_config_parser.h',
'src/core/xds/grpc/xds_routing.h',
'src/core/xds/grpc/xds_server_grpc.h',
'src/core/xds/grpc/xds_transport_grpc.h',
'src/core/xds/xds_client/xds_api.h',
'src/core/xds/xds_client/xds_bootstrap.h',
@ -2613,19 +2620,26 @@ Pod::Spec.new do |s|
'src/core/xds/grpc/xds_certificate_provider.h',
'src/core/xds/grpc/xds_client_grpc.h',
'src/core/xds/grpc/xds_cluster.h',
'src/core/xds/grpc/xds_cluster_parser.h',
'src/core/xds/grpc/xds_cluster_specifier_plugin.h',
'src/core/xds/grpc/xds_common_types.h',
'src/core/xds/grpc/xds_common_types_parser.h',
'src/core/xds/grpc/xds_enabled_server.h',
'src/core/xds/grpc/xds_endpoint.h',
'src/core/xds/grpc/xds_endpoint_parser.h',
'src/core/xds/grpc/xds_health_status.h',
'src/core/xds/grpc/xds_http_fault_filter.h',
'src/core/xds/grpc/xds_http_filters.h',
'src/core/xds/grpc/xds_http_filter.h',
'src/core/xds/grpc/xds_http_filter_registry.h',
'src/core/xds/grpc/xds_http_rbac_filter.h',
'src/core/xds/grpc/xds_http_stateful_session_filter.h',
'src/core/xds/grpc/xds_lb_policy_registry.h',
'src/core/xds/grpc/xds_listener.h',
'src/core/xds/grpc/xds_listener_parser.h',
'src/core/xds/grpc/xds_route_config.h',
'src/core/xds/grpc/xds_route_config_parser.h',
'src/core/xds/grpc/xds_routing.h',
'src/core/xds/grpc/xds_server_grpc.h',
'src/core/xds/grpc/xds_transport_grpc.h',
'src/core/xds/xds_client/xds_api.h',
'src/core/xds/xds_client/xds_bootstrap.h',

26
gRPC-Core.podspec generated

@ -2091,18 +2091,25 @@ Pod::Spec.new do |s|
'src/core/xds/grpc/xds_client_grpc.h',
'src/core/xds/grpc/xds_cluster.cc',
'src/core/xds/grpc/xds_cluster.h',
'src/core/xds/grpc/xds_cluster_parser.cc',
'src/core/xds/grpc/xds_cluster_parser.h',
'src/core/xds/grpc/xds_cluster_specifier_plugin.cc',
'src/core/xds/grpc/xds_cluster_specifier_plugin.h',
'src/core/xds/grpc/xds_common_types.cc',
'src/core/xds/grpc/xds_common_types.h',
'src/core/xds/grpc/xds_common_types_parser.cc',
'src/core/xds/grpc/xds_common_types_parser.h',
'src/core/xds/grpc/xds_endpoint.cc',
'src/core/xds/grpc/xds_endpoint.h',
'src/core/xds/grpc/xds_endpoint_parser.cc',
'src/core/xds/grpc/xds_endpoint_parser.h',
'src/core/xds/grpc/xds_health_status.cc',
'src/core/xds/grpc/xds_health_status.h',
'src/core/xds/grpc/xds_http_fault_filter.cc',
'src/core/xds/grpc/xds_http_fault_filter.h',
'src/core/xds/grpc/xds_http_filters.cc',
'src/core/xds/grpc/xds_http_filters.h',
'src/core/xds/grpc/xds_http_filter.h',
'src/core/xds/grpc/xds_http_filter_registry.cc',
'src/core/xds/grpc/xds_http_filter_registry.h',
'src/core/xds/grpc/xds_http_rbac_filter.cc',
'src/core/xds/grpc/xds_http_rbac_filter.h',
'src/core/xds/grpc/xds_http_stateful_session_filter.cc',
@ -2111,10 +2118,16 @@ Pod::Spec.new do |s|
'src/core/xds/grpc/xds_lb_policy_registry.h',
'src/core/xds/grpc/xds_listener.cc',
'src/core/xds/grpc/xds_listener.h',
'src/core/xds/grpc/xds_listener_parser.cc',
'src/core/xds/grpc/xds_listener_parser.h',
'src/core/xds/grpc/xds_route_config.cc',
'src/core/xds/grpc/xds_route_config.h',
'src/core/xds/grpc/xds_route_config_parser.cc',
'src/core/xds/grpc/xds_route_config_parser.h',
'src/core/xds/grpc/xds_routing.cc',
'src/core/xds/grpc/xds_routing.h',
'src/core/xds/grpc/xds_server_grpc.cc',
'src/core/xds/grpc/xds_server_grpc.h',
'src/core/xds/grpc/xds_transport_grpc.cc',
'src/core/xds/grpc/xds_transport_grpc.h',
'src/core/xds/xds_client/xds_api.cc',
@ -3388,18 +3401,25 @@ Pod::Spec.new do |s|
'src/core/xds/grpc/xds_certificate_provider.h',
'src/core/xds/grpc/xds_client_grpc.h',
'src/core/xds/grpc/xds_cluster.h',
'src/core/xds/grpc/xds_cluster_parser.h',
'src/core/xds/grpc/xds_cluster_specifier_plugin.h',
'src/core/xds/grpc/xds_common_types.h',
'src/core/xds/grpc/xds_common_types_parser.h',
'src/core/xds/grpc/xds_endpoint.h',
'src/core/xds/grpc/xds_endpoint_parser.h',
'src/core/xds/grpc/xds_health_status.h',
'src/core/xds/grpc/xds_http_fault_filter.h',
'src/core/xds/grpc/xds_http_filters.h',
'src/core/xds/grpc/xds_http_filter.h',
'src/core/xds/grpc/xds_http_filter_registry.h',
'src/core/xds/grpc/xds_http_rbac_filter.h',
'src/core/xds/grpc/xds_http_stateful_session_filter.h',
'src/core/xds/grpc/xds_lb_policy_registry.h',
'src/core/xds/grpc/xds_listener.h',
'src/core/xds/grpc/xds_listener_parser.h',
'src/core/xds/grpc/xds_route_config.h',
'src/core/xds/grpc/xds_route_config_parser.h',
'src/core/xds/grpc/xds_routing.h',
'src/core/xds/grpc/xds_server_grpc.h',
'src/core/xds/grpc/xds_transport_grpc.h',
'src/core/xds/xds_client/xds_api.h',
'src/core/xds/xds_client/xds_bootstrap.h',

17
grpc.gemspec generated

@ -1977,18 +1977,25 @@ Gem::Specification.new do |s|
s.files += %w( src/core/xds/grpc/xds_client_grpc.h )
s.files += %w( src/core/xds/grpc/xds_cluster.cc )
s.files += %w( src/core/xds/grpc/xds_cluster.h )
s.files += %w( src/core/xds/grpc/xds_cluster_parser.cc )
s.files += %w( src/core/xds/grpc/xds_cluster_parser.h )
s.files += %w( src/core/xds/grpc/xds_cluster_specifier_plugin.cc )
s.files += %w( src/core/xds/grpc/xds_cluster_specifier_plugin.h )
s.files += %w( src/core/xds/grpc/xds_common_types.cc )
s.files += %w( src/core/xds/grpc/xds_common_types.h )
s.files += %w( src/core/xds/grpc/xds_common_types_parser.cc )
s.files += %w( src/core/xds/grpc/xds_common_types_parser.h )
s.files += %w( src/core/xds/grpc/xds_endpoint.cc )
s.files += %w( src/core/xds/grpc/xds_endpoint.h )
s.files += %w( src/core/xds/grpc/xds_endpoint_parser.cc )
s.files += %w( src/core/xds/grpc/xds_endpoint_parser.h )
s.files += %w( src/core/xds/grpc/xds_health_status.cc )
s.files += %w( src/core/xds/grpc/xds_health_status.h )
s.files += %w( src/core/xds/grpc/xds_http_fault_filter.cc )
s.files += %w( src/core/xds/grpc/xds_http_fault_filter.h )
s.files += %w( src/core/xds/grpc/xds_http_filters.cc )
s.files += %w( src/core/xds/grpc/xds_http_filters.h )
s.files += %w( src/core/xds/grpc/xds_http_filter.h )
s.files += %w( src/core/xds/grpc/xds_http_filter_registry.cc )
s.files += %w( src/core/xds/grpc/xds_http_filter_registry.h )
s.files += %w( src/core/xds/grpc/xds_http_rbac_filter.cc )
s.files += %w( src/core/xds/grpc/xds_http_rbac_filter.h )
s.files += %w( src/core/xds/grpc/xds_http_stateful_session_filter.cc )
@ -1997,10 +2004,16 @@ Gem::Specification.new do |s|
s.files += %w( src/core/xds/grpc/xds_lb_policy_registry.h )
s.files += %w( src/core/xds/grpc/xds_listener.cc )
s.files += %w( src/core/xds/grpc/xds_listener.h )
s.files += %w( src/core/xds/grpc/xds_listener_parser.cc )
s.files += %w( src/core/xds/grpc/xds_listener_parser.h )
s.files += %w( src/core/xds/grpc/xds_route_config.cc )
s.files += %w( src/core/xds/grpc/xds_route_config.h )
s.files += %w( src/core/xds/grpc/xds_route_config_parser.cc )
s.files += %w( src/core/xds/grpc/xds_route_config_parser.h )
s.files += %w( src/core/xds/grpc/xds_routing.cc )
s.files += %w( src/core/xds/grpc/xds_routing.h )
s.files += %w( src/core/xds/grpc/xds_server_grpc.cc )
s.files += %w( src/core/xds/grpc/xds_server_grpc.h )
s.files += %w( src/core/xds/grpc/xds_transport_grpc.cc )
s.files += %w( src/core/xds/grpc/xds_transport_grpc.h )
s.files += %w( src/core/xds/xds_client/xds_api.cc )

17
package.xml generated

@ -1959,18 +1959,25 @@
<file baseinstalldir="/" name="src/core/xds/grpc/xds_client_grpc.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_cluster.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_cluster.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_cluster_parser.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_cluster_parser.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_cluster_specifier_plugin.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_cluster_specifier_plugin.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_common_types.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_common_types.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_common_types_parser.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_common_types_parser.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_endpoint.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_endpoint.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_endpoint_parser.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_endpoint_parser.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_health_status.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_health_status.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_http_fault_filter.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_http_fault_filter.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_http_filters.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_http_filters.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_http_filter.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_http_filter_registry.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_http_filter_registry.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_http_rbac_filter.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_http_rbac_filter.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_http_stateful_session_filter.cc" role="src" />
@ -1979,10 +1986,16 @@
<file baseinstalldir="/" name="src/core/xds/grpc/xds_lb_policy_registry.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_listener.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_listener.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_listener_parser.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_listener_parser.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_route_config.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_route_config.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_route_config_parser.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_route_config_parser.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_routing.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_routing.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_server_grpc.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_server_grpc.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_transport_grpc.cc" role="src" />
<file baseinstalldir="/" name="src/core/xds/grpc/xds_transport_grpc.h" role="src" />
<file baseinstalldir="/" name="src/core/xds/xds_client/xds_api.cc" role="src" />

@ -5229,24 +5229,226 @@ grpc_cc_library(
],
)
grpc_cc_library(
name = "xds_common_types",
srcs = [
"xds/grpc/xds_common_types.cc",
],
hdrs = [
"xds/grpc/xds_common_types.h",
],
external_deps = [
"absl/strings",
"absl/strings:str_format",
"absl/types:variant",
],
language = "c++",
tags = ["nofixdeps"],
deps = [
"grpc_matchers",
"json",
"validation_errors",
],
)
grpc_cc_library(
name = "xds_http_filter",
hdrs = [
"xds/grpc/xds_http_filter.h",
],
external_deps = [
"absl/status:statusor",
"absl/strings",
"absl/types:optional",
"@com_google_protobuf//upb:reflection",
],
language = "c++",
tags = ["nofixdeps"],
deps = [
"channel_args",
"channel_fwd",
"interception_chain",
"json",
"json_writer",
"validation_errors",
"xds_common_types",
"//:xds_client",
],
)
grpc_cc_library(
name = "xds_route_config",
srcs = [
"xds/grpc/xds_route_config.cc",
],
hdrs = [
"xds/grpc/xds_route_config.h",
],
external_deps = [
"absl/strings",
"absl/strings:str_format",
"absl/types:optional",
"absl/types:variant",
"re2",
],
language = "c++",
tags = ["nofixdeps"],
deps = [
"grpc_matchers",
"match",
"time",
"xds_http_filter",
"//:grpc_base",
"//:xds_client",
],
)
grpc_cc_library(
name = "xds_listener",
srcs = [
"xds/grpc/xds_listener.cc",
],
hdrs = [
"xds/grpc/xds_listener.h",
],
external_deps = [
"absl/strings",
"absl/strings:str_format",
"absl/types:optional",
"absl/types:variant",
],
language = "c++",
tags = ["nofixdeps"],
deps = [
"match",
"resolved_address",
"time",
"xds_common_types",
"xds_http_filter",
"xds_route_config",
"//:sockaddr_utils",
"//:xds_client",
],
)
grpc_cc_library(
name = "xds_health_status",
srcs = [
"xds/grpc/xds_health_status.cc",
],
hdrs = [
"xds/grpc/xds_health_status.h",
],
external_deps = [
"absl/strings",
"absl/types:optional",
"absl/types:span",
],
language = "c++",
tags = ["nofixdeps"],
deps = [
"envoy_config_core_upb",
"//:endpoint_addresses",
],
)
grpc_cc_library(
name = "xds_server_grpc",
srcs = [
"xds/grpc/xds_server_grpc.cc",
],
hdrs = [
"xds/grpc/xds_server_grpc.h",
],
external_deps = [
"absl/strings",
],
language = "c++",
tags = ["nofixdeps"],
deps = [
"channel_creds_registry",
"json",
"json_args",
"json_object_loader",
"json_reader",
"json_writer",
"validation_errors",
"//:config",
"//:ref_counted_ptr",
"//:xds_client",
],
)
grpc_cc_library(
name = "xds_cluster",
srcs = [
"xds/grpc/xds_cluster.cc",
],
hdrs = [
"xds/grpc/xds_cluster.h",
],
external_deps = [
"absl/strings",
"absl/types:optional",
"absl/types:variant",
],
language = "c++",
tags = ["nofixdeps"],
deps = [
"grpc_outlier_detection_header",
"json",
"json_writer",
"match",
"time",
"xds_common_types",
"xds_health_status",
"xds_server_grpc",
"//:xds_client",
],
)
grpc_cc_library(
name = "xds_endpoint",
srcs = [
"xds/grpc/xds_endpoint.cc",
],
hdrs = [
"xds/grpc/xds_endpoint.h",
],
external_deps = [
"absl/base:core_headers",
"absl/random",
"absl/strings",
],
language = "c++",
tags = ["nofixdeps"],
deps = [
"ref_counted",
"//:endpoint_addresses",
"//:gpr",
"//:ref_counted_ptr",
"//:xds_client",
],
)
# TODO(roth): Split this up into individual targets.
grpc_cc_library(
name = "grpc_xds_client",
srcs = [
"xds/grpc/xds_audit_logger_registry.cc",
"xds/grpc/xds_bootstrap_grpc.cc",
"xds/grpc/xds_client_grpc.cc",
"xds/grpc/xds_cluster.cc",
"xds/grpc/xds_cluster_parser.cc",
"xds/grpc/xds_cluster_specifier_plugin.cc",
"xds/grpc/xds_common_types.cc",
"xds/grpc/xds_endpoint.cc",
"xds/grpc/xds_health_status.cc",
"xds/grpc/xds_common_types_parser.cc",
"xds/grpc/xds_endpoint_parser.cc",
"xds/grpc/xds_http_fault_filter.cc",
"xds/grpc/xds_http_filters.cc",
"xds/grpc/xds_http_filter_registry.cc",
"xds/grpc/xds_http_rbac_filter.cc",
"xds/grpc/xds_http_stateful_session_filter.cc",
"xds/grpc/xds_lb_policy_registry.cc",
"xds/grpc/xds_listener.cc",
"xds/grpc/xds_route_config.cc",
"xds/grpc/xds_listener_parser.cc",
"xds/grpc/xds_route_config_parser.cc",
"xds/grpc/xds_routing.cc",
"xds/grpc/xds_transport_grpc.cc",
],
@ -5254,18 +5456,17 @@ grpc_cc_library(
"xds/grpc/xds_audit_logger_registry.h",
"xds/grpc/xds_bootstrap_grpc.h",
"xds/grpc/xds_client_grpc.h",
"xds/grpc/xds_cluster.h",
"xds/grpc/xds_cluster_parser.h",
"xds/grpc/xds_cluster_specifier_plugin.h",
"xds/grpc/xds_common_types.h",
"xds/grpc/xds_endpoint.h",
"xds/grpc/xds_health_status.h",
"xds/grpc/xds_common_types_parser.h",
"xds/grpc/xds_endpoint_parser.h",
"xds/grpc/xds_http_fault_filter.h",
"xds/grpc/xds_http_filters.h",
"xds/grpc/xds_http_filter_registry.h",
"xds/grpc/xds_http_rbac_filter.h",
"xds/grpc/xds_http_stateful_session_filter.h",
"xds/grpc/xds_lb_policy_registry.h",
"xds/grpc/xds_listener.h",
"xds/grpc/xds_route_config.h",
"xds/grpc/xds_listener_parser.h",
"xds/grpc/xds_route_config_parser.h",
"xds/grpc/xds_routing.h",
"xds/grpc/xds_transport_grpc.h",
],
@ -5400,8 +5601,16 @@ grpc_cc_library(
"validation_errors",
"xds_certificate_provider",
"xds_certificate_provider_store",
"xds_cluster",
"xds_common_types",
"xds_credentials",
"xds_endpoint",
"xds_file_watcher_certificate_provider_factory",
"xds_health_status",
"xds_http_filter",
"xds_listener",
"xds_route_config",
"xds_server_grpc",
"xds_type_upb",
"xds_type_upbdefs",
"//:channel",
@ -5492,7 +5701,11 @@ grpc_cc_library(
"unique_type_name",
"xds_certificate_provider",
"xds_certificate_provider_store",
"xds_common_types",
"xds_credentials",
"xds_http_filter",
"xds_listener",
"xds_route_config",
"//:api_trace",
"//:config",
"//:debug_location",
@ -5574,7 +5787,10 @@ grpc_cc_library(
"pollset_set",
"time",
"unique_type_name",
"xds_cluster",
"xds_common_types",
"xds_dependency_manager",
"xds_health_status",
"//:config",
"//:debug_location",
"//:gpr",
@ -5639,6 +5855,7 @@ grpc_cc_library(
"validation_errors",
"xds_credentials",
"xds_dependency_manager",
"xds_endpoint",
"//:call_tracer",
"//:config",
"//:debug_location",
@ -6350,6 +6567,7 @@ grpc_cc_library(
"subchannel_interface",
"validation_errors",
"xds_dependency_manager",
"xds_health_status",
"//:config",
"//:debug_location",
"//:endpoint_addresses",
@ -6713,6 +6931,10 @@ grpc_cc_library(
"grpc_xds_client",
"match",
"ref_counted",
"xds_cluster",
"xds_endpoint",
"xds_listener",
"xds_route_config",
"//:config",
"//:gpr",
"//:grpc_resolver",
@ -6761,6 +6983,9 @@ grpc_cc_library(
"slice",
"time",
"xds_dependency_manager",
"xds_http_filter",
"xds_listener",
"xds_route_config",
"xxhash_inline",
"//:channel_arg_names",
"//:config",

@ -28,6 +28,10 @@
#include "src/core/lib/gprpp/match.h"
#include "src/core/load_balancing/xds/xds_channel_args.h"
#include "src/core/resolver/fake/fake_resolver.h"
#include "src/core/xds/grpc/xds_cluster_parser.h"
#include "src/core/xds/grpc/xds_endpoint_parser.h"
#include "src/core/xds/grpc/xds_listener_parser.h"
#include "src/core/xds/grpc/xds_route_config_parser.h"
#include "src/core/xds/grpc/xds_routing.h"
namespace grpc_core {

@ -86,7 +86,7 @@
#include "src/core/service_config/service_config_impl.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_client_grpc.h"
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/grpc/xds_http_filter.h"
#include "src/core/xds/grpc/xds_listener.h"
#include "src/core/xds/grpc/xds_route_config.h"
#include "src/core/xds/grpc/xds_routing.h"

@ -84,9 +84,12 @@
#include "src/core/xds/grpc/xds_certificate_provider.h"
#include "src/core/xds/grpc/xds_client_grpc.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/grpc/xds_http_filter.h"
#include "src/core/xds/grpc/xds_http_filter_registry.h"
#include "src/core/xds/grpc/xds_listener.h"
#include "src/core/xds/grpc/xds_listener_parser.h"
#include "src/core/xds/grpc/xds_route_config.h"
#include "src/core/xds/grpc/xds_route_config_parser.h"
#include "src/core/xds/grpc/xds_routing.h"
#include "src/core/xds/xds_client/xds_client.h"

@ -32,6 +32,7 @@
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/lib/security/authorization/audit_logging.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_common_types_parser.h"
namespace grpc_core {

@ -90,131 +90,6 @@ const JsonLoaderInterface* GrpcXdsBootstrap::GrpcNode::JsonLoader(
return loader;
}
//
// GrpcXdsBootstrap::GrpcXdsServer
//
namespace {
constexpr absl::string_view kServerFeatureIgnoreResourceDeletion =
"ignore_resource_deletion";
} // namespace
bool GrpcXdsBootstrap::GrpcXdsServer::IgnoreResourceDeletion() const {
return server_features_.find(std::string(
kServerFeatureIgnoreResourceDeletion)) != server_features_.end();
}
bool GrpcXdsBootstrap::GrpcXdsServer::Equals(const XdsServer& other) const {
const auto& o = static_cast<const GrpcXdsServer&>(other);
return (server_uri_ == o.server_uri_ &&
channel_creds_config_->type() == o.channel_creds_config_->type() &&
channel_creds_config_->Equals(*o.channel_creds_config_) &&
server_features_ == o.server_features_);
}
std::string GrpcXdsBootstrap::GrpcXdsServer::Key() const {
return JsonDump(ToJson());
}
const JsonLoaderInterface* GrpcXdsBootstrap::GrpcXdsServer::JsonLoader(
const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<GrpcXdsServer>()
.Field("server_uri", &GrpcXdsServer::server_uri_)
.Finish();
return loader;
}
namespace {
struct ChannelCreds {
std::string type;
Json::Object config;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<ChannelCreds>()
.Field("type", &ChannelCreds::type)
.OptionalField("config", &ChannelCreds::config)
.Finish();
return loader;
}
};
} // namespace
void GrpcXdsBootstrap::GrpcXdsServer::JsonPostLoad(const Json& json,
const JsonArgs& args,
ValidationErrors* errors) {
// Parse "channel_creds".
auto channel_creds_list = LoadJsonObjectField<std::vector<ChannelCreds>>(
json.object(), args, "channel_creds", errors);
if (channel_creds_list.has_value()) {
ValidationErrors::ScopedField field(errors, ".channel_creds");
for (size_t i = 0; i < channel_creds_list->size(); ++i) {
ValidationErrors::ScopedField field(errors, absl::StrCat("[", i, "]"));
auto& creds = (*channel_creds_list)[i];
// Select the first channel creds type that we support, but
// validate all entries.
if (CoreConfiguration::Get().channel_creds_registry().IsSupported(
creds.type)) {
ValidationErrors::ScopedField field(errors, ".config");
auto config =
CoreConfiguration::Get().channel_creds_registry().ParseConfig(
creds.type, Json::FromObject(creds.config), args, errors);
if (channel_creds_config_ == nullptr) {
channel_creds_config_ = std::move(config);
}
}
}
if (channel_creds_config_ == nullptr) {
errors->AddError("no known creds type found");
}
}
// Parse "server_features".
{
ValidationErrors::ScopedField field(errors, ".server_features");
auto it = json.object().find("server_features");
if (it != json.object().end()) {
if (it->second.type() != Json::Type::kArray) {
errors->AddError("is not an array");
} else {
const Json::Array& array = it->second.array();
for (const Json& feature_json : array) {
if (feature_json.type() == Json::Type::kString &&
(feature_json.string() == kServerFeatureIgnoreResourceDeletion)) {
server_features_.insert(feature_json.string());
}
}
}
}
}
}
Json GrpcXdsBootstrap::GrpcXdsServer::ToJson() const {
Json::Object channel_creds_json{
{"type", Json::FromString(std::string(channel_creds_config_->type()))},
};
if (channel_creds_config_ != nullptr) {
channel_creds_json["config"] = channel_creds_config_->ToJson();
}
Json::Object json{
{"server_uri", Json::FromString(server_uri_)},
{"channel_creds",
Json::FromArray({Json::FromObject(std::move(channel_creds_json))})},
};
if (!server_features_.empty()) {
Json::Array server_features_json;
for (auto& feature : server_features_) {
server_features_json.emplace_back(Json::FromString(feature));
}
json["server_features"] = Json::FromArray(std::move(server_features_json));
}
return Json::FromObject(std::move(json));
}
//
// GrpcXdsBootstrap::GrpcAuthority
//

@ -38,8 +38,9 @@
#include "src/core/xds/grpc/certificate_provider_store.h"
#include "src/core/xds/grpc/xds_audit_logger_registry.h"
#include "src/core/xds/grpc/xds_cluster_specifier_plugin.h"
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/grpc/xds_http_filter_registry.h"
#include "src/core/xds/grpc/xds_lb_policy_registry.h"
#include "src/core/xds/grpc/xds_server_grpc.h"
#include "src/core/xds/xds_client/xds_bootstrap.h"
namespace grpc_core {
@ -76,32 +77,6 @@ class GrpcXdsBootstrap final : public XdsBootstrap {
Json::Object metadata_;
};
class GrpcXdsServer final : public XdsServer {
public:
const std::string& server_uri() const override { return server_uri_; }
bool IgnoreResourceDeletion() const override;
bool Equals(const XdsServer& other) const override;
std::string Key() const override;
RefCountedPtr<ChannelCredsConfig> channel_creds_config() const {
return channel_creds_config_;
}
static const JsonLoaderInterface* JsonLoader(const JsonArgs&);
void JsonPostLoad(const Json& json, const JsonArgs& args,
ValidationErrors* errors);
Json ToJson() const;
private:
std::string server_uri_;
RefCountedPtr<ChannelCredsConfig> channel_creds_config_;
std::set<std::string> server_features_;
};
class GrpcAuthority final : public Authority {
public:
std::vector<const XdsServer*> servers() const override {

@ -16,68 +16,16 @@
#include "src/core/xds/grpc/xds_cluster.h"
#include <stddef.h>
#include <algorithm>
#include <memory>
#include <utility>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/strip.h"
#include "absl/types/variant.h"
#include "envoy/config/cluster/v3/circuit_breaker.upb.h"
#include "envoy/config/cluster/v3/cluster.upb.h"
#include "envoy/config/cluster/v3/cluster.upbdefs.h"
#include "envoy/config/cluster/v3/outlier_detection.upb.h"
#include "envoy/config/core/v3/address.upb.h"
#include "envoy/config/core/v3/base.upb.h"
#include "envoy/config/core/v3/config_source.upb.h"
#include "envoy/config/core/v3/extension.upb.h"
#include "envoy/config/core/v3/health_check.upb.h"
#include "envoy/config/core/v3/protocol.upb.h"
#include "envoy/config/endpoint/v3/endpoint.upb.h"
#include "envoy/config/endpoint/v3/endpoint_components.upb.h"
#include "envoy/extensions/clusters/aggregate/v3/cluster.upb.h"
#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h"
#include "envoy/extensions/upstreams/http/v3/http_protocol_options.upb.h"
#include "google/protobuf/any.upb.h"
#include "google/protobuf/duration.upb.h"
#include "google/protobuf/struct.upb.h"
#include "google/protobuf/wrappers.upb.h"
#include "upb/base/string_view.h"
#include "upb/text/encode.h"
#include <grpc/support/json.h>
#include <grpc/support/port_platform.h>
#include "src/core/lib/config/core_configuration.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/gprpp/host_port.h"
#include "src/core/lib/gprpp/match.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/lib/matchers/matchers.h"
#include "src/core/load_balancing/lb_policy_registry.h"
#include "src/core/util/json/json_writer.h"
#include "src/core/util/upb_utils.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_lb_policy_registry.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
namespace grpc_core {
//
// XdsClusterResource
//
std::string XdsClusterResource::ToString() const {
std::vector<std::string> contents;
Match(
@ -129,674 +77,4 @@ std::string XdsClusterResource::ToString() const {
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}");
}
//
// XdsClusterResourceType
//
namespace {
CommonTlsContext UpstreamTlsContextParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_core_v3_TransportSocket* transport_socket,
ValidationErrors* errors) {
ValidationErrors::ScopedField field(errors, ".typed_config");
const auto* typed_config =
envoy_config_core_v3_TransportSocket_typed_config(transport_socket);
auto extension = ExtractXdsExtension(context, typed_config, errors);
if (!extension.has_value()) return {};
if (extension->type !=
"envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext") {
ValidationErrors::ScopedField field(errors, ".type_url");
errors->AddError("unsupported transport socket type");
return {};
}
absl::string_view* serialized_upstream_tls_context =
absl::get_if<absl::string_view>(&extension->value);
if (serialized_upstream_tls_context == nullptr) {
errors->AddError("can't decode UpstreamTlsContext");
return {};
}
const auto* upstream_tls_context =
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_parse(
serialized_upstream_tls_context->data(),
serialized_upstream_tls_context->size(), context.arena);
if (upstream_tls_context == nullptr) {
errors->AddError("can't decode UpstreamTlsContext");
return {};
}
ValidationErrors::ScopedField field3(errors, ".common_tls_context");
const auto* common_tls_context_proto =
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_common_tls_context(
upstream_tls_context);
CommonTlsContext common_tls_context;
if (common_tls_context_proto != nullptr) {
common_tls_context =
CommonTlsContext::Parse(context, common_tls_context_proto, errors);
}
if (common_tls_context.certificate_validation_context
.ca_certificate_provider_instance.instance_name.empty()) {
errors->AddError("no CA certificate provider instance configured");
}
return common_tls_context;
}
XdsClusterResource::Eds EdsConfigParse(
const envoy_config_cluster_v3_Cluster* cluster, ValidationErrors* errors) {
XdsClusterResource::Eds eds;
ValidationErrors::ScopedField field(errors, ".eds_cluster_config");
const envoy_config_cluster_v3_Cluster_EdsClusterConfig* eds_cluster_config =
envoy_config_cluster_v3_Cluster_eds_cluster_config(cluster);
if (eds_cluster_config == nullptr) {
errors->AddError("field not present");
} else {
// Validate ConfigSource.
{
ValidationErrors::ScopedField field(errors, ".eds_config");
const envoy_config_core_v3_ConfigSource* eds_config =
envoy_config_cluster_v3_Cluster_EdsClusterConfig_eds_config(
eds_cluster_config);
if (eds_config == nullptr) {
errors->AddError("field not present");
} else {
if (!envoy_config_core_v3_ConfigSource_has_ads(eds_config) &&
!envoy_config_core_v3_ConfigSource_has_self(eds_config)) {
errors->AddError("ConfigSource is not ads or self");
}
}
}
// Record EDS service_name (if any).
// This field is required if the CDS resource has an xdstp name.
eds.eds_service_name = UpbStringToStdString(
envoy_config_cluster_v3_Cluster_EdsClusterConfig_service_name(
eds_cluster_config));
if (eds.eds_service_name.empty()) {
absl::string_view cluster_name =
UpbStringToAbsl(envoy_config_cluster_v3_Cluster_name(cluster));
if (absl::StartsWith(cluster_name, "xdstp:")) {
ValidationErrors::ScopedField field(errors, ".service_name");
errors->AddError("must be set if Cluster resource has an xdstp name");
}
}
}
return eds;
}
XdsClusterResource::LogicalDns LogicalDnsParse(
const envoy_config_cluster_v3_Cluster* cluster, ValidationErrors* errors) {
XdsClusterResource::LogicalDns logical_dns;
ValidationErrors::ScopedField field(errors, ".load_assignment");
const auto* load_assignment =
envoy_config_cluster_v3_Cluster_load_assignment(cluster);
if (load_assignment == nullptr) {
errors->AddError("field not present for LOGICAL_DNS cluster");
return logical_dns;
}
ValidationErrors::ScopedField field2(errors, ".endpoints");
size_t num_localities;
const auto* const* localities =
envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints(load_assignment,
&num_localities);
if (num_localities != 1) {
errors->AddError(absl::StrCat(
"must contain exactly one locality for LOGICAL_DNS cluster, found ",
num_localities));
return logical_dns;
}
ValidationErrors::ScopedField field3(errors, "[0].lb_endpoints");
size_t num_endpoints;
const auto* const* endpoints =
envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints(localities[0],
&num_endpoints);
if (num_endpoints != 1) {
errors->AddError(absl::StrCat(
"must contain exactly one endpoint for LOGICAL_DNS cluster, found ",
num_endpoints));
return logical_dns;
}
ValidationErrors::ScopedField field4(errors, "[0].endpoint");
const auto* endpoint =
envoy_config_endpoint_v3_LbEndpoint_endpoint(endpoints[0]);
if (endpoint == nullptr) {
errors->AddError("field not present");
return logical_dns;
}
ValidationErrors::ScopedField field5(errors, ".address");
const auto* address = envoy_config_endpoint_v3_Endpoint_address(endpoint);
if (address == nullptr) {
errors->AddError("field not present");
return logical_dns;
}
ValidationErrors::ScopedField field6(errors, ".socket_address");
const auto* socket_address =
envoy_config_core_v3_Address_socket_address(address);
if (socket_address == nullptr) {
errors->AddError("field not present");
return logical_dns;
}
if (envoy_config_core_v3_SocketAddress_resolver_name(socket_address).size !=
0) {
ValidationErrors::ScopedField field(errors, ".resolver_name");
errors->AddError(
"LOGICAL_DNS clusters must NOT have a custom resolver name set");
}
absl::string_view address_str = UpbStringToAbsl(
envoy_config_core_v3_SocketAddress_address(socket_address));
if (address_str.empty()) {
ValidationErrors::ScopedField field(errors, ".address");
errors->AddError("field not present");
}
if (!envoy_config_core_v3_SocketAddress_has_port_value(socket_address)) {
ValidationErrors::ScopedField field(errors, ".port_value");
errors->AddError("field not present");
}
logical_dns.hostname = JoinHostPort(
address_str,
envoy_config_core_v3_SocketAddress_port_value(socket_address));
return logical_dns;
}
XdsClusterResource::Aggregate AggregateClusterParse(
const XdsResourceType::DecodeContext& context,
absl::string_view serialized_config, ValidationErrors* errors) {
XdsClusterResource::Aggregate aggregate;
const auto* aggregate_cluster_config =
envoy_extensions_clusters_aggregate_v3_ClusterConfig_parse(
serialized_config.data(), serialized_config.size(), context.arena);
if (aggregate_cluster_config == nullptr) {
errors->AddError("can't parse aggregate cluster config");
return aggregate;
}
size_t size;
const upb_StringView* clusters =
envoy_extensions_clusters_aggregate_v3_ClusterConfig_clusters(
aggregate_cluster_config, &size);
if (size == 0) {
ValidationErrors::ScopedField field(errors, ".clusters");
errors->AddError("must be non-empty");
}
for (size_t i = 0; i < size; ++i) {
aggregate.prioritized_cluster_names.emplace_back(
UpbStringToStdString(clusters[i]));
}
return aggregate;
}
void ParseLbPolicyConfig(const XdsResourceType::DecodeContext& context,
const envoy_config_cluster_v3_Cluster* cluster,
XdsClusterResource* cds_update,
ValidationErrors* errors) {
// First, check the new load_balancing_policy field.
const auto* load_balancing_policy =
envoy_config_cluster_v3_Cluster_load_balancing_policy(cluster);
if (load_balancing_policy != nullptr) {
const auto& registry =
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap())
.lb_policy_registry();
ValidationErrors::ScopedField field(errors, ".load_balancing_policy");
const size_t original_error_count = errors->size();
cds_update->lb_policy_config = registry.ConvertXdsLbPolicyConfig(
context, load_balancing_policy, errors);
// If there were no conversion errors, validate that the converted config
// parses with the gRPC LB policy registry.
if (original_error_count == errors->size()) {
auto config = CoreConfiguration::Get()
.lb_policy_registry()
.ParseLoadBalancingConfig(
Json::FromArray(cds_update->lb_policy_config));
if (!config.ok()) errors->AddError(config.status().message());
}
return;
}
// Didn't find load_balancing_policy field, so fall back to the old
// lb_policy enum field.
if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) ==
envoy_config_cluster_v3_Cluster_ROUND_ROBIN) {
cds_update->lb_policy_config = {
Json::FromObject({
{"xds_wrr_locality_experimental",
Json::FromObject({
{"childPolicy", Json::FromArray({
Json::FromObject({
{"round_robin", Json::FromObject({})},
}),
})},
})},
}),
};
} else if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) ==
envoy_config_cluster_v3_Cluster_RING_HASH) {
// Record ring hash lb config
auto* ring_hash_config =
envoy_config_cluster_v3_Cluster_ring_hash_lb_config(cluster);
uint64_t min_ring_size = 1024;
uint64_t max_ring_size = 8388608;
if (ring_hash_config != nullptr) {
ValidationErrors::ScopedField field(errors, ".ring_hash_lb_config");
const google_protobuf_UInt64Value* uint64_value =
envoy_config_cluster_v3_Cluster_RingHashLbConfig_maximum_ring_size(
ring_hash_config);
if (uint64_value != nullptr) {
ValidationErrors::ScopedField field(errors, ".maximum_ring_size");
max_ring_size = google_protobuf_UInt64Value_value(uint64_value);
if (max_ring_size > 8388608 || max_ring_size == 0) {
errors->AddError("must be in the range of 1 to 8388608");
}
}
uint64_value =
envoy_config_cluster_v3_Cluster_RingHashLbConfig_minimum_ring_size(
ring_hash_config);
if (uint64_value != nullptr) {
ValidationErrors::ScopedField field(errors, ".minimum_ring_size");
min_ring_size = google_protobuf_UInt64Value_value(uint64_value);
if (min_ring_size > 8388608 || min_ring_size == 0) {
errors->AddError("must be in the range of 1 to 8388608");
}
if (min_ring_size > max_ring_size) {
errors->AddError("cannot be greater than maximum_ring_size");
}
}
if (envoy_config_cluster_v3_Cluster_RingHashLbConfig_hash_function(
ring_hash_config) !=
envoy_config_cluster_v3_Cluster_RingHashLbConfig_XX_HASH) {
ValidationErrors::ScopedField field(errors, ".hash_function");
errors->AddError("invalid hash function");
}
}
cds_update->lb_policy_config = {
Json::FromObject({
{"ring_hash_experimental",
Json::FromObject({
{"minRingSize", Json::FromNumber(min_ring_size)},
{"maxRingSize", Json::FromNumber(max_ring_size)},
})},
}),
};
} else {
ValidationErrors::ScopedField field(errors, ".lb_policy");
errors->AddError("LB policy is not supported");
}
}
void ParseUpstreamConfig(
const XdsResourceType::DecodeContext& context,
const envoy_config_core_v3_TypedExtensionConfig* upstream_config,
XdsClusterResource* cds_update, ValidationErrors* errors) {
ValidationErrors::ScopedField field(errors, ".typed_config");
const auto* typed_config =
envoy_config_core_v3_TypedExtensionConfig_typed_config(upstream_config);
auto extension = ExtractXdsExtension(context, typed_config, errors);
if (!extension.has_value()) return;
if (extension->type !=
"envoy.extensions.upstreams.http.v3.HttpProtocolOptions") {
ValidationErrors::ScopedField field(errors, ".type_url");
errors->AddError("unsupported upstream config type");
return;
}
absl::string_view* serialized_http_protocol_options =
absl::get_if<absl::string_view>(&extension->value);
if (serialized_http_protocol_options == nullptr) {
errors->AddError("can't decode HttpProtocolOptions");
return;
}
const auto* http_protocol_options =
envoy_extensions_upstreams_http_v3_HttpProtocolOptions_parse(
serialized_http_protocol_options->data(),
serialized_http_protocol_options->size(), context.arena);
if (http_protocol_options == nullptr) {
errors->AddError("can't decode HttpProtocolOptions");
return;
}
ValidationErrors::ScopedField field2(errors, ".common_http_protocol_options");
const auto* common_http_protocol_options =
envoy_extensions_upstreams_http_v3_HttpProtocolOptions_common_http_protocol_options(
http_protocol_options);
if (common_http_protocol_options != nullptr) {
const auto* idle_timeout =
envoy_config_core_v3_HttpProtocolOptions_idle_timeout(
common_http_protocol_options);
if (idle_timeout != nullptr) {
ValidationErrors::ScopedField field(errors, ".idle_timeout");
cds_update->connection_idle_timeout = ParseDuration(idle_timeout, errors);
}
}
}
absl::StatusOr<std::shared_ptr<const XdsClusterResource>> CdsResourceParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_cluster_v3_Cluster* cluster) {
auto cds_update = std::make_shared<XdsClusterResource>();
ValidationErrors errors;
// Check the cluster discovery type.
if (envoy_config_cluster_v3_Cluster_type(cluster) ==
envoy_config_cluster_v3_Cluster_EDS) {
cds_update->type = EdsConfigParse(cluster, &errors);
} else if (envoy_config_cluster_v3_Cluster_type(cluster) ==
envoy_config_cluster_v3_Cluster_LOGICAL_DNS) {
cds_update->type = LogicalDnsParse(cluster, &errors);
} else if (envoy_config_cluster_v3_Cluster_has_cluster_type(cluster)) {
ValidationErrors::ScopedField field(&errors, ".cluster_type");
const auto* custom_cluster_type =
envoy_config_cluster_v3_Cluster_cluster_type(cluster);
CHECK_NE(custom_cluster_type, nullptr);
ValidationErrors::ScopedField field2(&errors, ".typed_config");
const auto* typed_config =
envoy_config_cluster_v3_Cluster_CustomClusterType_typed_config(
custom_cluster_type);
if (typed_config == nullptr) {
errors.AddError("field not present");
} else {
absl::string_view type_url = absl::StripPrefix(
UpbStringToAbsl(google_protobuf_Any_type_url(typed_config)),
"type.googleapis.com/");
if (type_url != "envoy.extensions.clusters.aggregate.v3.ClusterConfig") {
ValidationErrors::ScopedField field(&errors, ".type_url");
errors.AddError(
absl::StrCat("unknown cluster_type extension: ", type_url));
} else {
// Retrieve aggregate clusters.
ValidationErrors::ScopedField field(
&errors,
".value[envoy.extensions.clusters.aggregate.v3.ClusterConfig]");
absl::string_view serialized_config =
UpbStringToAbsl(google_protobuf_Any_value(typed_config));
cds_update->type =
AggregateClusterParse(context, serialized_config, &errors);
}
}
} else {
ValidationErrors::ScopedField field(&errors, ".type");
errors.AddError("unknown discovery type");
}
// Check the LB policy.
ParseLbPolicyConfig(context, cluster, cds_update.get(), &errors);
// transport_socket
auto* transport_socket =
envoy_config_cluster_v3_Cluster_transport_socket(cluster);
if (transport_socket != nullptr) {
ValidationErrors::ScopedField field(&errors, ".transport_socket");
cds_update->common_tls_context =
UpstreamTlsContextParse(context, transport_socket, &errors);
}
// Record LRS server name (if any).
const envoy_config_core_v3_ConfigSource* lrs_server =
envoy_config_cluster_v3_Cluster_lrs_server(cluster);
if (lrs_server != nullptr) {
if (!envoy_config_core_v3_ConfigSource_has_self(lrs_server)) {
ValidationErrors::ScopedField field(&errors, ".lrs_server");
errors.AddError("ConfigSource is not self");
}
cds_update->lrs_load_reporting_server.emplace(
static_cast<const GrpcXdsBootstrap::GrpcXdsServer&>(context.server));
}
// Protocol options.
auto* upstream_config =
envoy_config_cluster_v3_Cluster_upstream_config(cluster);
if (upstream_config != nullptr) {
ValidationErrors::ScopedField field(&errors, ".upstream_config");
ParseUpstreamConfig(context, upstream_config, cds_update.get(), &errors);
}
// The Cluster resource encodes the circuit breaking parameters in a list of
// Thresholds messages, where each message specifies the parameters for a
// particular RoutingPriority. we will look only at the first entry in the
// list for priority DEFAULT and default to 1024 if not found.
if (envoy_config_cluster_v3_Cluster_has_circuit_breakers(cluster)) {
const envoy_config_cluster_v3_CircuitBreakers* circuit_breakers =
envoy_config_cluster_v3_Cluster_circuit_breakers(cluster);
size_t num_thresholds;
const envoy_config_cluster_v3_CircuitBreakers_Thresholds* const*
thresholds = envoy_config_cluster_v3_CircuitBreakers_thresholds(
circuit_breakers, &num_thresholds);
for (size_t i = 0; i < num_thresholds; ++i) {
const auto* threshold = thresholds[i];
if (envoy_config_cluster_v3_CircuitBreakers_Thresholds_priority(
threshold) == envoy_config_core_v3_DEFAULT) {
const google_protobuf_UInt32Value* max_requests =
envoy_config_cluster_v3_CircuitBreakers_Thresholds_max_requests(
threshold);
if (max_requests != nullptr) {
cds_update->max_concurrent_requests =
google_protobuf_UInt32Value_value(max_requests);
}
break;
}
}
}
// Outlier detection config.
if (envoy_config_cluster_v3_Cluster_has_outlier_detection(cluster)) {
ValidationErrors::ScopedField field(&errors, ".outlier_detection");
OutlierDetectionConfig outlier_detection_update;
const envoy_config_cluster_v3_OutlierDetection* outlier_detection =
envoy_config_cluster_v3_Cluster_outlier_detection(cluster);
const google_protobuf_Duration* duration =
envoy_config_cluster_v3_OutlierDetection_interval(outlier_detection);
if (duration != nullptr) {
ValidationErrors::ScopedField field(&errors, ".interval");
outlier_detection_update.interval = ParseDuration(duration, &errors);
}
duration = envoy_config_cluster_v3_OutlierDetection_base_ejection_time(
outlier_detection);
if (duration != nullptr) {
ValidationErrors::ScopedField field(&errors, ".base_ejection_time");
outlier_detection_update.base_ejection_time =
ParseDuration(duration, &errors);
}
duration = envoy_config_cluster_v3_OutlierDetection_max_ejection_time(
outlier_detection);
if (duration != nullptr) {
ValidationErrors::ScopedField field(&errors, ".max_ejection_time");
outlier_detection_update.max_ejection_time =
ParseDuration(duration, &errors);
}
const google_protobuf_UInt32Value* max_ejection_percent =
envoy_config_cluster_v3_OutlierDetection_max_ejection_percent(
outlier_detection);
if (max_ejection_percent != nullptr) {
outlier_detection_update.max_ejection_percent =
google_protobuf_UInt32Value_value(max_ejection_percent);
if (outlier_detection_update.max_ejection_percent > 100) {
ValidationErrors::ScopedField field(&errors, ".max_ejection_percent");
errors.AddError("value must be <= 100");
}
}
const google_protobuf_UInt32Value* enforcing_success_rate =
envoy_config_cluster_v3_OutlierDetection_enforcing_success_rate(
outlier_detection);
if (enforcing_success_rate != nullptr) {
uint32_t enforcement_percentage =
google_protobuf_UInt32Value_value(enforcing_success_rate);
if (enforcement_percentage > 100) {
ValidationErrors::ScopedField field(&errors, ".enforcing_success_rate");
errors.AddError("value must be <= 100");
}
if (enforcement_percentage != 0) {
OutlierDetectionConfig::SuccessRateEjection success_rate_ejection;
success_rate_ejection.enforcement_percentage = enforcement_percentage;
const google_protobuf_UInt32Value* minimum_hosts =
envoy_config_cluster_v3_OutlierDetection_success_rate_minimum_hosts(
outlier_detection);
if (minimum_hosts != nullptr) {
success_rate_ejection.minimum_hosts =
google_protobuf_UInt32Value_value(minimum_hosts);
}
const google_protobuf_UInt32Value* request_volume =
envoy_config_cluster_v3_OutlierDetection_success_rate_request_volume(
outlier_detection);
if (request_volume != nullptr) {
success_rate_ejection.request_volume =
google_protobuf_UInt32Value_value(request_volume);
}
const google_protobuf_UInt32Value* stdev_factor =
envoy_config_cluster_v3_OutlierDetection_success_rate_stdev_factor(
outlier_detection);
if (stdev_factor != nullptr) {
success_rate_ejection.stdev_factor =
google_protobuf_UInt32Value_value(stdev_factor);
}
outlier_detection_update.success_rate_ejection = success_rate_ejection;
}
}
const google_protobuf_UInt32Value* enforcing_failure_percentage =
envoy_config_cluster_v3_OutlierDetection_enforcing_failure_percentage(
outlier_detection);
if (enforcing_failure_percentage != nullptr) {
uint32_t enforcement_percentage =
google_protobuf_UInt32Value_value(enforcing_failure_percentage);
if (enforcement_percentage > 100) {
ValidationErrors::ScopedField field(&errors,
".enforcing_failure_percentage");
errors.AddError("value must be <= 100");
}
if (enforcement_percentage != 0) {
OutlierDetectionConfig::FailurePercentageEjection
failure_percentage_ejection;
failure_percentage_ejection.enforcement_percentage =
enforcement_percentage;
const google_protobuf_UInt32Value* minimum_hosts =
envoy_config_cluster_v3_OutlierDetection_failure_percentage_minimum_hosts(
outlier_detection);
if (minimum_hosts != nullptr) {
failure_percentage_ejection.minimum_hosts =
google_protobuf_UInt32Value_value(minimum_hosts);
}
const google_protobuf_UInt32Value* request_volume =
envoy_config_cluster_v3_OutlierDetection_failure_percentage_request_volume(
outlier_detection);
if (request_volume != nullptr) {
failure_percentage_ejection.request_volume =
google_protobuf_UInt32Value_value(request_volume);
}
const google_protobuf_UInt32Value* threshold =
envoy_config_cluster_v3_OutlierDetection_failure_percentage_threshold(
outlier_detection);
if (threshold != nullptr) {
failure_percentage_ejection.threshold =
google_protobuf_UInt32Value_value(threshold);
if (enforcement_percentage > 100) {
ValidationErrors::ScopedField field(
&errors, ".failure_percentage_threshold");
errors.AddError("value must be <= 100");
}
}
outlier_detection_update.failure_percentage_ejection =
failure_percentage_ejection;
}
}
cds_update->outlier_detection = outlier_detection_update;
}
// Validate override host status.
const auto* common_lb_config =
envoy_config_cluster_v3_Cluster_common_lb_config(cluster);
bool override_host_status_found = false;
if (common_lb_config != nullptr) {
ValidationErrors::ScopedField field(&errors, ".common_lb_config");
const auto* override_host_status =
envoy_config_cluster_v3_Cluster_CommonLbConfig_override_host_status(
common_lb_config);
if (override_host_status != nullptr) {
ValidationErrors::ScopedField field(&errors, ".override_host_status");
size_t size;
const int32_t* statuses = envoy_config_core_v3_HealthStatusSet_statuses(
override_host_status, &size);
for (size_t i = 0; i < size; ++i) {
auto status = XdsHealthStatus::FromUpb(statuses[i]);
if (status.has_value()) {
cds_update->override_host_statuses.Add(*status);
}
}
override_host_status_found = true;
}
}
// If the field is not set, we default to [UNKNOWN, HEALTHY].
if (!override_host_status_found) {
cds_update->override_host_statuses.Add(
XdsHealthStatus(XdsHealthStatus::kUnknown));
cds_update->override_host_statuses.Add(
XdsHealthStatus(XdsHealthStatus::kHealthy));
}
// Record telemetry labels (if any).
const envoy_config_core_v3_Metadata* metadata =
envoy_config_cluster_v3_Cluster_metadata(cluster);
if (metadata != nullptr) {
google_protobuf_Struct* telemetry_labels_struct;
if (envoy_config_core_v3_Metadata_filter_metadata_get(
metadata,
StdStringToUpbString(
absl::string_view("com.google.csm.telemetry_labels")),
&telemetry_labels_struct)) {
size_t iter = kUpb_Map_Begin;
const google_protobuf_Struct_FieldsEntry* fields_entry;
while ((fields_entry = google_protobuf_Struct_fields_next(
telemetry_labels_struct, &iter)) != nullptr) {
// Adds any entry whose value is a string to telemetry_labels.
const google_protobuf_Value* value =
google_protobuf_Struct_FieldsEntry_value(fields_entry);
if (google_protobuf_Value_has_string_value(value)) {
if (UpbStringToAbsl(google_protobuf_Struct_FieldsEntry_key(
fields_entry)) == "service_name") {
cds_update->service_telemetry_label = RefCountedStringValue(
UpbStringToAbsl(google_protobuf_Value_string_value(value)));
} else if (UpbStringToAbsl(google_protobuf_Struct_FieldsEntry_key(
fields_entry)) == "service_namespace") {
cds_update->namespace_telemetry_label = RefCountedStringValue(
UpbStringToAbsl(google_protobuf_Value_string_value(value)));
}
}
}
}
}
// Return result.
if (!errors.ok()) {
return errors.status(absl::StatusCode::kInvalidArgument,
"errors validating Cluster resource");
}
return cds_update;
}
void MaybeLogCluster(const XdsResourceType::DecodeContext& context,
const envoy_config_cluster_v3_Cluster* cluster) {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) {
const upb_MessageDef* msg_type =
envoy_config_cluster_v3_Cluster_getmsgdef(context.symtab);
char buf[10240];
upb_TextEncode(reinterpret_cast<const upb_Message*>(cluster), msg_type,
nullptr, 0, buf, sizeof(buf));
VLOG(2) << "[xds_client " << context.client << "] Cluster: " << buf;
}
}
} // namespace
XdsResourceType::DecodeResult XdsClusterResourceType::Decode(
const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const {
DecodeResult result;
// Parse serialized proto.
auto* resource = envoy_config_cluster_v3_Cluster_parse(
serialized_resource.data(), serialized_resource.size(), context.arena);
if (resource == nullptr) {
result.resource =
absl::InvalidArgumentError("Can't parse Cluster resource.");
return result;
}
MaybeLogCluster(context, resource);
// Validate resource.
result.name =
UpbStringToStdString(envoy_config_cluster_v3_Cluster_name(resource));
auto cds_resource = CdsResourceParse(context, resource);
if (!cds_resource.ok()) {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(ERROR) << "[xds_client " << context.client << "] invalid Cluster "
<< *result.name << ": " << cds_resource.status();
}
result.resource = cds_resource.status();
} else {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(INFO) << "[xds_client " << context.client << "] parsed Cluster "
<< *result.name << ": " << (*cds_resource)->ToString();
}
result.resource = std::move(*cds_resource);
}
return result;
}
} // namespace grpc_core

@ -17,31 +17,17 @@
#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_H
#define GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_H
#include <stdint.h>
#include <set>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "envoy/config/cluster/v3/cluster.upbdefs.h"
#include "envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.h"
#include "envoy/extensions/transport_sockets/tls/v3/tls.upbdefs.h"
#include "envoy/extensions/upstreams/http/v3/http_protocol_options.upbdefs.h"
#include "upb/reflection/def.h"
#include <grpc/support/json.h>
#include <grpc/support/port_platform.h>
#include "src/core/load_balancing/outlier_detection/outlier_detection.h"
#include "src/core/util/json/json.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_health_status.h"
#include "src/core/xds/xds_client/xds_bootstrap.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/core/xds/grpc/xds_server_grpc.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
#include "src/core/xds/xds_client/xds_resource_type_impl.h"
@ -84,7 +70,7 @@ struct XdsClusterResource : public XdsResourceType::ResourceData {
// The LRS server to use for load reporting.
// If not set, load reporting will be disabled.
absl::optional<GrpcXdsBootstrap::GrpcXdsServer> lrs_load_reporting_server;
absl::optional<GrpcXdsServer> lrs_load_reporting_server;
// Tls Context used by clients
CommonTlsContext common_tls_context;
@ -118,27 +104,6 @@ struct XdsClusterResource : public XdsResourceType::ResourceData {
std::string ToString() const;
};
class XdsClusterResourceType
: public XdsResourceTypeImpl<XdsClusterResourceType, XdsClusterResource> {
public:
absl::string_view type_url() const override {
return "envoy.config.cluster.v3.Cluster";
}
DecodeResult Decode(const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const override;
bool AllResourcesRequiredInSotW() const override { return true; }
void InitUpbSymtab(XdsClient*, upb_DefPool* symtab) const override {
envoy_config_cluster_v3_Cluster_getmsgdef(symtab);
envoy_extensions_clusters_aggregate_v3_ClusterConfig_getmsgdef(symtab);
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_getmsgdef(
symtab);
envoy_extensions_upstreams_http_v3_HttpProtocolOptions_getmsgdef(symtab);
}
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_H

@ -0,0 +1,730 @@
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "src/core/xds/grpc/xds_cluster_parser.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/strip.h"
#include "envoy/config/cluster/v3/circuit_breaker.upb.h"
#include "envoy/config/cluster/v3/cluster.upb.h"
#include "envoy/config/cluster/v3/cluster.upbdefs.h"
#include "envoy/config/cluster/v3/outlier_detection.upb.h"
#include "envoy/config/core/v3/address.upb.h"
#include "envoy/config/core/v3/base.upb.h"
#include "envoy/config/core/v3/config_source.upb.h"
#include "envoy/config/core/v3/extension.upb.h"
#include "envoy/config/core/v3/health_check.upb.h"
#include "envoy/config/core/v3/protocol.upb.h"
#include "envoy/config/endpoint/v3/endpoint.upb.h"
#include "envoy/config/endpoint/v3/endpoint_components.upb.h"
#include "envoy/extensions/clusters/aggregate/v3/cluster.upb.h"
#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h"
#include "envoy/extensions/upstreams/http/v3/http_protocol_options.upb.h"
#include "google/protobuf/any.upb.h"
#include "google/protobuf/duration.upb.h"
#include "google/protobuf/struct.upb.h"
#include "google/protobuf/wrappers.upb.h"
#include "upb/base/string_view.h"
#include "upb/text/encode.h"
#include "src/core/lib/config/core_configuration.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/gprpp/host_port.h"
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/load_balancing/lb_policy_registry.h"
#include "src/core/util/upb_utils.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_common_types_parser.h"
#include "src/core/xds/grpc/xds_lb_policy_registry.h"
namespace grpc_core {
namespace {
CommonTlsContext UpstreamTlsContextParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_core_v3_TransportSocket* transport_socket,
ValidationErrors* errors) {
ValidationErrors::ScopedField field(errors, ".typed_config");
const auto* typed_config =
envoy_config_core_v3_TransportSocket_typed_config(transport_socket);
auto extension = ExtractXdsExtension(context, typed_config, errors);
if (!extension.has_value()) return {};
if (extension->type !=
"envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext") {
ValidationErrors::ScopedField field(errors, ".type_url");
errors->AddError("unsupported transport socket type");
return {};
}
absl::string_view* serialized_upstream_tls_context =
absl::get_if<absl::string_view>(&extension->value);
if (serialized_upstream_tls_context == nullptr) {
errors->AddError("can't decode UpstreamTlsContext");
return {};
}
const auto* upstream_tls_context =
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_parse(
serialized_upstream_tls_context->data(),
serialized_upstream_tls_context->size(), context.arena);
if (upstream_tls_context == nullptr) {
errors->AddError("can't decode UpstreamTlsContext");
return {};
}
ValidationErrors::ScopedField field3(errors, ".common_tls_context");
const auto* common_tls_context_proto =
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_common_tls_context(
upstream_tls_context);
CommonTlsContext common_tls_context;
if (common_tls_context_proto != nullptr) {
common_tls_context =
CommonTlsContextParse(context, common_tls_context_proto, errors);
}
if (common_tls_context.certificate_validation_context
.ca_certificate_provider_instance.instance_name.empty()) {
errors->AddError("no CA certificate provider instance configured");
}
return common_tls_context;
}
XdsClusterResource::Eds EdsConfigParse(
const envoy_config_cluster_v3_Cluster* cluster, ValidationErrors* errors) {
XdsClusterResource::Eds eds;
ValidationErrors::ScopedField field(errors, ".eds_cluster_config");
const envoy_config_cluster_v3_Cluster_EdsClusterConfig* eds_cluster_config =
envoy_config_cluster_v3_Cluster_eds_cluster_config(cluster);
if (eds_cluster_config == nullptr) {
errors->AddError("field not present");
} else {
// Validate ConfigSource.
{
ValidationErrors::ScopedField field(errors, ".eds_config");
const envoy_config_core_v3_ConfigSource* eds_config =
envoy_config_cluster_v3_Cluster_EdsClusterConfig_eds_config(
eds_cluster_config);
if (eds_config == nullptr) {
errors->AddError("field not present");
} else {
if (!envoy_config_core_v3_ConfigSource_has_ads(eds_config) &&
!envoy_config_core_v3_ConfigSource_has_self(eds_config)) {
errors->AddError("ConfigSource is not ads or self");
}
}
}
// Record EDS service_name (if any).
// This field is required if the CDS resource has an xdstp name.
eds.eds_service_name = UpbStringToStdString(
envoy_config_cluster_v3_Cluster_EdsClusterConfig_service_name(
eds_cluster_config));
if (eds.eds_service_name.empty()) {
absl::string_view cluster_name =
UpbStringToAbsl(envoy_config_cluster_v3_Cluster_name(cluster));
if (absl::StartsWith(cluster_name, "xdstp:")) {
ValidationErrors::ScopedField field(errors, ".service_name");
errors->AddError("must be set if Cluster resource has an xdstp name");
}
}
}
return eds;
}
XdsClusterResource::LogicalDns LogicalDnsParse(
const envoy_config_cluster_v3_Cluster* cluster, ValidationErrors* errors) {
XdsClusterResource::LogicalDns logical_dns;
ValidationErrors::ScopedField field(errors, ".load_assignment");
const auto* load_assignment =
envoy_config_cluster_v3_Cluster_load_assignment(cluster);
if (load_assignment == nullptr) {
errors->AddError("field not present for LOGICAL_DNS cluster");
return logical_dns;
}
ValidationErrors::ScopedField field2(errors, ".endpoints");
size_t num_localities;
const auto* const* localities =
envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints(load_assignment,
&num_localities);
if (num_localities != 1) {
errors->AddError(absl::StrCat(
"must contain exactly one locality for LOGICAL_DNS cluster, found ",
num_localities));
return logical_dns;
}
ValidationErrors::ScopedField field3(errors, "[0].lb_endpoints");
size_t num_endpoints;
const auto* const* endpoints =
envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints(localities[0],
&num_endpoints);
if (num_endpoints != 1) {
errors->AddError(absl::StrCat(
"must contain exactly one endpoint for LOGICAL_DNS cluster, found ",
num_endpoints));
return logical_dns;
}
ValidationErrors::ScopedField field4(errors, "[0].endpoint");
const auto* endpoint =
envoy_config_endpoint_v3_LbEndpoint_endpoint(endpoints[0]);
if (endpoint == nullptr) {
errors->AddError("field not present");
return logical_dns;
}
ValidationErrors::ScopedField field5(errors, ".address");
const auto* address = envoy_config_endpoint_v3_Endpoint_address(endpoint);
if (address == nullptr) {
errors->AddError("field not present");
return logical_dns;
}
ValidationErrors::ScopedField field6(errors, ".socket_address");
const auto* socket_address =
envoy_config_core_v3_Address_socket_address(address);
if (socket_address == nullptr) {
errors->AddError("field not present");
return logical_dns;
}
if (envoy_config_core_v3_SocketAddress_resolver_name(socket_address).size !=
0) {
ValidationErrors::ScopedField field(errors, ".resolver_name");
errors->AddError(
"LOGICAL_DNS clusters must NOT have a custom resolver name set");
}
absl::string_view address_str = UpbStringToAbsl(
envoy_config_core_v3_SocketAddress_address(socket_address));
if (address_str.empty()) {
ValidationErrors::ScopedField field(errors, ".address");
errors->AddError("field not present");
}
if (!envoy_config_core_v3_SocketAddress_has_port_value(socket_address)) {
ValidationErrors::ScopedField field(errors, ".port_value");
errors->AddError("field not present");
}
logical_dns.hostname = JoinHostPort(
address_str,
envoy_config_core_v3_SocketAddress_port_value(socket_address));
return logical_dns;
}
XdsClusterResource::Aggregate AggregateClusterParse(
const XdsResourceType::DecodeContext& context,
absl::string_view serialized_config, ValidationErrors* errors) {
XdsClusterResource::Aggregate aggregate;
const auto* aggregate_cluster_config =
envoy_extensions_clusters_aggregate_v3_ClusterConfig_parse(
serialized_config.data(), serialized_config.size(), context.arena);
if (aggregate_cluster_config == nullptr) {
errors->AddError("can't parse aggregate cluster config");
return aggregate;
}
size_t size;
const upb_StringView* clusters =
envoy_extensions_clusters_aggregate_v3_ClusterConfig_clusters(
aggregate_cluster_config, &size);
if (size == 0) {
ValidationErrors::ScopedField field(errors, ".clusters");
errors->AddError("must be non-empty");
}
for (size_t i = 0; i < size; ++i) {
aggregate.prioritized_cluster_names.emplace_back(
UpbStringToStdString(clusters[i]));
}
return aggregate;
}
void ParseLbPolicyConfig(const XdsResourceType::DecodeContext& context,
const envoy_config_cluster_v3_Cluster* cluster,
XdsClusterResource* cds_update,
ValidationErrors* errors) {
// First, check the new load_balancing_policy field.
const auto* load_balancing_policy =
envoy_config_cluster_v3_Cluster_load_balancing_policy(cluster);
if (load_balancing_policy != nullptr) {
const auto& registry =
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap())
.lb_policy_registry();
ValidationErrors::ScopedField field(errors, ".load_balancing_policy");
const size_t original_error_count = errors->size();
cds_update->lb_policy_config = registry.ConvertXdsLbPolicyConfig(
context, load_balancing_policy, errors);
// If there were no conversion errors, validate that the converted config
// parses with the gRPC LB policy registry.
if (original_error_count == errors->size()) {
auto config = CoreConfiguration::Get()
.lb_policy_registry()
.ParseLoadBalancingConfig(
Json::FromArray(cds_update->lb_policy_config));
if (!config.ok()) errors->AddError(config.status().message());
}
return;
}
// Didn't find load_balancing_policy field, so fall back to the old
// lb_policy enum field.
if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) ==
envoy_config_cluster_v3_Cluster_ROUND_ROBIN) {
cds_update->lb_policy_config = {
Json::FromObject({
{"xds_wrr_locality_experimental",
Json::FromObject({
{"childPolicy", Json::FromArray({
Json::FromObject({
{"round_robin", Json::FromObject({})},
}),
})},
})},
}),
};
} else if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) ==
envoy_config_cluster_v3_Cluster_RING_HASH) {
// Record ring hash lb config
auto* ring_hash_config =
envoy_config_cluster_v3_Cluster_ring_hash_lb_config(cluster);
uint64_t min_ring_size = 1024;
uint64_t max_ring_size = 8388608;
if (ring_hash_config != nullptr) {
ValidationErrors::ScopedField field(errors, ".ring_hash_lb_config");
const google_protobuf_UInt64Value* uint64_value =
envoy_config_cluster_v3_Cluster_RingHashLbConfig_maximum_ring_size(
ring_hash_config);
if (uint64_value != nullptr) {
ValidationErrors::ScopedField field(errors, ".maximum_ring_size");
max_ring_size = google_protobuf_UInt64Value_value(uint64_value);
if (max_ring_size > 8388608 || max_ring_size == 0) {
errors->AddError("must be in the range of 1 to 8388608");
}
}
uint64_value =
envoy_config_cluster_v3_Cluster_RingHashLbConfig_minimum_ring_size(
ring_hash_config);
if (uint64_value != nullptr) {
ValidationErrors::ScopedField field(errors, ".minimum_ring_size");
min_ring_size = google_protobuf_UInt64Value_value(uint64_value);
if (min_ring_size > 8388608 || min_ring_size == 0) {
errors->AddError("must be in the range of 1 to 8388608");
}
if (min_ring_size > max_ring_size) {
errors->AddError("cannot be greater than maximum_ring_size");
}
}
if (envoy_config_cluster_v3_Cluster_RingHashLbConfig_hash_function(
ring_hash_config) !=
envoy_config_cluster_v3_Cluster_RingHashLbConfig_XX_HASH) {
ValidationErrors::ScopedField field(errors, ".hash_function");
errors->AddError("invalid hash function");
}
}
cds_update->lb_policy_config = {
Json::FromObject({
{"ring_hash_experimental",
Json::FromObject({
{"minRingSize", Json::FromNumber(min_ring_size)},
{"maxRingSize", Json::FromNumber(max_ring_size)},
})},
}),
};
} else {
ValidationErrors::ScopedField field(errors, ".lb_policy");
errors->AddError("LB policy is not supported");
}
}
void ParseUpstreamConfig(
const XdsResourceType::DecodeContext& context,
const envoy_config_core_v3_TypedExtensionConfig* upstream_config,
XdsClusterResource* cds_update, ValidationErrors* errors) {
ValidationErrors::ScopedField field(errors, ".typed_config");
const auto* typed_config =
envoy_config_core_v3_TypedExtensionConfig_typed_config(upstream_config);
auto extension = ExtractXdsExtension(context, typed_config, errors);
if (!extension.has_value()) return;
if (extension->type !=
"envoy.extensions.upstreams.http.v3.HttpProtocolOptions") {
ValidationErrors::ScopedField field(errors, ".type_url");
errors->AddError("unsupported upstream config type");
return;
}
absl::string_view* serialized_http_protocol_options =
absl::get_if<absl::string_view>(&extension->value);
if (serialized_http_protocol_options == nullptr) {
errors->AddError("can't decode HttpProtocolOptions");
return;
}
const auto* http_protocol_options =
envoy_extensions_upstreams_http_v3_HttpProtocolOptions_parse(
serialized_http_protocol_options->data(),
serialized_http_protocol_options->size(), context.arena);
if (http_protocol_options == nullptr) {
errors->AddError("can't decode HttpProtocolOptions");
return;
}
ValidationErrors::ScopedField field2(errors, ".common_http_protocol_options");
const auto* common_http_protocol_options =
envoy_extensions_upstreams_http_v3_HttpProtocolOptions_common_http_protocol_options(
http_protocol_options);
if (common_http_protocol_options != nullptr) {
const auto* idle_timeout =
envoy_config_core_v3_HttpProtocolOptions_idle_timeout(
common_http_protocol_options);
if (idle_timeout != nullptr) {
ValidationErrors::ScopedField field(errors, ".idle_timeout");
cds_update->connection_idle_timeout = ParseDuration(idle_timeout, errors);
}
}
}
absl::StatusOr<std::shared_ptr<const XdsClusterResource>> CdsResourceParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_cluster_v3_Cluster* cluster) {
auto cds_update = std::make_shared<XdsClusterResource>();
ValidationErrors errors;
// Check the cluster discovery type.
if (envoy_config_cluster_v3_Cluster_type(cluster) ==
envoy_config_cluster_v3_Cluster_EDS) {
cds_update->type = EdsConfigParse(cluster, &errors);
} else if (envoy_config_cluster_v3_Cluster_type(cluster) ==
envoy_config_cluster_v3_Cluster_LOGICAL_DNS) {
cds_update->type = LogicalDnsParse(cluster, &errors);
} else if (envoy_config_cluster_v3_Cluster_has_cluster_type(cluster)) {
ValidationErrors::ScopedField field(&errors, ".cluster_type");
const auto* custom_cluster_type =
envoy_config_cluster_v3_Cluster_cluster_type(cluster);
CHECK_NE(custom_cluster_type, nullptr);
ValidationErrors::ScopedField field2(&errors, ".typed_config");
const auto* typed_config =
envoy_config_cluster_v3_Cluster_CustomClusterType_typed_config(
custom_cluster_type);
if (typed_config == nullptr) {
errors.AddError("field not present");
} else {
absl::string_view type_url = absl::StripPrefix(
UpbStringToAbsl(google_protobuf_Any_type_url(typed_config)),
"type.googleapis.com/");
if (type_url != "envoy.extensions.clusters.aggregate.v3.ClusterConfig") {
ValidationErrors::ScopedField field(&errors, ".type_url");
errors.AddError(
absl::StrCat("unknown cluster_type extension: ", type_url));
} else {
// Retrieve aggregate clusters.
ValidationErrors::ScopedField field(
&errors,
".value[envoy.extensions.clusters.aggregate.v3.ClusterConfig]");
absl::string_view serialized_config =
UpbStringToAbsl(google_protobuf_Any_value(typed_config));
cds_update->type =
AggregateClusterParse(context, serialized_config, &errors);
}
}
} else {
ValidationErrors::ScopedField field(&errors, ".type");
errors.AddError("unknown discovery type");
}
// Check the LB policy.
ParseLbPolicyConfig(context, cluster, cds_update.get(), &errors);
// transport_socket
auto* transport_socket =
envoy_config_cluster_v3_Cluster_transport_socket(cluster);
if (transport_socket != nullptr) {
ValidationErrors::ScopedField field(&errors, ".transport_socket");
cds_update->common_tls_context =
UpstreamTlsContextParse(context, transport_socket, &errors);
}
// Record LRS server name (if any).
const envoy_config_core_v3_ConfigSource* lrs_server =
envoy_config_cluster_v3_Cluster_lrs_server(cluster);
if (lrs_server != nullptr) {
if (!envoy_config_core_v3_ConfigSource_has_self(lrs_server)) {
ValidationErrors::ScopedField field(&errors, ".lrs_server");
errors.AddError("ConfigSource is not self");
}
cds_update->lrs_load_reporting_server.emplace(
static_cast<const GrpcXdsServer&>(context.server));
}
// Protocol options.
auto* upstream_config =
envoy_config_cluster_v3_Cluster_upstream_config(cluster);
if (upstream_config != nullptr) {
ValidationErrors::ScopedField field(&errors, ".upstream_config");
ParseUpstreamConfig(context, upstream_config, cds_update.get(), &errors);
}
// The Cluster resource encodes the circuit breaking parameters in a list of
// Thresholds messages, where each message specifies the parameters for a
// particular RoutingPriority. we will look only at the first entry in the
// list for priority DEFAULT and default to 1024 if not found.
if (envoy_config_cluster_v3_Cluster_has_circuit_breakers(cluster)) {
const envoy_config_cluster_v3_CircuitBreakers* circuit_breakers =
envoy_config_cluster_v3_Cluster_circuit_breakers(cluster);
size_t num_thresholds;
const envoy_config_cluster_v3_CircuitBreakers_Thresholds* const*
thresholds = envoy_config_cluster_v3_CircuitBreakers_thresholds(
circuit_breakers, &num_thresholds);
for (size_t i = 0; i < num_thresholds; ++i) {
const auto* threshold = thresholds[i];
if (envoy_config_cluster_v3_CircuitBreakers_Thresholds_priority(
threshold) == envoy_config_core_v3_DEFAULT) {
const google_protobuf_UInt32Value* max_requests =
envoy_config_cluster_v3_CircuitBreakers_Thresholds_max_requests(
threshold);
if (max_requests != nullptr) {
cds_update->max_concurrent_requests =
google_protobuf_UInt32Value_value(max_requests);
}
break;
}
}
}
// Outlier detection config.
if (envoy_config_cluster_v3_Cluster_has_outlier_detection(cluster)) {
ValidationErrors::ScopedField field(&errors, ".outlier_detection");
OutlierDetectionConfig outlier_detection_update;
const envoy_config_cluster_v3_OutlierDetection* outlier_detection =
envoy_config_cluster_v3_Cluster_outlier_detection(cluster);
const google_protobuf_Duration* duration =
envoy_config_cluster_v3_OutlierDetection_interval(outlier_detection);
if (duration != nullptr) {
ValidationErrors::ScopedField field(&errors, ".interval");
outlier_detection_update.interval = ParseDuration(duration, &errors);
}
duration = envoy_config_cluster_v3_OutlierDetection_base_ejection_time(
outlier_detection);
if (duration != nullptr) {
ValidationErrors::ScopedField field(&errors, ".base_ejection_time");
outlier_detection_update.base_ejection_time =
ParseDuration(duration, &errors);
}
duration = envoy_config_cluster_v3_OutlierDetection_max_ejection_time(
outlier_detection);
if (duration != nullptr) {
ValidationErrors::ScopedField field(&errors, ".max_ejection_time");
outlier_detection_update.max_ejection_time =
ParseDuration(duration, &errors);
}
const google_protobuf_UInt32Value* max_ejection_percent =
envoy_config_cluster_v3_OutlierDetection_max_ejection_percent(
outlier_detection);
if (max_ejection_percent != nullptr) {
outlier_detection_update.max_ejection_percent =
google_protobuf_UInt32Value_value(max_ejection_percent);
if (outlier_detection_update.max_ejection_percent > 100) {
ValidationErrors::ScopedField field(&errors, ".max_ejection_percent");
errors.AddError("value must be <= 100");
}
}
const google_protobuf_UInt32Value* enforcing_success_rate =
envoy_config_cluster_v3_OutlierDetection_enforcing_success_rate(
outlier_detection);
if (enforcing_success_rate != nullptr) {
uint32_t enforcement_percentage =
google_protobuf_UInt32Value_value(enforcing_success_rate);
if (enforcement_percentage > 100) {
ValidationErrors::ScopedField field(&errors, ".enforcing_success_rate");
errors.AddError("value must be <= 100");
}
if (enforcement_percentage != 0) {
OutlierDetectionConfig::SuccessRateEjection success_rate_ejection;
success_rate_ejection.enforcement_percentage = enforcement_percentage;
const google_protobuf_UInt32Value* minimum_hosts =
envoy_config_cluster_v3_OutlierDetection_success_rate_minimum_hosts(
outlier_detection);
if (minimum_hosts != nullptr) {
success_rate_ejection.minimum_hosts =
google_protobuf_UInt32Value_value(minimum_hosts);
}
const google_protobuf_UInt32Value* request_volume =
envoy_config_cluster_v3_OutlierDetection_success_rate_request_volume(
outlier_detection);
if (request_volume != nullptr) {
success_rate_ejection.request_volume =
google_protobuf_UInt32Value_value(request_volume);
}
const google_protobuf_UInt32Value* stdev_factor =
envoy_config_cluster_v3_OutlierDetection_success_rate_stdev_factor(
outlier_detection);
if (stdev_factor != nullptr) {
success_rate_ejection.stdev_factor =
google_protobuf_UInt32Value_value(stdev_factor);
}
outlier_detection_update.success_rate_ejection = success_rate_ejection;
}
}
const google_protobuf_UInt32Value* enforcing_failure_percentage =
envoy_config_cluster_v3_OutlierDetection_enforcing_failure_percentage(
outlier_detection);
if (enforcing_failure_percentage != nullptr) {
uint32_t enforcement_percentage =
google_protobuf_UInt32Value_value(enforcing_failure_percentage);
if (enforcement_percentage > 100) {
ValidationErrors::ScopedField field(&errors,
".enforcing_failure_percentage");
errors.AddError("value must be <= 100");
}
if (enforcement_percentage != 0) {
OutlierDetectionConfig::FailurePercentageEjection
failure_percentage_ejection;
failure_percentage_ejection.enforcement_percentage =
enforcement_percentage;
const google_protobuf_UInt32Value* minimum_hosts =
envoy_config_cluster_v3_OutlierDetection_failure_percentage_minimum_hosts(
outlier_detection);
if (minimum_hosts != nullptr) {
failure_percentage_ejection.minimum_hosts =
google_protobuf_UInt32Value_value(minimum_hosts);
}
const google_protobuf_UInt32Value* request_volume =
envoy_config_cluster_v3_OutlierDetection_failure_percentage_request_volume(
outlier_detection);
if (request_volume != nullptr) {
failure_percentage_ejection.request_volume =
google_protobuf_UInt32Value_value(request_volume);
}
const google_protobuf_UInt32Value* threshold =
envoy_config_cluster_v3_OutlierDetection_failure_percentage_threshold(
outlier_detection);
if (threshold != nullptr) {
failure_percentage_ejection.threshold =
google_protobuf_UInt32Value_value(threshold);
if (enforcement_percentage > 100) {
ValidationErrors::ScopedField field(
&errors, ".failure_percentage_threshold");
errors.AddError("value must be <= 100");
}
}
outlier_detection_update.failure_percentage_ejection =
failure_percentage_ejection;
}
}
cds_update->outlier_detection = outlier_detection_update;
}
// Validate override host status.
const auto* common_lb_config =
envoy_config_cluster_v3_Cluster_common_lb_config(cluster);
bool override_host_status_found = false;
if (common_lb_config != nullptr) {
ValidationErrors::ScopedField field(&errors, ".common_lb_config");
const auto* override_host_status =
envoy_config_cluster_v3_Cluster_CommonLbConfig_override_host_status(
common_lb_config);
if (override_host_status != nullptr) {
ValidationErrors::ScopedField field(&errors, ".override_host_status");
size_t size;
const int32_t* statuses = envoy_config_core_v3_HealthStatusSet_statuses(
override_host_status, &size);
for (size_t i = 0; i < size; ++i) {
auto status = XdsHealthStatus::FromUpb(statuses[i]);
if (status.has_value()) {
cds_update->override_host_statuses.Add(*status);
}
}
override_host_status_found = true;
}
}
// If the field is not set, we default to [UNKNOWN, HEALTHY].
if (!override_host_status_found) {
cds_update->override_host_statuses.Add(
XdsHealthStatus(XdsHealthStatus::kUnknown));
cds_update->override_host_statuses.Add(
XdsHealthStatus(XdsHealthStatus::kHealthy));
}
// Record telemetry labels (if any).
const envoy_config_core_v3_Metadata* metadata =
envoy_config_cluster_v3_Cluster_metadata(cluster);
if (metadata != nullptr) {
google_protobuf_Struct* telemetry_labels_struct;
if (envoy_config_core_v3_Metadata_filter_metadata_get(
metadata,
StdStringToUpbString(
absl::string_view("com.google.csm.telemetry_labels")),
&telemetry_labels_struct)) {
size_t iter = kUpb_Map_Begin;
const google_protobuf_Struct_FieldsEntry* fields_entry;
while ((fields_entry = google_protobuf_Struct_fields_next(
telemetry_labels_struct, &iter)) != nullptr) {
// Adds any entry whose value is a string to telemetry_labels.
const google_protobuf_Value* value =
google_protobuf_Struct_FieldsEntry_value(fields_entry);
if (google_protobuf_Value_has_string_value(value)) {
if (UpbStringToAbsl(google_protobuf_Struct_FieldsEntry_key(
fields_entry)) == "service_name") {
cds_update->service_telemetry_label = RefCountedStringValue(
UpbStringToAbsl(google_protobuf_Value_string_value(value)));
} else if (UpbStringToAbsl(google_protobuf_Struct_FieldsEntry_key(
fields_entry)) == "service_namespace") {
cds_update->namespace_telemetry_label = RefCountedStringValue(
UpbStringToAbsl(google_protobuf_Value_string_value(value)));
}
}
}
}
}
// Return result.
if (!errors.ok()) {
return errors.status(absl::StatusCode::kInvalidArgument,
"errors validating Cluster resource");
}
return cds_update;
}
void MaybeLogCluster(const XdsResourceType::DecodeContext& context,
const envoy_config_cluster_v3_Cluster* cluster) {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) {
const upb_MessageDef* msg_type =
envoy_config_cluster_v3_Cluster_getmsgdef(context.symtab);
char buf[10240];
upb_TextEncode(reinterpret_cast<const upb_Message*>(cluster), msg_type,
nullptr, 0, buf, sizeof(buf));
VLOG(2) << "[xds_client " << context.client << "] Cluster: " << buf;
}
}
} // namespace
XdsResourceType::DecodeResult XdsClusterResourceType::Decode(
const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const {
DecodeResult result;
// Parse serialized proto.
auto* resource = envoy_config_cluster_v3_Cluster_parse(
serialized_resource.data(), serialized_resource.size(), context.arena);
if (resource == nullptr) {
result.resource =
absl::InvalidArgumentError("Can't parse Cluster resource.");
return result;
}
MaybeLogCluster(context, resource);
// Validate resource.
result.name =
UpbStringToStdString(envoy_config_cluster_v3_Cluster_name(resource));
auto cds_resource = CdsResourceParse(context, resource);
if (!cds_resource.ok()) {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(ERROR) << "[xds_client " << context.client << "] invalid Cluster "
<< *result.name << ": " << cds_resource.status();
}
result.resource = cds_resource.status();
} else {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(INFO) << "[xds_client " << context.client << "] parsed Cluster "
<< *result.name << ": " << (*cds_resource)->ToString();
}
result.resource = std::move(*cds_resource);
}
return result;
}
} // namespace grpc_core

@ -0,0 +1,57 @@
//
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_PARSER_H
#define GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_PARSER_H
#include "absl/strings/string_view.h"
#include "envoy/config/cluster/v3/cluster.upbdefs.h"
#include "envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.h"
#include "envoy/extensions/transport_sockets/tls/v3/tls.upbdefs.h"
#include "envoy/extensions/upstreams/http/v3/http_protocol_options.upbdefs.h"
#include "upb/reflection/def.h"
#include "src/core/xds/grpc/xds_cluster.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
#include "src/core/xds/xds_client/xds_resource_type_impl.h"
namespace grpc_core {
class XdsClusterResourceType
: public XdsResourceTypeImpl<XdsClusterResourceType, XdsClusterResource> {
public:
absl::string_view type_url() const override {
return "envoy.config.cluster.v3.Cluster";
}
DecodeResult Decode(const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const override;
bool AllResourcesRequiredInSotW() const override { return true; }
void InitUpbSymtab(XdsClient*, upb_DefPool* symtab) const override {
envoy_config_cluster_v3_Cluster_getmsgdef(symtab);
envoy_extensions_clusters_aggregate_v3_ClusterConfig_getmsgdef(symtab);
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_getmsgdef(
symtab);
envoy_extensions_upstreams_http_v3_HttpProtocolOptions_getmsgdef(symtab);
}
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_PARSER_H

@ -16,60 +16,12 @@
#include "src/core/xds/grpc/xds_common_types.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <map>
#include <utility>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "envoy/extensions/transport_sockets/tls/v3/common.upb.h"
#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h"
#include "envoy/type/matcher/v3/regex.upb.h"
#include "envoy/type/matcher/v3/string.upb.h"
#include "google/protobuf/any.upb.h"
#include "google/protobuf/struct.upb.h"
#include "google/protobuf/struct.upbdefs.h"
#include "google/protobuf/wrappers.upb.h"
#include "upb/base/status.hpp"
#include "upb/json/encode.h"
#include "upb/mem/arena.h"
#include "xds/type/v3/typed_struct.upb.h"
#include <grpc/support/json.h>
#include <grpc/support/port_platform.h>
#include "src/core/util/json/json_reader.h"
#include "src/core/util/upb_utils.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/xds_client/xds_client.h"
namespace grpc_core {
//
// ParseDuration()
//
Duration ParseDuration(const google_protobuf_Duration* proto_duration,
ValidationErrors* errors) {
int64_t seconds = google_protobuf_Duration_seconds(proto_duration);
if (seconds < 0 || seconds > 315576000000) {
ValidationErrors::ScopedField field(errors, ".seconds");
errors->AddError("value must be in the range [0, 315576000000]");
}
int32_t nanos = google_protobuf_Duration_nanos(proto_duration);
if (nanos < 0 || nanos > 999999999) {
ValidationErrors::ScopedField field(errors, ".nanos");
errors->AddError("value must be in the range [0, 999999999]");
}
return Duration::FromSecondsAndNanoseconds(seconds, nanos);
}
//
// CommonTlsContext::CertificateValidationContext
//
@ -133,376 +85,4 @@ bool CommonTlsContext::Empty() const {
certificate_validation_context.Empty();
}
namespace {
// CertificateProviderInstance is deprecated but we are still supporting it for
// backward compatibility reasons. Note that we still parse the data into the
// same CertificateProviderPluginInstance struct since the fields are the same.
// TODO(yashykt): Remove this once we stop supporting the old way of fetching
// certificate provider instances.
CommonTlsContext::CertificateProviderPluginInstance
CertificateProviderInstanceParse(
const XdsResourceType::DecodeContext& context,
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance*
certificate_provider_instance_proto,
ValidationErrors* errors) {
CommonTlsContext::CertificateProviderPluginInstance cert_provider;
cert_provider.instance_name = UpbStringToStdString(
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_instance_name(
certificate_provider_instance_proto));
const auto& bootstrap =
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap());
if (bootstrap.certificate_providers().find(cert_provider.instance_name) ==
bootstrap.certificate_providers().end()) {
ValidationErrors::ScopedField field(errors, ".instance_name");
errors->AddError(
absl::StrCat("unrecognized certificate provider instance name: ",
cert_provider.instance_name));
}
cert_provider.certificate_name = UpbStringToStdString(
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_certificate_name(
certificate_provider_instance_proto));
return cert_provider;
}
CommonTlsContext::CertificateProviderPluginInstance
CertificateProviderPluginInstanceParse(
const XdsResourceType::DecodeContext& context,
const envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance*
certificate_provider_plugin_instance_proto,
ValidationErrors* errors) {
CommonTlsContext::CertificateProviderPluginInstance cert_provider;
cert_provider.instance_name = UpbStringToStdString(
envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance_instance_name(
certificate_provider_plugin_instance_proto));
const auto& bootstrap =
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap());
if (bootstrap.certificate_providers().find(cert_provider.instance_name) ==
bootstrap.certificate_providers().end()) {
ValidationErrors::ScopedField field(errors, ".instance_name");
errors->AddError(
absl::StrCat("unrecognized certificate provider instance name: ",
cert_provider.instance_name));
}
cert_provider.certificate_name = UpbStringToStdString(
envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance_certificate_name(
certificate_provider_plugin_instance_proto));
return cert_provider;
}
CommonTlsContext::CertificateValidationContext
CertificateValidationContextParse(
const XdsResourceType::DecodeContext& context,
const envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext*
certificate_validation_context_proto,
ValidationErrors* errors) {
CommonTlsContext::CertificateValidationContext certificate_validation_context;
size_t len = 0;
auto* subject_alt_names_matchers =
envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_match_subject_alt_names(
certificate_validation_context_proto, &len);
for (size_t i = 0; i < len; ++i) {
ValidationErrors::ScopedField field(
errors, absl::StrCat(".match_subject_alt_names[", i, "]"));
StringMatcher::Type type;
std::string matcher;
if (envoy_type_matcher_v3_StringMatcher_has_exact(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kExact;
matcher = UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_exact(
subject_alt_names_matchers[i]));
} else if (envoy_type_matcher_v3_StringMatcher_has_prefix(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kPrefix;
matcher = UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_prefix(
subject_alt_names_matchers[i]));
} else if (envoy_type_matcher_v3_StringMatcher_has_suffix(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kSuffix;
matcher = UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_suffix(
subject_alt_names_matchers[i]));
} else if (envoy_type_matcher_v3_StringMatcher_has_contains(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kContains;
matcher =
UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_contains(
subject_alt_names_matchers[i]));
} else if (envoy_type_matcher_v3_StringMatcher_has_safe_regex(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kSafeRegex;
auto* regex_matcher = envoy_type_matcher_v3_StringMatcher_safe_regex(
subject_alt_names_matchers[i]);
matcher = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher));
} else {
errors->AddError("invalid StringMatcher specified");
continue;
}
bool ignore_case = envoy_type_matcher_v3_StringMatcher_ignore_case(
subject_alt_names_matchers[i]);
absl::StatusOr<StringMatcher> string_matcher =
StringMatcher::Create(type, matcher,
/*case_sensitive=*/!ignore_case);
if (!string_matcher.ok()) {
errors->AddError(string_matcher.status().message());
continue;
}
if (type == StringMatcher::Type::kSafeRegex && ignore_case) {
ValidationErrors::ScopedField field(errors, ".ignore_case");
errors->AddError("not supported for regex matcher");
continue;
}
certificate_validation_context.match_subject_alt_names.push_back(
std::move(string_matcher.value()));
}
auto* ca_certificate_provider_instance =
envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_ca_certificate_provider_instance(
certificate_validation_context_proto);
if (ca_certificate_provider_instance != nullptr) {
ValidationErrors::ScopedField field(errors,
".ca_certificate_provider_instance");
certificate_validation_context.ca_certificate_provider_instance =
CertificateProviderPluginInstanceParse(
context, ca_certificate_provider_instance, errors);
}
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_spki(
certificate_validation_context_proto, nullptr) != nullptr) {
ValidationErrors::ScopedField field(errors, ".verify_certificate_spki");
errors->AddError("feature unsupported");
}
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_hash(
certificate_validation_context_proto, nullptr) != nullptr) {
ValidationErrors::ScopedField field(errors, ".verify_certificate_hash");
errors->AddError("feature unsupported");
}
auto* require_signed_certificate_timestamp =
envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_require_signed_certificate_timestamp(
certificate_validation_context_proto);
if (require_signed_certificate_timestamp != nullptr &&
google_protobuf_BoolValue_value(require_signed_certificate_timestamp)) {
ValidationErrors::ScopedField field(
errors, ".require_signed_certificate_timestamp");
errors->AddError("feature unsupported");
}
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_crl(
certificate_validation_context_proto)) {
ValidationErrors::ScopedField field(errors, ".crl");
errors->AddError("feature unsupported");
}
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_custom_validator_config(
certificate_validation_context_proto)) {
ValidationErrors::ScopedField field(errors, ".custom_validator_config");
errors->AddError("feature unsupported");
}
return certificate_validation_context;
}
} // namespace
CommonTlsContext CommonTlsContext::Parse(
const XdsResourceType::DecodeContext& context,
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext*
common_tls_context_proto,
ValidationErrors* errors) {
CommonTlsContext common_tls_context;
// The validation context is derived from the oneof in
// 'validation_context_type'. 'validation_context_sds_secret_config' is not
// supported.
auto* combined_validation_context =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_combined_validation_context(
common_tls_context_proto);
if (combined_validation_context != nullptr) {
ValidationErrors::ScopedField field(errors, ".combined_validation_context");
auto* default_validation_context =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_default_validation_context(
combined_validation_context);
if (default_validation_context != nullptr) {
ValidationErrors::ScopedField field(errors,
".default_validation_context");
common_tls_context.certificate_validation_context =
CertificateValidationContextParse(context, default_validation_context,
errors);
}
// If after parsing default_validation_context,
// common_tls_context->certificate_validation_context.ca_certificate_provider_instance
// is empty, fall back onto
// 'validation_context_certificate_provider_instance' inside
// 'combined_validation_context'. Note that this way of fetching root
// certificates is deprecated and will be removed in the future.
// TODO(yashykt): Remove this once it's no longer needed.
const auto* validation_context_certificate_provider_instance =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_validation_context_certificate_provider_instance(
combined_validation_context);
if (common_tls_context.certificate_validation_context
.ca_certificate_provider_instance.Empty() &&
validation_context_certificate_provider_instance != nullptr) {
ValidationErrors::ScopedField field(
errors, ".validation_context_certificate_provider_instance");
common_tls_context.certificate_validation_context
.ca_certificate_provider_instance = CertificateProviderInstanceParse(
context, validation_context_certificate_provider_instance, errors);
}
} else {
auto* validation_context =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_validation_context(
common_tls_context_proto);
if (validation_context != nullptr) {
ValidationErrors::ScopedField field(errors, ".validation_context");
common_tls_context.certificate_validation_context =
CertificateValidationContextParse(context, validation_context,
errors);
} else if (
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_validation_context_sds_secret_config(
common_tls_context_proto)) {
ValidationErrors::ScopedField field(
errors, ".validation_context_sds_secret_config");
errors->AddError("feature unsupported");
}
}
auto* tls_certificate_provider_instance =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_provider_instance(
common_tls_context_proto);
if (tls_certificate_provider_instance != nullptr) {
ValidationErrors::ScopedField field(errors,
".tls_certificate_provider_instance");
common_tls_context.tls_certificate_provider_instance =
CertificateProviderPluginInstanceParse(
context, tls_certificate_provider_instance, errors);
} else {
// Fall back onto 'tls_certificate_certificate_provider_instance'. Note that
// this way of fetching identity certificates is deprecated and will be
// removed in the future.
// TODO(yashykt): Remove this once it's no longer needed.
auto* tls_certificate_certificate_provider_instance =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_certificate_provider_instance(
common_tls_context_proto);
if (tls_certificate_certificate_provider_instance != nullptr) {
ValidationErrors::ScopedField field(
errors, ".tls_certificate_certificate_provider_instance");
common_tls_context.tls_certificate_provider_instance =
CertificateProviderInstanceParse(
context, tls_certificate_certificate_provider_instance, errors);
} else {
size_t size;
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificates(
common_tls_context_proto, &size);
if (size != 0) {
ValidationErrors::ScopedField field(errors, ".tls_certificates");
errors->AddError("feature unsupported");
}
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_sds_secret_configs(
common_tls_context_proto, &size);
if (size != 0) {
ValidationErrors::ScopedField field(
errors, ".tls_certificate_sds_secret_configs");
errors->AddError("feature unsupported");
}
}
}
if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_tls_params(
common_tls_context_proto)) {
ValidationErrors::ScopedField field(errors, ".tls_params");
errors->AddError("feature unsupported");
}
if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_custom_handshaker(
common_tls_context_proto)) {
ValidationErrors::ScopedField field(errors, ".custom_handshaker");
errors->AddError("feature unsupported");
}
return common_tls_context;
}
//
// ExtractXdsExtension
//
namespace {
absl::StatusOr<Json> ParseProtobufStructToJson(
const XdsResourceType::DecodeContext& context,
const google_protobuf_Struct* resource) {
upb::Status status;
const auto* msg_def = google_protobuf_Struct_getmsgdef(context.symtab);
size_t json_size =
upb_JsonEncode(reinterpret_cast<const upb_Message*>(resource), msg_def,
context.symtab, 0, nullptr, 0, status.ptr());
if (json_size == static_cast<size_t>(-1)) {
return absl::InvalidArgumentError(
absl::StrCat("error encoding google::Protobuf::Struct as JSON: ",
upb_Status_ErrorMessage(status.ptr())));
}
void* buf = upb_Arena_Malloc(context.arena, json_size + 1);
upb_JsonEncode(reinterpret_cast<const upb_Message*>(resource), msg_def,
context.symtab, 0, reinterpret_cast<char*>(buf), json_size + 1,
status.ptr());
auto json = JsonParse(reinterpret_cast<char*>(buf));
if (!json.ok()) {
// This should never happen.
return absl::InternalError(
absl::StrCat("error parsing JSON form of google::Protobuf::Struct "
"produced by upb library: ",
json.status().ToString()));
}
return std::move(*json);
}
} // namespace
absl::optional<XdsExtension> ExtractXdsExtension(
const XdsResourceType::DecodeContext& context,
const google_protobuf_Any* any, ValidationErrors* errors) {
if (any == nullptr) {
errors->AddError("field not present");
return absl::nullopt;
}
XdsExtension extension;
auto strip_type_prefix = [&]() {
ValidationErrors::ScopedField field(errors, ".type_url");
if (extension.type.empty()) {
errors->AddError("field not present");
return false;
}
size_t pos = extension.type.rfind('/');
if (pos == absl::string_view::npos || pos == extension.type.size() - 1) {
errors->AddError(absl::StrCat("invalid value \"", extension.type, "\""));
} else {
extension.type = extension.type.substr(pos + 1);
}
return true;
};
extension.type = UpbStringToAbsl(google_protobuf_Any_type_url(any));
if (!strip_type_prefix()) return absl::nullopt;
extension.validation_fields.emplace_back(
errors, absl::StrCat(".value[", extension.type, "]"));
absl::string_view any_value = UpbStringToAbsl(google_protobuf_Any_value(any));
if (extension.type == "xds.type.v3.TypedStruct" ||
extension.type == "udpa.type.v1.TypedStruct") {
const auto* typed_struct = xds_type_v3_TypedStruct_parse(
any_value.data(), any_value.size(), context.arena);
if (typed_struct == nullptr) {
errors->AddError("could not parse");
return absl::nullopt;
}
extension.type =
UpbStringToAbsl(xds_type_v3_TypedStruct_type_url(typed_struct));
if (!strip_type_prefix()) return absl::nullopt;
extension.validation_fields.emplace_back(
errors, absl::StrCat(".value[", extension.type, "]"));
auto* protobuf_struct = xds_type_v3_TypedStruct_value(typed_struct);
if (protobuf_struct == nullptr) {
extension.value = Json::FromObject({}); // Default to empty object.
} else {
auto json = ParseProtobufStructToJson(context, protobuf_struct);
if (!json.ok()) {
errors->AddError(json.status().message());
return absl::nullopt;
}
extension.value = std::move(*json);
}
} else {
extension.value = any_value;
}
return std::move(extension);
}
} // namespace grpc_core

@ -21,25 +21,14 @@
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h"
#include "google/protobuf/any.upb.h"
#include "google/protobuf/duration.upb.h"
#include <grpc/support/port_platform.h>
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/lib/matchers/matchers.h"
#include "src/core/util/json/json.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
namespace grpc_core {
Duration ParseDuration(const google_protobuf_Duration* proto_duration,
ValidationErrors* errors);
struct CommonTlsContext {
struct CertificateProviderPluginInstance {
std::string instance_name;
@ -80,12 +69,6 @@ struct CommonTlsContext {
std::string ToString() const;
bool Empty() const;
static CommonTlsContext Parse(
const XdsResourceType::DecodeContext& context,
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext*
common_tls_context_proto,
ValidationErrors* errors);
};
struct XdsExtension {
@ -99,10 +82,6 @@ struct XdsExtension {
std::vector<ValidationErrors::ScopedField> validation_fields;
};
absl::optional<XdsExtension> ExtractXdsExtension(
const XdsResourceType::DecodeContext& context,
const google_protobuf_Any* any, ValidationErrors* errors);
} // namespace grpc_core
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_COMMON_TYPES_H

@ -0,0 +1,449 @@
//
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "src/core/xds/grpc/xds_common_types_parser.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <map>
#include <utility>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "envoy/extensions/transport_sockets/tls/v3/common.upb.h"
#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h"
#include "envoy/type/matcher/v3/regex.upb.h"
#include "envoy/type/matcher/v3/string.upb.h"
#include "google/protobuf/any.upb.h"
#include "google/protobuf/struct.upb.h"
#include "google/protobuf/struct.upbdefs.h"
#include "google/protobuf/wrappers.upb.h"
#include "upb/base/status.hpp"
#include "upb/json/encode.h"
#include "upb/mem/arena.h"
#include "xds/type/v3/typed_struct.upb.h"
#include <grpc/support/json.h>
#include <grpc/support/port_platform.h>
#include "src/core/util/json/json_reader.h"
#include "src/core/util/upb_utils.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/xds_client/xds_client.h"
namespace grpc_core {
//
// ParseDuration()
//
Duration ParseDuration(const google_protobuf_Duration* proto_duration,
ValidationErrors* errors) {
int64_t seconds = google_protobuf_Duration_seconds(proto_duration);
if (seconds < 0 || seconds > 315576000000) {
ValidationErrors::ScopedField field(errors, ".seconds");
errors->AddError("value must be in the range [0, 315576000000]");
}
int32_t nanos = google_protobuf_Duration_nanos(proto_duration);
if (nanos < 0 || nanos > 999999999) {
ValidationErrors::ScopedField field(errors, ".nanos");
errors->AddError("value must be in the range [0, 999999999]");
}
return Duration::FromSecondsAndNanoseconds(seconds, nanos);
}
//
// CommonTlsContextParse()
//
namespace {
// CertificateProviderInstance is deprecated but we are still supporting it for
// backward compatibility reasons. Note that we still parse the data into the
// same CertificateProviderPluginInstance struct since the fields are the same.
// TODO(yashykt): Remove this once we stop supporting the old way of fetching
// certificate provider instances.
CommonTlsContext::CertificateProviderPluginInstance
CertificateProviderInstanceParse(
const XdsResourceType::DecodeContext& context,
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance*
certificate_provider_instance_proto,
ValidationErrors* errors) {
CommonTlsContext::CertificateProviderPluginInstance cert_provider;
cert_provider.instance_name = UpbStringToStdString(
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_instance_name(
certificate_provider_instance_proto));
const auto& bootstrap =
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap());
if (bootstrap.certificate_providers().find(cert_provider.instance_name) ==
bootstrap.certificate_providers().end()) {
ValidationErrors::ScopedField field(errors, ".instance_name");
errors->AddError(
absl::StrCat("unrecognized certificate provider instance name: ",
cert_provider.instance_name));
}
cert_provider.certificate_name = UpbStringToStdString(
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_certificate_name(
certificate_provider_instance_proto));
return cert_provider;
}
CommonTlsContext::CertificateProviderPluginInstance
CertificateProviderPluginInstanceParse(
const XdsResourceType::DecodeContext& context,
const envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance*
certificate_provider_plugin_instance_proto,
ValidationErrors* errors) {
CommonTlsContext::CertificateProviderPluginInstance cert_provider;
cert_provider.instance_name = UpbStringToStdString(
envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance_instance_name(
certificate_provider_plugin_instance_proto));
const auto& bootstrap =
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap());
if (bootstrap.certificate_providers().find(cert_provider.instance_name) ==
bootstrap.certificate_providers().end()) {
ValidationErrors::ScopedField field(errors, ".instance_name");
errors->AddError(
absl::StrCat("unrecognized certificate provider instance name: ",
cert_provider.instance_name));
}
cert_provider.certificate_name = UpbStringToStdString(
envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance_certificate_name(
certificate_provider_plugin_instance_proto));
return cert_provider;
}
CommonTlsContext::CertificateValidationContext
CertificateValidationContextParse(
const XdsResourceType::DecodeContext& context,
const envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext*
certificate_validation_context_proto,
ValidationErrors* errors) {
CommonTlsContext::CertificateValidationContext certificate_validation_context;
size_t len = 0;
auto* subject_alt_names_matchers =
envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_match_subject_alt_names(
certificate_validation_context_proto, &len);
for (size_t i = 0; i < len; ++i) {
ValidationErrors::ScopedField field(
errors, absl::StrCat(".match_subject_alt_names[", i, "]"));
StringMatcher::Type type;
std::string matcher;
if (envoy_type_matcher_v3_StringMatcher_has_exact(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kExact;
matcher = UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_exact(
subject_alt_names_matchers[i]));
} else if (envoy_type_matcher_v3_StringMatcher_has_prefix(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kPrefix;
matcher = UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_prefix(
subject_alt_names_matchers[i]));
} else if (envoy_type_matcher_v3_StringMatcher_has_suffix(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kSuffix;
matcher = UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_suffix(
subject_alt_names_matchers[i]));
} else if (envoy_type_matcher_v3_StringMatcher_has_contains(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kContains;
matcher =
UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_contains(
subject_alt_names_matchers[i]));
} else if (envoy_type_matcher_v3_StringMatcher_has_safe_regex(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kSafeRegex;
auto* regex_matcher = envoy_type_matcher_v3_StringMatcher_safe_regex(
subject_alt_names_matchers[i]);
matcher = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher));
} else {
errors->AddError("invalid StringMatcher specified");
continue;
}
bool ignore_case = envoy_type_matcher_v3_StringMatcher_ignore_case(
subject_alt_names_matchers[i]);
absl::StatusOr<StringMatcher> string_matcher =
StringMatcher::Create(type, matcher,
/*case_sensitive=*/!ignore_case);
if (!string_matcher.ok()) {
errors->AddError(string_matcher.status().message());
continue;
}
if (type == StringMatcher::Type::kSafeRegex && ignore_case) {
ValidationErrors::ScopedField field(errors, ".ignore_case");
errors->AddError("not supported for regex matcher");
continue;
}
certificate_validation_context.match_subject_alt_names.push_back(
std::move(string_matcher.value()));
}
auto* ca_certificate_provider_instance =
envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_ca_certificate_provider_instance(
certificate_validation_context_proto);
if (ca_certificate_provider_instance != nullptr) {
ValidationErrors::ScopedField field(errors,
".ca_certificate_provider_instance");
certificate_validation_context.ca_certificate_provider_instance =
CertificateProviderPluginInstanceParse(
context, ca_certificate_provider_instance, errors);
}
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_spki(
certificate_validation_context_proto, nullptr) != nullptr) {
ValidationErrors::ScopedField field(errors, ".verify_certificate_spki");
errors->AddError("feature unsupported");
}
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_hash(
certificate_validation_context_proto, nullptr) != nullptr) {
ValidationErrors::ScopedField field(errors, ".verify_certificate_hash");
errors->AddError("feature unsupported");
}
auto* require_signed_certificate_timestamp =
envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_require_signed_certificate_timestamp(
certificate_validation_context_proto);
if (require_signed_certificate_timestamp != nullptr &&
google_protobuf_BoolValue_value(require_signed_certificate_timestamp)) {
ValidationErrors::ScopedField field(
errors, ".require_signed_certificate_timestamp");
errors->AddError("feature unsupported");
}
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_crl(
certificate_validation_context_proto)) {
ValidationErrors::ScopedField field(errors, ".crl");
errors->AddError("feature unsupported");
}
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_custom_validator_config(
certificate_validation_context_proto)) {
ValidationErrors::ScopedField field(errors, ".custom_validator_config");
errors->AddError("feature unsupported");
}
return certificate_validation_context;
}
} // namespace
CommonTlsContext CommonTlsContextParse(
const XdsResourceType::DecodeContext& context,
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext*
common_tls_context_proto,
ValidationErrors* errors) {
CommonTlsContext common_tls_context;
// The validation context is derived from the oneof in
// 'validation_context_type'. 'validation_context_sds_secret_config' is not
// supported.
auto* combined_validation_context =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_combined_validation_context(
common_tls_context_proto);
if (combined_validation_context != nullptr) {
ValidationErrors::ScopedField field(errors, ".combined_validation_context");
auto* default_validation_context =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_default_validation_context(
combined_validation_context);
if (default_validation_context != nullptr) {
ValidationErrors::ScopedField field(errors,
".default_validation_context");
common_tls_context.certificate_validation_context =
CertificateValidationContextParse(context, default_validation_context,
errors);
}
// If after parsing default_validation_context,
// common_tls_context->certificate_validation_context.ca_certificate_provider_instance
// is empty, fall back onto
// 'validation_context_certificate_provider_instance' inside
// 'combined_validation_context'. Note that this way of fetching root
// certificates is deprecated and will be removed in the future.
// TODO(yashykt): Remove this once it's no longer needed.
const auto* validation_context_certificate_provider_instance =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_validation_context_certificate_provider_instance(
combined_validation_context);
if (common_tls_context.certificate_validation_context
.ca_certificate_provider_instance.Empty() &&
validation_context_certificate_provider_instance != nullptr) {
ValidationErrors::ScopedField field(
errors, ".validation_context_certificate_provider_instance");
common_tls_context.certificate_validation_context
.ca_certificate_provider_instance = CertificateProviderInstanceParse(
context, validation_context_certificate_provider_instance, errors);
}
} else {
auto* validation_context =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_validation_context(
common_tls_context_proto);
if (validation_context != nullptr) {
ValidationErrors::ScopedField field(errors, ".validation_context");
common_tls_context.certificate_validation_context =
CertificateValidationContextParse(context, validation_context,
errors);
} else if (
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_validation_context_sds_secret_config(
common_tls_context_proto)) {
ValidationErrors::ScopedField field(
errors, ".validation_context_sds_secret_config");
errors->AddError("feature unsupported");
}
}
auto* tls_certificate_provider_instance =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_provider_instance(
common_tls_context_proto);
if (tls_certificate_provider_instance != nullptr) {
ValidationErrors::ScopedField field(errors,
".tls_certificate_provider_instance");
common_tls_context.tls_certificate_provider_instance =
CertificateProviderPluginInstanceParse(
context, tls_certificate_provider_instance, errors);
} else {
// Fall back onto 'tls_certificate_certificate_provider_instance'. Note that
// this way of fetching identity certificates is deprecated and will be
// removed in the future.
// TODO(yashykt): Remove this once it's no longer needed.
auto* tls_certificate_certificate_provider_instance =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_certificate_provider_instance(
common_tls_context_proto);
if (tls_certificate_certificate_provider_instance != nullptr) {
ValidationErrors::ScopedField field(
errors, ".tls_certificate_certificate_provider_instance");
common_tls_context.tls_certificate_provider_instance =
CertificateProviderInstanceParse(
context, tls_certificate_certificate_provider_instance, errors);
} else {
size_t size;
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificates(
common_tls_context_proto, &size);
if (size != 0) {
ValidationErrors::ScopedField field(errors, ".tls_certificates");
errors->AddError("feature unsupported");
}
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_sds_secret_configs(
common_tls_context_proto, &size);
if (size != 0) {
ValidationErrors::ScopedField field(
errors, ".tls_certificate_sds_secret_configs");
errors->AddError("feature unsupported");
}
}
}
if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_tls_params(
common_tls_context_proto)) {
ValidationErrors::ScopedField field(errors, ".tls_params");
errors->AddError("feature unsupported");
}
if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_custom_handshaker(
common_tls_context_proto)) {
ValidationErrors::ScopedField field(errors, ".custom_handshaker");
errors->AddError("feature unsupported");
}
return common_tls_context;
}
//
// ExtractXdsExtension
//
namespace {
absl::StatusOr<Json> ParseProtobufStructToJson(
const XdsResourceType::DecodeContext& context,
const google_protobuf_Struct* resource) {
upb::Status status;
const auto* msg_def = google_protobuf_Struct_getmsgdef(context.symtab);
size_t json_size =
upb_JsonEncode(reinterpret_cast<const upb_Message*>(resource), msg_def,
context.symtab, 0, nullptr, 0, status.ptr());
if (json_size == static_cast<size_t>(-1)) {
return absl::InvalidArgumentError(
absl::StrCat("error encoding google::Protobuf::Struct as JSON: ",
upb_Status_ErrorMessage(status.ptr())));
}
void* buf = upb_Arena_Malloc(context.arena, json_size + 1);
upb_JsonEncode(reinterpret_cast<const upb_Message*>(resource), msg_def,
context.symtab, 0, reinterpret_cast<char*>(buf), json_size + 1,
status.ptr());
auto json = JsonParse(reinterpret_cast<char*>(buf));
if (!json.ok()) {
// This should never happen.
return absl::InternalError(
absl::StrCat("error parsing JSON form of google::Protobuf::Struct "
"produced by upb library: ",
json.status().ToString()));
}
return std::move(*json);
}
} // namespace
absl::optional<XdsExtension> ExtractXdsExtension(
const XdsResourceType::DecodeContext& context,
const google_protobuf_Any* any, ValidationErrors* errors) {
if (any == nullptr) {
errors->AddError("field not present");
return absl::nullopt;
}
XdsExtension extension;
auto strip_type_prefix = [&]() {
ValidationErrors::ScopedField field(errors, ".type_url");
if (extension.type.empty()) {
errors->AddError("field not present");
return false;
}
size_t pos = extension.type.rfind('/');
if (pos == absl::string_view::npos || pos == extension.type.size() - 1) {
errors->AddError(absl::StrCat("invalid value \"", extension.type, "\""));
} else {
extension.type = extension.type.substr(pos + 1);
}
return true;
};
extension.type = UpbStringToAbsl(google_protobuf_Any_type_url(any));
if (!strip_type_prefix()) return absl::nullopt;
extension.validation_fields.emplace_back(
errors, absl::StrCat(".value[", extension.type, "]"));
absl::string_view any_value = UpbStringToAbsl(google_protobuf_Any_value(any));
if (extension.type == "xds.type.v3.TypedStruct" ||
extension.type == "udpa.type.v1.TypedStruct") {
const auto* typed_struct = xds_type_v3_TypedStruct_parse(
any_value.data(), any_value.size(), context.arena);
if (typed_struct == nullptr) {
errors->AddError("could not parse");
return absl::nullopt;
}
extension.type =
UpbStringToAbsl(xds_type_v3_TypedStruct_type_url(typed_struct));
if (!strip_type_prefix()) return absl::nullopt;
extension.validation_fields.emplace_back(
errors, absl::StrCat(".value[", extension.type, "]"));
auto* protobuf_struct = xds_type_v3_TypedStruct_value(typed_struct);
if (protobuf_struct == nullptr) {
extension.value = Json::FromObject({}); // Default to empty object.
} else {
auto json = ParseProtobufStructToJson(context, protobuf_struct);
if (!json.ok()) {
errors->AddError(json.status().message());
return absl::nullopt;
}
extension.value = std::move(*json);
}
} else {
extension.value = any_value;
}
return std::move(extension);
}
} // namespace grpc_core

@ -0,0 +1,47 @@
//
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_COMMON_TYPES_PARSER_H
#define GRPC_SRC_CORE_XDS_GRPC_XDS_COMMON_TYPES_PARSER_H
#include "absl/types/optional.h"
#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h"
#include "google/protobuf/any.upb.h"
#include "google/protobuf/duration.upb.h"
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
namespace grpc_core {
Duration ParseDuration(const google_protobuf_Duration* proto_duration,
ValidationErrors* errors);
CommonTlsContext CommonTlsContextParse(
const XdsResourceType::DecodeContext& context,
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext*
common_tls_context_proto,
ValidationErrors* errors);
absl::optional<XdsExtension> ExtractXdsExtension(
const XdsResourceType::DecodeContext& context,
const google_protobuf_Any* any, ValidationErrors* errors);
} // namespace grpc_core
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_COMMON_TYPES_PARSER_H

@ -16,66 +16,13 @@
#include "src/core/xds/grpc/xds_endpoint.h"
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <set>
#include <vector>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/types/optional.h"
#include "envoy/config/core/v3/address.upb.h"
#include "envoy/config/core/v3/base.upb.h"
#include "envoy/config/endpoint/v3/endpoint.upb.h"
#include "envoy/config/endpoint/v3/endpoint.upbdefs.h"
#include "envoy/config/endpoint/v3/endpoint_components.upb.h"
#include "envoy/type/v3/percent.upb.h"
#include "google/protobuf/wrappers.upb.h"
#include "upb/text/encode.h"
#include <grpc/support/port_platform.h>
#include "src/core/lib/address_utils/parse_address.h"
#include "src/core/lib/address_utils/sockaddr_utils.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/gprpp/env.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/lib/iomgr/resolved_address.h"
#include "src/core/util/string.h"
#include "src/core/util/upb_utils.h"
#include "src/core/xds/grpc/xds_health_status.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
// IWYU pragma: no_include "absl/meta/type_traits.h"
namespace grpc_core {
namespace {
// TODO(roth): Remove this once dualstack support is stable.
bool XdsDualstackEndpointsEnabled() {
auto value = GetEnv("GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS");
if (!value.has_value()) return false;
bool parsed_value;
bool parse_succeeded = gpr_parse_bool_value(value->c_str(), &parsed_value);
return parse_succeeded && parsed_value;
}
} // namespace
//
// XdsEndpointResource
//
std::string XdsEndpointResource::Priority::Locality::ToString() const {
std::vector<std::string> endpoint_strings;
for (const EndpointAddresses& endpoint : endpoints) {
@ -147,370 +94,4 @@ std::string XdsEndpointResource::ToString() const {
drop_config == nullptr ? "<null>" : drop_config->ToString());
}
//
// XdsEndpointResourceType
//
namespace {
void MaybeLogClusterLoadAssignment(
const XdsResourceType::DecodeContext& context,
const envoy_config_endpoint_v3_ClusterLoadAssignment* cla) {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) {
const upb_MessageDef* msg_type =
envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef(
context.symtab);
char buf[10240];
upb_TextEncode(reinterpret_cast<const upb_Message*>(cla), msg_type, nullptr,
0, buf, sizeof(buf));
VLOG(2) << "[xds_client " << context.client
<< "] ClusterLoadAssignment: " << buf;
}
}
absl::optional<grpc_resolved_address> ParseCoreAddress(
const envoy_config_core_v3_Address* address, ValidationErrors* errors) {
if (address == nullptr) {
errors->AddError("field not present");
return absl::nullopt;
}
ValidationErrors::ScopedField field(errors, ".socket_address");
const envoy_config_core_v3_SocketAddress* socket_address =
envoy_config_core_v3_Address_socket_address(address);
if (socket_address == nullptr) {
errors->AddError("field not present");
return absl::nullopt;
}
std::string address_str = UpbStringToStdString(
envoy_config_core_v3_SocketAddress_address(socket_address));
uint32_t port;
{
ValidationErrors::ScopedField field(errors, ".port_value");
port = envoy_config_core_v3_SocketAddress_port_value(socket_address);
if (GPR_UNLIKELY(port >> 16) != 0) {
errors->AddError("invalid port");
return absl::nullopt;
}
}
auto addr = StringToSockaddr(address_str, port);
if (!addr.ok()) {
errors->AddError(addr.status().message());
return absl::nullopt;
}
return *addr;
}
absl::optional<EndpointAddresses> EndpointAddressesParse(
const envoy_config_endpoint_v3_LbEndpoint* lb_endpoint,
ValidationErrors* errors) {
// health_status
const int32_t health_status =
envoy_config_endpoint_v3_LbEndpoint_health_status(lb_endpoint);
auto status = XdsHealthStatus::FromUpb(health_status);
if (!status.has_value()) return absl::nullopt;
// load_balancing_weight
uint32_t weight = 1;
{
ValidationErrors::ScopedField field(errors, ".load_balancing_weight");
const google_protobuf_UInt32Value* load_balancing_weight =
envoy_config_endpoint_v3_LbEndpoint_load_balancing_weight(lb_endpoint);
if (load_balancing_weight != nullptr) {
weight = google_protobuf_UInt32Value_value(load_balancing_weight);
if (weight == 0) {
errors->AddError("must be greater than 0");
}
}
}
// endpoint
std::vector<grpc_resolved_address> addresses;
{
ValidationErrors::ScopedField field(errors, ".endpoint");
const envoy_config_endpoint_v3_Endpoint* endpoint =
envoy_config_endpoint_v3_LbEndpoint_endpoint(lb_endpoint);
if (endpoint == nullptr) {
errors->AddError("field not present");
return absl::nullopt;
}
{
ValidationErrors::ScopedField field(errors, ".address");
auto address = ParseCoreAddress(
envoy_config_endpoint_v3_Endpoint_address(endpoint), errors);
if (address.has_value()) addresses.push_back(*address);
}
if (XdsDualstackEndpointsEnabled()) {
size_t size;
auto* additional_addresses =
envoy_config_endpoint_v3_Endpoint_additional_addresses(endpoint,
&size);
for (size_t i = 0; i < size; ++i) {
ValidationErrors::ScopedField field(
errors, absl::StrCat(".additional_addresses[", i, "].address"));
auto address = ParseCoreAddress(
envoy_config_endpoint_v3_Endpoint_AdditionalAddress_address(
additional_addresses[i]),
errors);
if (address.has_value()) addresses.push_back(*address);
}
}
}
if (addresses.empty()) return absl::nullopt;
// Convert to EndpointAddresses.
return EndpointAddresses(
addresses, ChannelArgs()
.Set(GRPC_ARG_ADDRESS_WEIGHT, weight)
.Set(GRPC_ARG_XDS_HEALTH_STATUS, status->status()));
}
struct ParsedLocality {
size_t priority;
XdsEndpointResource::Priority::Locality locality;
};
struct ResolvedAddressLessThan {
bool operator()(const grpc_resolved_address& a1,
const grpc_resolved_address& a2) const {
if (a1.len != a2.len) return a1.len < a2.len;
return memcmp(a1.addr, a2.addr, a1.len) < 0;
}
};
using ResolvedAddressSet =
std::set<grpc_resolved_address, ResolvedAddressLessThan>;
absl::optional<ParsedLocality> LocalityParse(
const envoy_config_endpoint_v3_LocalityLbEndpoints* locality_lb_endpoints,
ResolvedAddressSet* address_set, ValidationErrors* errors) {
const size_t original_error_size = errors->size();
ParsedLocality parsed_locality;
// load_balancing_weight
// If LB weight is not specified or 0, it means this locality is assigned
// no load.
const google_protobuf_UInt32Value* lb_weight =
envoy_config_endpoint_v3_LocalityLbEndpoints_load_balancing_weight(
locality_lb_endpoints);
parsed_locality.locality.lb_weight =
lb_weight != nullptr ? google_protobuf_UInt32Value_value(lb_weight) : 0;
if (parsed_locality.locality.lb_weight == 0) return absl::nullopt;
// locality
const envoy_config_core_v3_Locality* locality =
envoy_config_endpoint_v3_LocalityLbEndpoints_locality(
locality_lb_endpoints);
if (locality == nullptr) {
ValidationErrors::ScopedField field(errors, ".locality");
errors->AddError("field not present");
return absl::nullopt;
}
// region
std::string region =
UpbStringToStdString(envoy_config_core_v3_Locality_region(locality));
// zone
std::string zone =
UpbStringToStdString(envoy_config_core_v3_Locality_zone(locality));
// sub_zone
std::string sub_zone =
UpbStringToStdString(envoy_config_core_v3_Locality_sub_zone(locality));
parsed_locality.locality.name = MakeRefCounted<XdsLocalityName>(
std::move(region), std::move(zone), std::move(sub_zone));
// lb_endpoints
size_t size;
const envoy_config_endpoint_v3_LbEndpoint* const* lb_endpoints =
envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints(
locality_lb_endpoints, &size);
for (size_t i = 0; i < size; ++i) {
ValidationErrors::ScopedField field(errors,
absl::StrCat(".lb_endpoints[", i, "]"));
auto endpoint = EndpointAddressesParse(lb_endpoints[i], errors);
if (endpoint.has_value()) {
for (const auto& address : endpoint->addresses()) {
bool inserted = address_set->insert(address).second;
if (!inserted) {
errors->AddError(absl::StrCat(
"duplicate endpoint address \"",
grpc_sockaddr_to_uri(&address).value_or("<unknown>"), "\""));
}
}
parsed_locality.locality.endpoints.push_back(std::move(*endpoint));
}
}
// priority
parsed_locality.priority =
envoy_config_endpoint_v3_LocalityLbEndpoints_priority(
locality_lb_endpoints);
// Return result.
if (original_error_size != errors->size()) return absl::nullopt;
return parsed_locality;
}
void DropParseAndAppend(
const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload*
drop_overload,
XdsEndpointResource::DropConfig* drop_config, ValidationErrors* errors) {
// category
std::string category = UpbStringToStdString(
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_category(
drop_overload));
if (category.empty()) {
ValidationErrors::ScopedField field(errors, ".category");
errors->AddError("empty drop category name");
}
// drop_percentage
uint32_t numerator;
{
ValidationErrors::ScopedField field(errors, ".drop_percentage");
const envoy_type_v3_FractionalPercent* drop_percentage =
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_drop_percentage(
drop_overload);
if (drop_percentage == nullptr) {
errors->AddError("field not present");
return;
}
numerator = envoy_type_v3_FractionalPercent_numerator(drop_percentage);
{
ValidationErrors::ScopedField field(errors, ".denominator");
const int denominator =
envoy_type_v3_FractionalPercent_denominator(drop_percentage);
// Normalize to million.
switch (denominator) {
case envoy_type_v3_FractionalPercent_HUNDRED:
numerator *= 10000;
break;
case envoy_type_v3_FractionalPercent_TEN_THOUSAND:
numerator *= 100;
break;
case envoy_type_v3_FractionalPercent_MILLION:
break;
default:
errors->AddError("unknown denominator type");
}
}
// Cap numerator to 1000000.
numerator = std::min(numerator, 1000000u);
}
// Add category.
drop_config->AddCategory(std::move(category), numerator);
}
absl::StatusOr<std::shared_ptr<const XdsEndpointResource>> EdsResourceParse(
const XdsResourceType::DecodeContext& /*context*/,
const envoy_config_endpoint_v3_ClusterLoadAssignment*
cluster_load_assignment) {
ValidationErrors errors;
auto eds_resource = std::make_shared<XdsEndpointResource>();
// endpoints
{
ValidationErrors::ScopedField field(&errors, "endpoints");
ResolvedAddressSet address_set;
size_t locality_size;
const envoy_config_endpoint_v3_LocalityLbEndpoints* const* endpoints =
envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints(
cluster_load_assignment, &locality_size);
for (size_t i = 0; i < locality_size; ++i) {
ValidationErrors::ScopedField field(&errors, absl::StrCat("[", i, "]"));
auto parsed_locality = LocalityParse(endpoints[i], &address_set, &errors);
if (parsed_locality.has_value()) {
CHECK_NE(parsed_locality->locality.lb_weight, 0u);
// Make sure prorities is big enough. Note that they might not
// arrive in priority order.
if (eds_resource->priorities.size() < parsed_locality->priority + 1) {
eds_resource->priorities.resize(parsed_locality->priority + 1);
}
auto& locality_map =
eds_resource->priorities[parsed_locality->priority].localities;
auto it = locality_map.find(parsed_locality->locality.name.get());
if (it != locality_map.end()) {
errors.AddError(absl::StrCat(
"duplicate locality ",
parsed_locality->locality.name->human_readable_string()
.as_string_view(),
" found in priority ", parsed_locality->priority));
} else {
locality_map.emplace(parsed_locality->locality.name.get(),
std::move(parsed_locality->locality));
}
}
}
for (size_t i = 0; i < eds_resource->priorities.size(); ++i) {
const auto& priority = eds_resource->priorities[i];
if (priority.localities.empty()) {
errors.AddError(absl::StrCat("priority ", i, " empty"));
} else {
// Check that the sum of the locality weights in this priority
// does not exceed the max value for a uint32.
uint64_t total_weight = 0;
for (const auto& p : priority.localities) {
total_weight += p.second.lb_weight;
if (total_weight > std::numeric_limits<uint32_t>::max()) {
errors.AddError(
absl::StrCat("sum of locality weights for priority ", i,
" exceeds uint32 max"));
break;
}
}
}
}
}
// policy
const auto* policy = envoy_config_endpoint_v3_ClusterLoadAssignment_policy(
cluster_load_assignment);
if (policy != nullptr) {
ValidationErrors::ScopedField field(&errors, "policy");
size_t drop_size;
const auto* const* drop_overload =
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_drop_overloads(
policy, &drop_size);
if (drop_size > 0) {
eds_resource->drop_config =
MakeRefCounted<XdsEndpointResource::DropConfig>();
}
for (size_t i = 0; i < drop_size; ++i) {
ValidationErrors::ScopedField field(
&errors, absl::StrCat(".drop_overloads[", i, "]"));
DropParseAndAppend(drop_overload[i], eds_resource->drop_config.get(),
&errors);
}
}
// Return result.
if (!errors.ok()) {
return errors.status(absl::StatusCode::kInvalidArgument,
"errors parsing EDS resource");
}
return eds_resource;
}
} // namespace
XdsResourceType::DecodeResult XdsEndpointResourceType::Decode(
const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const {
DecodeResult result;
// Parse serialized proto.
auto* resource = envoy_config_endpoint_v3_ClusterLoadAssignment_parse(
serialized_resource.data(), serialized_resource.size(), context.arena);
if (resource == nullptr) {
result.resource = absl::InvalidArgumentError(
"Can't parse ClusterLoadAssignment resource.");
return result;
}
MaybeLogClusterLoadAssignment(context, resource);
// Validate resource.
result.name = UpbStringToStdString(
envoy_config_endpoint_v3_ClusterLoadAssignment_cluster_name(resource));
auto eds_resource = EdsResourceParse(context, resource);
if (!eds_resource.ok()) {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(ERROR) << "[xds_client " << context.client
<< "] invalid ClusterLoadAssignment " << *result.name << ": "
<< eds_resource.status();
}
result.resource = eds_resource.status();
} else {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(INFO) << "[xds_client " << context.client
<< "] parsed ClusterLoadAssignment " << *result.name << ": "
<< (*eds_resource)->ToString();
}
result.resource = std::move(*eds_resource);
}
return result;
}
} // namespace grpc_core

@ -17,9 +17,6 @@
#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_H
#define GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_H
#include <stdint.h>
#include <algorithm>
#include <map>
#include <string>
#include <utility>
@ -27,17 +24,11 @@
#include "absl/base/thread_annotations.h"
#include "absl/random/random.h"
#include "absl/strings/string_view.h"
#include "envoy/config/endpoint/v3/endpoint.upbdefs.h"
#include "upb/reflection/def.h"
#include <grpc/support/port_platform.h>
#include "src/core/lib/gprpp/ref_counted.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/gprpp/sync.h"
#include "src/core/resolver/endpoint_addresses.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/core/xds/xds_client/xds_client_stats.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
#include "src/core/xds/xds_client/xds_resource_type_impl.h"
@ -130,21 +121,6 @@ struct XdsEndpointResource : public XdsResourceType::ResourceData {
std::string ToString() const;
};
class XdsEndpointResourceType final
: public XdsResourceTypeImpl<XdsEndpointResourceType, XdsEndpointResource> {
public:
absl::string_view type_url() const override {
return "envoy.config.endpoint.v3.ClusterLoadAssignment";
}
DecodeResult Decode(const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const override;
void InitUpbSymtab(XdsClient*, upb_DefPool* symtab) const override {
envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef(symtab);
}
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_H

@ -0,0 +1,433 @@
//
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "src/core/xds/grpc/xds_endpoint_parser.h"
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <set>
#include <vector>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/types/optional.h"
#include "envoy/config/core/v3/address.upb.h"
#include "envoy/config/core/v3/base.upb.h"
#include "envoy/config/endpoint/v3/endpoint.upb.h"
#include "envoy/config/endpoint/v3/endpoint.upbdefs.h"
#include "envoy/config/endpoint/v3/endpoint_components.upb.h"
#include "envoy/type/v3/percent.upb.h"
#include "google/protobuf/wrappers.upb.h"
#include "upb/text/encode.h"
#include <grpc/support/port_platform.h>
#include "src/core/lib/address_utils/parse_address.h"
#include "src/core/lib/address_utils/sockaddr_utils.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/gprpp/env.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/lib/iomgr/resolved_address.h"
#include "src/core/util/string.h"
#include "src/core/util/upb_utils.h"
#include "src/core/xds/grpc/xds_health_status.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
// IWYU pragma: no_include "absl/meta/type_traits.h"
namespace grpc_core {
namespace {
// TODO(roth): Remove this once dualstack support is stable.
bool XdsDualstackEndpointsEnabled() {
auto value = GetEnv("GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS");
if (!value.has_value()) return false;
bool parsed_value;
bool parse_succeeded = gpr_parse_bool_value(value->c_str(), &parsed_value);
return parse_succeeded && parsed_value;
}
void MaybeLogClusterLoadAssignment(
const XdsResourceType::DecodeContext& context,
const envoy_config_endpoint_v3_ClusterLoadAssignment* cla) {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) {
const upb_MessageDef* msg_type =
envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef(
context.symtab);
char buf[10240];
upb_TextEncode(reinterpret_cast<const upb_Message*>(cla), msg_type, nullptr,
0, buf, sizeof(buf));
VLOG(2) << "[xds_client " << context.client
<< "] ClusterLoadAssignment: " << buf;
}
}
absl::optional<grpc_resolved_address> ParseCoreAddress(
const envoy_config_core_v3_Address* address, ValidationErrors* errors) {
if (address == nullptr) {
errors->AddError("field not present");
return absl::nullopt;
}
ValidationErrors::ScopedField field(errors, ".socket_address");
const envoy_config_core_v3_SocketAddress* socket_address =
envoy_config_core_v3_Address_socket_address(address);
if (socket_address == nullptr) {
errors->AddError("field not present");
return absl::nullopt;
}
std::string address_str = UpbStringToStdString(
envoy_config_core_v3_SocketAddress_address(socket_address));
uint32_t port;
{
ValidationErrors::ScopedField field(errors, ".port_value");
port = envoy_config_core_v3_SocketAddress_port_value(socket_address);
if (GPR_UNLIKELY(port >> 16) != 0) {
errors->AddError("invalid port");
return absl::nullopt;
}
}
auto addr = StringToSockaddr(address_str, port);
if (!addr.ok()) {
errors->AddError(addr.status().message());
return absl::nullopt;
}
return *addr;
}
absl::optional<EndpointAddresses> EndpointAddressesParse(
const envoy_config_endpoint_v3_LbEndpoint* lb_endpoint,
ValidationErrors* errors) {
// health_status
const int32_t health_status =
envoy_config_endpoint_v3_LbEndpoint_health_status(lb_endpoint);
auto status = XdsHealthStatus::FromUpb(health_status);
if (!status.has_value()) return absl::nullopt;
// load_balancing_weight
uint32_t weight = 1;
{
ValidationErrors::ScopedField field(errors, ".load_balancing_weight");
const google_protobuf_UInt32Value* load_balancing_weight =
envoy_config_endpoint_v3_LbEndpoint_load_balancing_weight(lb_endpoint);
if (load_balancing_weight != nullptr) {
weight = google_protobuf_UInt32Value_value(load_balancing_weight);
if (weight == 0) {
errors->AddError("must be greater than 0");
}
}
}
// endpoint
std::vector<grpc_resolved_address> addresses;
{
ValidationErrors::ScopedField field(errors, ".endpoint");
const envoy_config_endpoint_v3_Endpoint* endpoint =
envoy_config_endpoint_v3_LbEndpoint_endpoint(lb_endpoint);
if (endpoint == nullptr) {
errors->AddError("field not present");
return absl::nullopt;
}
{
ValidationErrors::ScopedField field(errors, ".address");
auto address = ParseCoreAddress(
envoy_config_endpoint_v3_Endpoint_address(endpoint), errors);
if (address.has_value()) addresses.push_back(*address);
}
if (XdsDualstackEndpointsEnabled()) {
size_t size;
auto* additional_addresses =
envoy_config_endpoint_v3_Endpoint_additional_addresses(endpoint,
&size);
for (size_t i = 0; i < size; ++i) {
ValidationErrors::ScopedField field(
errors, absl::StrCat(".additional_addresses[", i, "].address"));
auto address = ParseCoreAddress(
envoy_config_endpoint_v3_Endpoint_AdditionalAddress_address(
additional_addresses[i]),
errors);
if (address.has_value()) addresses.push_back(*address);
}
}
}
if (addresses.empty()) return absl::nullopt;
// Convert to EndpointAddresses.
return EndpointAddresses(
addresses, ChannelArgs()
.Set(GRPC_ARG_ADDRESS_WEIGHT, weight)
.Set(GRPC_ARG_XDS_HEALTH_STATUS, status->status()));
}
struct ParsedLocality {
size_t priority;
XdsEndpointResource::Priority::Locality locality;
};
struct ResolvedAddressLessThan {
bool operator()(const grpc_resolved_address& a1,
const grpc_resolved_address& a2) const {
if (a1.len != a2.len) return a1.len < a2.len;
return memcmp(a1.addr, a2.addr, a1.len) < 0;
}
};
using ResolvedAddressSet =
std::set<grpc_resolved_address, ResolvedAddressLessThan>;
absl::optional<ParsedLocality> LocalityParse(
const envoy_config_endpoint_v3_LocalityLbEndpoints* locality_lb_endpoints,
ResolvedAddressSet* address_set, ValidationErrors* errors) {
const size_t original_error_size = errors->size();
ParsedLocality parsed_locality;
// load_balancing_weight
// If LB weight is not specified or 0, it means this locality is assigned
// no load.
const google_protobuf_UInt32Value* lb_weight =
envoy_config_endpoint_v3_LocalityLbEndpoints_load_balancing_weight(
locality_lb_endpoints);
parsed_locality.locality.lb_weight =
lb_weight != nullptr ? google_protobuf_UInt32Value_value(lb_weight) : 0;
if (parsed_locality.locality.lb_weight == 0) return absl::nullopt;
// locality
const envoy_config_core_v3_Locality* locality =
envoy_config_endpoint_v3_LocalityLbEndpoints_locality(
locality_lb_endpoints);
if (locality == nullptr) {
ValidationErrors::ScopedField field(errors, ".locality");
errors->AddError("field not present");
return absl::nullopt;
}
// region
std::string region =
UpbStringToStdString(envoy_config_core_v3_Locality_region(locality));
// zone
std::string zone =
UpbStringToStdString(envoy_config_core_v3_Locality_zone(locality));
// sub_zone
std::string sub_zone =
UpbStringToStdString(envoy_config_core_v3_Locality_sub_zone(locality));
parsed_locality.locality.name = MakeRefCounted<XdsLocalityName>(
std::move(region), std::move(zone), std::move(sub_zone));
// lb_endpoints
size_t size;
const envoy_config_endpoint_v3_LbEndpoint* const* lb_endpoints =
envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints(
locality_lb_endpoints, &size);
for (size_t i = 0; i < size; ++i) {
ValidationErrors::ScopedField field(errors,
absl::StrCat(".lb_endpoints[", i, "]"));
auto endpoint = EndpointAddressesParse(lb_endpoints[i], errors);
if (endpoint.has_value()) {
for (const auto& address : endpoint->addresses()) {
bool inserted = address_set->insert(address).second;
if (!inserted) {
errors->AddError(absl::StrCat(
"duplicate endpoint address \"",
grpc_sockaddr_to_uri(&address).value_or("<unknown>"), "\""));
}
}
parsed_locality.locality.endpoints.push_back(std::move(*endpoint));
}
}
// priority
parsed_locality.priority =
envoy_config_endpoint_v3_LocalityLbEndpoints_priority(
locality_lb_endpoints);
// Return result.
if (original_error_size != errors->size()) return absl::nullopt;
return parsed_locality;
}
void DropParseAndAppend(
const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload*
drop_overload,
XdsEndpointResource::DropConfig* drop_config, ValidationErrors* errors) {
// category
std::string category = UpbStringToStdString(
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_category(
drop_overload));
if (category.empty()) {
ValidationErrors::ScopedField field(errors, ".category");
errors->AddError("empty drop category name");
}
// drop_percentage
uint32_t numerator;
{
ValidationErrors::ScopedField field(errors, ".drop_percentage");
const envoy_type_v3_FractionalPercent* drop_percentage =
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_drop_percentage(
drop_overload);
if (drop_percentage == nullptr) {
errors->AddError("field not present");
return;
}
numerator = envoy_type_v3_FractionalPercent_numerator(drop_percentage);
{
ValidationErrors::ScopedField field(errors, ".denominator");
const int denominator =
envoy_type_v3_FractionalPercent_denominator(drop_percentage);
// Normalize to million.
switch (denominator) {
case envoy_type_v3_FractionalPercent_HUNDRED:
numerator *= 10000;
break;
case envoy_type_v3_FractionalPercent_TEN_THOUSAND:
numerator *= 100;
break;
case envoy_type_v3_FractionalPercent_MILLION:
break;
default:
errors->AddError("unknown denominator type");
}
}
// Cap numerator to 1000000.
numerator = std::min(numerator, 1000000u);
}
// Add category.
drop_config->AddCategory(std::move(category), numerator);
}
absl::StatusOr<std::shared_ptr<const XdsEndpointResource>> EdsResourceParse(
const XdsResourceType::DecodeContext& /*context*/,
const envoy_config_endpoint_v3_ClusterLoadAssignment*
cluster_load_assignment) {
ValidationErrors errors;
auto eds_resource = std::make_shared<XdsEndpointResource>();
// endpoints
{
ValidationErrors::ScopedField field(&errors, "endpoints");
ResolvedAddressSet address_set;
size_t locality_size;
const envoy_config_endpoint_v3_LocalityLbEndpoints* const* endpoints =
envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints(
cluster_load_assignment, &locality_size);
for (size_t i = 0; i < locality_size; ++i) {
ValidationErrors::ScopedField field(&errors, absl::StrCat("[", i, "]"));
auto parsed_locality = LocalityParse(endpoints[i], &address_set, &errors);
if (parsed_locality.has_value()) {
CHECK_NE(parsed_locality->locality.lb_weight, 0u);
// Make sure prorities is big enough. Note that they might not
// arrive in priority order.
if (eds_resource->priorities.size() < parsed_locality->priority + 1) {
eds_resource->priorities.resize(parsed_locality->priority + 1);
}
auto& locality_map =
eds_resource->priorities[parsed_locality->priority].localities;
auto it = locality_map.find(parsed_locality->locality.name.get());
if (it != locality_map.end()) {
errors.AddError(absl::StrCat(
"duplicate locality ",
parsed_locality->locality.name->human_readable_string()
.as_string_view(),
" found in priority ", parsed_locality->priority));
} else {
locality_map.emplace(parsed_locality->locality.name.get(),
std::move(parsed_locality->locality));
}
}
}
for (size_t i = 0; i < eds_resource->priorities.size(); ++i) {
const auto& priority = eds_resource->priorities[i];
if (priority.localities.empty()) {
errors.AddError(absl::StrCat("priority ", i, " empty"));
} else {
// Check that the sum of the locality weights in this priority
// does not exceed the max value for a uint32.
uint64_t total_weight = 0;
for (const auto& p : priority.localities) {
total_weight += p.second.lb_weight;
if (total_weight > std::numeric_limits<uint32_t>::max()) {
errors.AddError(
absl::StrCat("sum of locality weights for priority ", i,
" exceeds uint32 max"));
break;
}
}
}
}
}
// policy
const auto* policy = envoy_config_endpoint_v3_ClusterLoadAssignment_policy(
cluster_load_assignment);
if (policy != nullptr) {
ValidationErrors::ScopedField field(&errors, "policy");
size_t drop_size;
const auto* const* drop_overload =
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_drop_overloads(
policy, &drop_size);
if (drop_size > 0) {
eds_resource->drop_config =
MakeRefCounted<XdsEndpointResource::DropConfig>();
}
for (size_t i = 0; i < drop_size; ++i) {
ValidationErrors::ScopedField field(
&errors, absl::StrCat(".drop_overloads[", i, "]"));
DropParseAndAppend(drop_overload[i], eds_resource->drop_config.get(),
&errors);
}
}
// Return result.
if (!errors.ok()) {
return errors.status(absl::StatusCode::kInvalidArgument,
"errors parsing EDS resource");
}
return eds_resource;
}
} // namespace
XdsResourceType::DecodeResult XdsEndpointResourceType::Decode(
const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const {
DecodeResult result;
// Parse serialized proto.
auto* resource = envoy_config_endpoint_v3_ClusterLoadAssignment_parse(
serialized_resource.data(), serialized_resource.size(), context.arena);
if (resource == nullptr) {
result.resource = absl::InvalidArgumentError(
"Can't parse ClusterLoadAssignment resource.");
return result;
}
MaybeLogClusterLoadAssignment(context, resource);
// Validate resource.
result.name = UpbStringToStdString(
envoy_config_endpoint_v3_ClusterLoadAssignment_cluster_name(resource));
auto eds_resource = EdsResourceParse(context, resource);
if (!eds_resource.ok()) {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(ERROR) << "[xds_client " << context.client
<< "] invalid ClusterLoadAssignment " << *result.name << ": "
<< eds_resource.status();
}
result.resource = eds_resource.status();
} else {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(INFO) << "[xds_client " << context.client
<< "] parsed ClusterLoadAssignment " << *result.name << ": "
<< (*eds_resource)->ToString();
}
result.resource = std::move(*eds_resource);
}
return result;
}
} // namespace grpc_core

@ -0,0 +1,48 @@
//
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_PARSER_H
#define GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_PARSER_H
#include "absl/strings/string_view.h"
#include "envoy/config/endpoint/v3/endpoint.upbdefs.h"
#include "upb/reflection/def.h"
#include "src/core/xds/grpc/xds_endpoint.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
#include "src/core/xds/xds_client/xds_resource_type_impl.h"
namespace grpc_core {
class XdsEndpointResourceType final
: public XdsResourceTypeImpl<XdsEndpointResourceType, XdsEndpointResource> {
public:
absl::string_view type_url() const override {
return "envoy.config.endpoint.v3.ClusterLoadAssignment";
}
DecodeResult Decode(const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const override;
void InitUpbSymtab(XdsClient*, upb_DefPool* symtab) const override {
envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef(symtab);
}
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_PARSER_H

@ -22,8 +22,6 @@
#include "absl/strings/str_join.h"
#include "envoy/config/core/v3/health_check.upb.h"
#include <grpc/support/port_platform.h>
namespace grpc_core {
absl::optional<XdsHealthStatus> XdsHealthStatus::FromUpb(uint32_t status) {

@ -23,8 +23,6 @@
#include "absl/types/optional.h"
#include "absl/types/span.h"
#include <grpc/support/port_platform.h>
#include "src/core/resolver/endpoint_addresses.h"
// Channel arg key for xDS health status.

@ -45,7 +45,8 @@
#include "src/core/util/json/json.h"
#include "src/core/util/json/json_writer.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/grpc/xds_common_types_parser.h"
#include "src/core/xds/grpc/xds_http_filter.h"
namespace grpc_core {

@ -28,7 +28,7 @@
#include "src/core/lib/channel/channel_fwd.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/grpc/xds_http_filter.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
namespace grpc_core {

@ -14,24 +14,17 @@
// limitations under the License.
//
#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTERS_H
#define GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTERS_H
#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_H
#define GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_H
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "upb/reflection/def.h"
#include <grpc/support/port_platform.h>
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/channel/channel_fwd.h"
#include "src/core/lib/gprpp/validation_errors.h"
@ -127,60 +120,6 @@ class XdsHttpFilterImpl {
virtual bool IsTerminalFilter() const { return false; }
};
class XdsHttpRouterFilter final : public XdsHttpFilterImpl {
public:
absl::string_view ConfigProtoName() const override;
absl::string_view OverrideConfigProtoName() const override;
void PopulateSymtab(upb_DefPool* symtab) const override;
absl::optional<FilterConfig> GenerateFilterConfig(
const XdsResourceType::DecodeContext& context, XdsExtension extension,
ValidationErrors* errors) const override;
absl::optional<FilterConfig> GenerateFilterConfigOverride(
const XdsResourceType::DecodeContext& context, XdsExtension extension,
ValidationErrors* errors) const override;
void AddFilter(InterceptionChainBuilder& /*builder*/) const override {}
const grpc_channel_filter* channel_filter() const override { return nullptr; }
absl::StatusOr<ServiceConfigJsonEntry> GenerateServiceConfig(
const FilterConfig& /*hcm_filter_config*/,
const FilterConfig* /*filter_config_override*/) const override {
// This will never be called, since channel_filter() returns null.
return absl::UnimplementedError("router filter should never be called");
}
bool IsSupportedOnClients() const override { return true; }
bool IsSupportedOnServers() const override { return true; }
bool IsTerminalFilter() const override { return true; }
};
class XdsHttpFilterRegistry final {
public:
explicit XdsHttpFilterRegistry(bool register_builtins = true);
// Not copyable.
XdsHttpFilterRegistry(const XdsHttpFilterRegistry&) = delete;
XdsHttpFilterRegistry& operator=(const XdsHttpFilterRegistry&) = delete;
// Movable.
XdsHttpFilterRegistry(XdsHttpFilterRegistry&& other) noexcept
: owning_list_(std::move(other.owning_list_)),
registry_map_(std::move(other.registry_map_)) {}
XdsHttpFilterRegistry& operator=(XdsHttpFilterRegistry&& other) noexcept {
owning_list_ = std::move(other.owning_list_);
registry_map_ = std::move(other.registry_map_);
return *this;
}
void RegisterFilter(std::unique_ptr<XdsHttpFilterImpl> filter);
const XdsHttpFilterImpl* GetFilterForType(
absl::string_view proto_type_name) const;
void PopulateSymtab(upb_DefPool* symtab) const;
private:
std::vector<std::unique_ptr<XdsHttpFilterImpl>> owning_list_;
std::map<absl::string_view, XdsHttpFilterImpl*> registry_map_;
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTERS_H
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_H

@ -14,7 +14,7 @@
// limitations under the License.
//
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/grpc/xds_http_filter_registry.h"
#include <map>
#include <utility>
@ -28,6 +28,7 @@
#include <grpc/support/log.h>
#include <grpc/support/port_platform.h>
#include "src/core/util/json/json.h"
#include "src/core/xds/grpc/xds_http_fault_filter.h"
#include "src/core/xds/grpc/xds_http_rbac_filter.h"
#include "src/core/xds/grpc/xds_http_stateful_session_filter.h"

@ -0,0 +1,98 @@
//
// Copyright 2021 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_REGISTRY_H
#define GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_REGISTRY_H
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "upb/reflection/def.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/channel/channel_fwd.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/lib/transport/interception_chain.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_http_filter.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
namespace grpc_core {
// Exposed for testing purposes only.
class XdsHttpRouterFilter final : public XdsHttpFilterImpl {
public:
absl::string_view ConfigProtoName() const override;
absl::string_view OverrideConfigProtoName() const override;
void PopulateSymtab(upb_DefPool* symtab) const override;
absl::optional<FilterConfig> GenerateFilterConfig(
const XdsResourceType::DecodeContext& context, XdsExtension extension,
ValidationErrors* errors) const override;
absl::optional<FilterConfig> GenerateFilterConfigOverride(
const XdsResourceType::DecodeContext& context, XdsExtension extension,
ValidationErrors* errors) const override;
void AddFilter(InterceptionChainBuilder& /*builder*/) const override {}
const grpc_channel_filter* channel_filter() const override { return nullptr; }
absl::StatusOr<ServiceConfigJsonEntry> GenerateServiceConfig(
const FilterConfig& /*hcm_filter_config*/,
const FilterConfig* /*filter_config_override*/) const override {
// This will never be called, since channel_filter() returns null.
return absl::UnimplementedError("router filter should never be called");
}
bool IsSupportedOnClients() const override { return true; }
bool IsSupportedOnServers() const override { return true; }
bool IsTerminalFilter() const override { return true; }
};
class XdsHttpFilterRegistry final {
public:
explicit XdsHttpFilterRegistry(bool register_builtins = true);
// Not copyable.
XdsHttpFilterRegistry(const XdsHttpFilterRegistry&) = delete;
XdsHttpFilterRegistry& operator=(const XdsHttpFilterRegistry&) = delete;
// Movable.
XdsHttpFilterRegistry(XdsHttpFilterRegistry&& other) noexcept
: owning_list_(std::move(other.owning_list_)),
registry_map_(std::move(other.registry_map_)) {}
XdsHttpFilterRegistry& operator=(XdsHttpFilterRegistry&& other) noexcept {
owning_list_ = std::move(other.owning_list_);
registry_map_ = std::move(other.registry_map_);
return *this;
}
void RegisterFilter(std::unique_ptr<XdsHttpFilterImpl> filter);
const XdsHttpFilterImpl* GetFilterForType(
absl::string_view proto_type_name) const;
void PopulateSymtab(upb_DefPool* symtab) const;
private:
std::vector<std::unique_ptr<XdsHttpFilterImpl>> owning_list_;
std::map<absl::string_view, XdsHttpFilterImpl*> registry_map_;
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_REGISTRY_H

@ -28,7 +28,7 @@
#include "src/core/lib/channel/channel_fwd.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/grpc/xds_http_filter.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
namespace grpc_core {

@ -41,7 +41,8 @@
#include "src/core/util/json/json_writer.h"
#include "src/core/util/upb_utils.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/grpc/xds_common_types_parser.h"
#include "src/core/xds/grpc/xds_http_filter.h"
namespace grpc_core {

@ -28,7 +28,7 @@
#include "src/core/lib/channel/channel_fwd.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/grpc/xds_http_filter.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
namespace grpc_core {

@ -40,6 +40,7 @@
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/load_balancing/lb_policy_registry.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_common_types_parser.h"
namespace grpc_core {

File diff suppressed because it is too large Load Diff

@ -27,22 +27,14 @@
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "envoy/config/listener/v3/listener.upbdefs.h"
#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h"
#include "upb/reflection/def.h"
#include <grpc/support/port_platform.h>
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/iomgr/resolved_address.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/grpc/xds_http_filter.h"
#include "src/core/xds/grpc/xds_route_config.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
#include "src/core/xds/xds_client/xds_resource_type_impl.h"
@ -207,30 +199,6 @@ struct XdsListenerResource : public XdsResourceType::ResourceData {
std::string ToString() const;
};
class XdsListenerResourceType final
: public XdsResourceTypeImpl<XdsListenerResourceType, XdsListenerResource> {
public:
absl::string_view type_url() const override {
return "envoy.config.listener.v3.Listener";
}
DecodeResult Decode(const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const override;
bool AllResourcesRequiredInSotW() const override { return true; }
void InitUpbSymtab(XdsClient* xds_client,
upb_DefPool* symtab) const override {
envoy_config_listener_v3_Listener_getmsgdef(symtab);
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef(
symtab);
const auto& http_filter_registry =
static_cast<const GrpcXdsBootstrap&>(xds_client->bootstrap())
.http_filter_registry();
http_filter_registry.PopulateSymtab(symtab);
}
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_LISTENER_H

@ -0,0 +1,991 @@
//
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "src/core/xds/grpc/xds_listener_parser.h"
#include <stdint.h>
#include <set>
#include <utility>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "envoy/config/core/v3/address.upb.h"
#include "envoy/config/core/v3/base.upb.h"
#include "envoy/config/core/v3/config_source.upb.h"
#include "envoy/config/core/v3/protocol.upb.h"
#include "envoy/config/listener/v3/api_listener.upb.h"
#include "envoy/config/listener/v3/listener.upb.h"
#include "envoy/config/listener/v3/listener.upbdefs.h"
#include "envoy/config/listener/v3/listener_components.upb.h"
#include "envoy/config/route/v3/route.upb.h"
#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upb.h"
#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h"
#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h"
#include "google/protobuf/any.upb.h"
#include "google/protobuf/duration.upb.h"
#include "google/protobuf/wrappers.upb.h"
#include "upb/text/encode.h"
#include <grpc/support/port_platform.h>
#include "src/core/lib/address_utils/parse_address.h"
#include "src/core/lib/address_utils/sockaddr_utils.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/gprpp/host_port.h"
#include "src/core/lib/gprpp/match.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/lib/iomgr/sockaddr.h"
#include "src/core/lib/matchers/matchers.h"
#include "src/core/util/upb_utils.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_common_types_parser.h"
#include "src/core/xds/grpc/xds_route_config_parser.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
namespace grpc_core {
namespace {
struct FilterChain {
struct FilterChainMatch {
uint32_t destination_port = 0;
std::vector<XdsListenerResource::FilterChainMap::CidrRange> prefix_ranges;
XdsListenerResource::FilterChainMap::ConnectionSourceType source_type =
XdsListenerResource::FilterChainMap::ConnectionSourceType::kAny;
std::vector<XdsListenerResource::FilterChainMap::CidrRange>
source_prefix_ranges;
std::vector<uint32_t> source_ports;
std::vector<std::string> server_names;
std::string transport_protocol;
std::vector<std::string> application_protocols;
std::string ToString() const;
} filter_chain_match;
std::shared_ptr<XdsListenerResource::FilterChainData> filter_chain_data;
};
std::string FilterChain::FilterChainMatch::ToString() const {
std::vector<std::string> contents;
if (destination_port != 0) {
contents.push_back(absl::StrCat("destination_port=", destination_port));
}
if (!prefix_ranges.empty()) {
std::vector<std::string> prefix_ranges_content;
prefix_ranges_content.reserve(prefix_ranges.size());
for (const auto& range : prefix_ranges) {
prefix_ranges_content.push_back(range.ToString());
}
contents.push_back(absl::StrCat(
"prefix_ranges={", absl::StrJoin(prefix_ranges_content, ", "), "}"));
}
if (source_type == XdsListenerResource::FilterChainMap::ConnectionSourceType::
kSameIpOrLoopback) {
contents.push_back("source_type=SAME_IP_OR_LOOPBACK");
} else if (source_type == XdsListenerResource::FilterChainMap::
ConnectionSourceType::kExternal) {
contents.push_back("source_type=EXTERNAL");
}
if (!source_prefix_ranges.empty()) {
std::vector<std::string> source_prefix_ranges_content;
source_prefix_ranges_content.reserve(source_prefix_ranges.size());
for (const auto& range : source_prefix_ranges) {
source_prefix_ranges_content.push_back(range.ToString());
}
contents.push_back(
absl::StrCat("source_prefix_ranges={",
absl::StrJoin(source_prefix_ranges_content, ", "), "}"));
}
if (!source_ports.empty()) {
contents.push_back(
absl::StrCat("source_ports={", absl::StrJoin(source_ports, ", "), "}"));
}
if (!server_names.empty()) {
contents.push_back(
absl::StrCat("server_names={", absl::StrJoin(server_names, ", "), "}"));
}
if (!transport_protocol.empty()) {
contents.push_back(absl::StrCat("transport_protocol=", transport_protocol));
}
if (!application_protocols.empty()) {
contents.push_back(absl::StrCat("application_protocols={",
absl::StrJoin(application_protocols, ", "),
"}"));
}
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}");
}
void MaybeLogHttpConnectionManager(
const XdsResourceType::DecodeContext& context,
const envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager*
http_connection_manager_config) {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) {
const upb_MessageDef* msg_type =
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef(
context.symtab);
char buf[10240];
upb_TextEncode(
reinterpret_cast<const upb_Message*>(http_connection_manager_config),
msg_type, nullptr, 0, buf, sizeof(buf));
VLOG(2) << "[xds_client " << context.client
<< "] HttpConnectionManager: " << buf;
}
}
XdsListenerResource::HttpConnectionManager HttpConnectionManagerParse(
bool is_client, const XdsResourceType::DecodeContext& context,
XdsExtension extension, ValidationErrors* errors) {
if (extension.type !=
"envoy.extensions.filters.network.http_connection_manager.v3"
".HttpConnectionManager") {
errors->AddError("unsupported filter type");
return {};
}
auto* serialized_hcm_config =
absl::get_if<absl::string_view>(&extension.value);
if (serialized_hcm_config == nullptr) {
errors->AddError("could not parse HttpConnectionManager config");
return {};
}
const auto* http_connection_manager_proto =
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_parse(
serialized_hcm_config->data(), serialized_hcm_config->size(),
context.arena);
if (http_connection_manager_proto == nullptr) {
errors->AddError("could not parse HttpConnectionManager config");
return {};
}
MaybeLogHttpConnectionManager(context, http_connection_manager_proto);
XdsListenerResource::HttpConnectionManager http_connection_manager;
// xff_num_trusted_hops -- must be zero as per
// https://github.com/grpc/proposal/blob/master/A41-xds-rbac.md
if (envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_xff_num_trusted_hops(
http_connection_manager_proto) != 0) {
ValidationErrors::ScopedField field(errors, ".xff_num_trusted_hops");
errors->AddError("must be zero");
}
// original_ip_detection_extensions -- must be empty as per
// https://github.com/grpc/proposal/blob/master/A41-xds-rbac.md
{
size_t size;
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_original_ip_detection_extensions(
http_connection_manager_proto, &size);
if (size != 0) {
ValidationErrors::ScopedField field(errors,
".original_ip_detection_extensions");
errors->AddError("must be empty");
}
}
// common_http_protocol_options
const envoy_config_core_v3_HttpProtocolOptions* options =
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_common_http_protocol_options(
http_connection_manager_proto);
if (options != nullptr) {
// max_stream_duration
const google_protobuf_Duration* duration =
envoy_config_core_v3_HttpProtocolOptions_max_stream_duration(options);
if (duration != nullptr) {
ValidationErrors::ScopedField field(
errors, ".common_http_protocol_options.max_stream_duration");
http_connection_manager.http_max_stream_duration =
ParseDuration(duration, errors);
}
}
// http_filters
{
ValidationErrors::ScopedField field(errors, ".http_filters");
const auto& http_filter_registry =
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap())
.http_filter_registry();
size_t num_filters = 0;
const auto* http_filters =
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_http_filters(
http_connection_manager_proto, &num_filters);
std::set<absl::string_view> names_seen;
const size_t original_error_size = errors->size();
for (size_t i = 0; i < num_filters; ++i) {
ValidationErrors::ScopedField field(errors, absl::StrCat("[", i, "]"));
const auto* http_filter = http_filters[i];
// name
absl::string_view name = UpbStringToAbsl(
envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_name(
http_filter));
{
ValidationErrors::ScopedField field(errors, ".name");
if (name.empty()) {
errors->AddError("empty filter name");
continue;
}
if (names_seen.find(name) != names_seen.end()) {
errors->AddError(absl::StrCat("duplicate HTTP filter name: ", name));
continue;
}
}
names_seen.insert(name);
// is_optional
const bool is_optional =
envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_is_optional(
http_filter);
// typed_config
{
ValidationErrors::ScopedField field(errors, ".typed_config");
const google_protobuf_Any* typed_config =
envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_typed_config(
http_filter);
auto extension = ExtractXdsExtension(context, typed_config, errors);
if (!extension.has_value()) continue;
const XdsHttpFilterImpl* filter_impl =
http_filter_registry.GetFilterForType(extension->type);
if (filter_impl == nullptr) {
if (!is_optional) errors->AddError("unsupported filter type");
continue;
}
if ((is_client && !filter_impl->IsSupportedOnClients()) ||
(!is_client && !filter_impl->IsSupportedOnServers())) {
if (!is_optional) {
errors->AddError(absl::StrCat("filter is not supported on ",
is_client ? "clients" : "servers"));
}
continue;
}
absl::optional<XdsHttpFilterImpl::FilterConfig> filter_config =
filter_impl->GenerateFilterConfig(context, std::move(*extension),
errors);
if (filter_config.has_value()) {
http_connection_manager.http_filters.emplace_back(
XdsListenerResource::HttpConnectionManager::HttpFilter{
std::string(name), std::move(*filter_config)});
}
}
}
if (errors->size() == original_error_size &&
http_connection_manager.http_filters.empty()) {
errors->AddError("expected at least one HTTP filter");
}
// Make sure that the last filter is terminal and non-last filters are
// non-terminal. Note that this check is being performed in a separate loop
// to take care of the case where there are two terminal filters in the list
// out of which only one gets added in the final list.
for (const auto& http_filter : http_connection_manager.http_filters) {
const XdsHttpFilterImpl* filter_impl =
http_filter_registry.GetFilterForType(
http_filter.config.config_proto_type_name);
if (&http_filter != &http_connection_manager.http_filters.back()) {
// Filters before the last filter must not be terminal.
if (filter_impl->IsTerminalFilter()) {
errors->AddError(
absl::StrCat("terminal filter for config type ",
http_filter.config.config_proto_type_name,
" must be the last filter in the chain"));
}
} else {
// The last filter must be terminal.
if (!filter_impl->IsTerminalFilter()) {
errors->AddError(
absl::StrCat("non-terminal filter for config type ",
http_filter.config.config_proto_type_name,
" is the last filter in the chain"));
}
}
}
}
// Found inlined route_config. Parse it to find the cluster_name.
if (envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_has_route_config(
http_connection_manager_proto)) {
const envoy_config_route_v3_RouteConfiguration* route_config =
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_route_config(
http_connection_manager_proto);
ValidationErrors::ScopedField field(errors, ".route_config");
http_connection_manager.route_config =
XdsRouteConfigResourceParse(context, route_config, errors);
} else {
// Validate that RDS must be used to get the route_config dynamically.
const envoy_extensions_filters_network_http_connection_manager_v3_Rds* rds =
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_rds(
http_connection_manager_proto);
if (rds == nullptr) {
errors->AddError("neither route_config nor rds fields are present");
} else {
// Get the route_config_name.
http_connection_manager.route_config = UpbStringToStdString(
envoy_extensions_filters_network_http_connection_manager_v3_Rds_route_config_name(
rds));
// Check that the ConfigSource specifies ADS.
const envoy_config_core_v3_ConfigSource* config_source =
envoy_extensions_filters_network_http_connection_manager_v3_Rds_config_source(
rds);
ValidationErrors::ScopedField field(errors, ".rds.config_source");
if (config_source == nullptr) {
errors->AddError("field not present");
} else if (!envoy_config_core_v3_ConfigSource_has_ads(config_source) &&
!envoy_config_core_v3_ConfigSource_has_self(config_source)) {
errors->AddError("ConfigSource does not specify ADS or SELF");
}
}
}
return http_connection_manager;
}
absl::StatusOr<std::shared_ptr<const XdsListenerResource>>
LdsResourceParseClient(
const XdsResourceType::DecodeContext& context,
const envoy_config_listener_v3_ApiListener* api_listener) {
auto lds_update = std::make_shared<XdsListenerResource>();
ValidationErrors errors;
ValidationErrors::ScopedField field(&errors, "api_listener.api_listener");
auto* api_listener_field =
envoy_config_listener_v3_ApiListener_api_listener(api_listener);
auto extension = ExtractXdsExtension(context, api_listener_field, &errors);
if (extension.has_value()) {
lds_update->listener = HttpConnectionManagerParse(
/*is_client=*/true, context, std::move(*extension), &errors);
}
if (!errors.ok()) {
return errors.status(absl::StatusCode::kInvalidArgument,
"errors validating ApiListener");
}
return std::move(lds_update);
}
XdsListenerResource::DownstreamTlsContext DownstreamTlsContextParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_core_v3_TransportSocket* transport_socket,
ValidationErrors* errors) {
ValidationErrors::ScopedField field(errors, ".typed_config");
const auto* typed_config =
envoy_config_core_v3_TransportSocket_typed_config(transport_socket);
auto extension = ExtractXdsExtension(context, typed_config, errors);
if (!extension.has_value()) return {};
if (extension->type !=
"envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext") {
ValidationErrors::ScopedField field(errors, ".type_url");
errors->AddError("unsupported transport socket type");
return {};
}
absl::string_view* serialized_downstream_tls_context =
absl::get_if<absl::string_view>(&extension->value);
if (serialized_downstream_tls_context == nullptr) {
errors->AddError("can't decode DownstreamTlsContext");
return {};
}
const auto* downstream_tls_context_proto =
envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_parse(
serialized_downstream_tls_context->data(),
serialized_downstream_tls_context->size(), context.arena);
if (downstream_tls_context_proto == nullptr) {
errors->AddError("can't decode DownstreamTlsContext");
return {};
}
XdsListenerResource::DownstreamTlsContext downstream_tls_context;
auto* common_tls_context =
envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_common_tls_context(
downstream_tls_context_proto);
if (common_tls_context != nullptr) {
ValidationErrors::ScopedField field(errors, ".common_tls_context");
downstream_tls_context.common_tls_context =
CommonTlsContextParse(context, common_tls_context, errors);
// Note: We can't be more specific about the field name for this
// error, because we don't know which fields they were found in
// inside of CommonTlsContext, so we make the error message a bit
// more verbose to compensate.
if (!downstream_tls_context.common_tls_context
.certificate_validation_context.match_subject_alt_names.empty()) {
errors->AddError("match_subject_alt_names not supported on servers");
}
}
// Note: We can't be more specific about the field name for this
// error, because we don't know which fields they were found in
// inside of CommonTlsContext, so we make the error message a bit
// more verbose to compensate.
if (downstream_tls_context.common_tls_context
.tls_certificate_provider_instance.instance_name.empty()) {
errors->AddError(
"TLS configuration provided but no "
"tls_certificate_provider_instance found");
}
auto* require_client_certificate =
envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_require_client_certificate(
downstream_tls_context_proto);
if (require_client_certificate != nullptr) {
downstream_tls_context.require_client_certificate =
google_protobuf_BoolValue_value(require_client_certificate);
if (downstream_tls_context.require_client_certificate &&
downstream_tls_context.common_tls_context.certificate_validation_context
.ca_certificate_provider_instance.instance_name.empty()) {
ValidationErrors::ScopedField field(errors,
".require_client_certificate");
errors->AddError(
"client certificate required but no certificate "
"provider instance specified for validation");
}
}
auto* require_sni =
envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_require_sni(
downstream_tls_context_proto);
if (require_sni != nullptr && google_protobuf_BoolValue_value(require_sni)) {
ValidationErrors::ScopedField field(errors, ".require_sni");
errors->AddError("field unsupported");
}
if (envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_ocsp_staple_policy(
downstream_tls_context_proto) !=
envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_LENIENT_STAPLING) {
ValidationErrors::ScopedField field(errors, ".ocsp_staple_policy");
errors->AddError("value must be LENIENT_STAPLING");
}
return downstream_tls_context;
}
absl::optional<XdsListenerResource::FilterChainMap::CidrRange> CidrRangeParse(
const envoy_config_core_v3_CidrRange* cidr_range_proto,
ValidationErrors* errors) {
ValidationErrors::ScopedField field(errors, ".address_prefix");
XdsListenerResource::FilterChainMap::CidrRange cidr_range;
std::string address_prefix = UpbStringToStdString(
envoy_config_core_v3_CidrRange_address_prefix(cidr_range_proto));
auto address = StringToSockaddr(address_prefix, /*port=*/0);
if (!address.ok()) {
errors->AddError(address.status().message());
return absl::nullopt;
}
cidr_range.address = *address;
cidr_range.prefix_len = 0;
auto* prefix_len_proto =
envoy_config_core_v3_CidrRange_prefix_len(cidr_range_proto);
if (prefix_len_proto != nullptr) {
cidr_range.prefix_len = std::min(
google_protobuf_UInt32Value_value(prefix_len_proto),
(reinterpret_cast<const grpc_sockaddr*>(cidr_range.address.addr))
->sa_family == GRPC_AF_INET
? uint32_t{32}
: uint32_t{128});
}
// Normalize the network address by masking it with prefix_len
grpc_sockaddr_mask_bits(&cidr_range.address, cidr_range.prefix_len);
return cidr_range;
}
absl::optional<FilterChain::FilterChainMatch> FilterChainMatchParse(
const envoy_config_listener_v3_FilterChainMatch* filter_chain_match_proto,
ValidationErrors* errors) {
FilterChain::FilterChainMatch filter_chain_match;
const size_t original_error_size = errors->size();
// destination_port
auto* destination_port =
envoy_config_listener_v3_FilterChainMatch_destination_port(
filter_chain_match_proto);
if (destination_port != nullptr) {
filter_chain_match.destination_port =
google_protobuf_UInt32Value_value(destination_port);
}
// prefix_ranges
size_t size = 0;
auto* prefix_ranges = envoy_config_listener_v3_FilterChainMatch_prefix_ranges(
filter_chain_match_proto, &size);
filter_chain_match.prefix_ranges.reserve(size);
for (size_t i = 0; i < size; i++) {
ValidationErrors::ScopedField field(
errors, absl::StrCat(".prefix_ranges[", i, "]"));
auto cidr_range = CidrRangeParse(prefix_ranges[i], errors);
if (cidr_range.has_value()) {
filter_chain_match.prefix_ranges.push_back(*cidr_range);
}
}
// source_type
filter_chain_match.source_type =
static_cast<XdsListenerResource::FilterChainMap::ConnectionSourceType>(
envoy_config_listener_v3_FilterChainMatch_source_type(
filter_chain_match_proto));
// source_prefix_ranges
auto* source_prefix_ranges =
envoy_config_listener_v3_FilterChainMatch_source_prefix_ranges(
filter_chain_match_proto, &size);
filter_chain_match.source_prefix_ranges.reserve(size);
for (size_t i = 0; i < size; i++) {
ValidationErrors::ScopedField field(
errors, absl::StrCat(".source_prefix_ranges[", i, "]"));
auto cidr_range = CidrRangeParse(source_prefix_ranges[i], errors);
if (cidr_range.has_value()) {
filter_chain_match.source_prefix_ranges.push_back(*cidr_range);
}
}
// source_ports
auto* source_ports = envoy_config_listener_v3_FilterChainMatch_source_ports(
filter_chain_match_proto, &size);
filter_chain_match.source_ports.reserve(size);
for (size_t i = 0; i < size; i++) {
filter_chain_match.source_ports.push_back(source_ports[i]);
}
// server_names
auto* server_names = envoy_config_listener_v3_FilterChainMatch_server_names(
filter_chain_match_proto, &size);
for (size_t i = 0; i < size; i++) {
filter_chain_match.server_names.push_back(
UpbStringToStdString(server_names[i]));
}
// transport_protocol
filter_chain_match.transport_protocol = UpbStringToStdString(
envoy_config_listener_v3_FilterChainMatch_transport_protocol(
filter_chain_match_proto));
// application_protocols
auto* application_protocols =
envoy_config_listener_v3_FilterChainMatch_application_protocols(
filter_chain_match_proto, &size);
for (size_t i = 0; i < size; i++) {
filter_chain_match.application_protocols.push_back(
UpbStringToStdString(application_protocols[i]));
}
// Return result.
if (errors->size() != original_error_size) return absl::nullopt;
return filter_chain_match;
}
absl::optional<FilterChain> FilterChainParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_listener_v3_FilterChain* filter_chain_proto,
ValidationErrors* errors) {
FilterChain filter_chain;
const size_t original_error_size = errors->size();
// filter_chain_match
auto* filter_chain_match =
envoy_config_listener_v3_FilterChain_filter_chain_match(
filter_chain_proto);
if (filter_chain_match != nullptr) {
ValidationErrors::ScopedField field(errors, ".filter_chain_match");
auto match = FilterChainMatchParse(filter_chain_match, errors);
if (match.has_value()) {
filter_chain.filter_chain_match = std::move(*match);
}
}
// filters
{
ValidationErrors::ScopedField field(errors, ".filters");
filter_chain.filter_chain_data =
std::make_shared<XdsListenerResource::FilterChainData>();
size_t size = 0;
auto* filters =
envoy_config_listener_v3_FilterChain_filters(filter_chain_proto, &size);
if (size != 1) {
errors->AddError(
"must have exactly one filter (HttpConnectionManager -- "
"no other filter is supported at the moment)");
}
// entries in filters list
for (size_t i = 0; i < size; ++i) {
ValidationErrors::ScopedField field(
errors, absl::StrCat("[", i, "].typed_config"));
auto* typed_config =
envoy_config_listener_v3_Filter_typed_config(filters[i]);
auto extension = ExtractXdsExtension(context, typed_config, errors);
if (extension.has_value()) {
filter_chain.filter_chain_data->http_connection_manager =
HttpConnectionManagerParse(/*is_client=*/false, context,
std::move(*extension), errors);
}
}
}
// transport_socket
auto* transport_socket =
envoy_config_listener_v3_FilterChain_transport_socket(filter_chain_proto);
if (transport_socket != nullptr) {
ValidationErrors::ScopedField field(errors, ".transport_socket");
filter_chain.filter_chain_data->downstream_tls_context =
DownstreamTlsContextParse(context, transport_socket, errors);
}
// Return result.
if (errors->size() != original_error_size) return absl::nullopt;
return filter_chain;
}
absl::optional<std::string> AddressParse(
const envoy_config_core_v3_Address* address_proto,
ValidationErrors* errors) {
if (address_proto == nullptr) {
errors->AddError("field not present");
return absl::nullopt;
}
ValidationErrors::ScopedField field(errors, ".socket_address");
const auto* socket_address =
envoy_config_core_v3_Address_socket_address(address_proto);
if (socket_address == nullptr) {
errors->AddError("field not present");
return absl::nullopt;
}
{
ValidationErrors::ScopedField field(errors, ".protocol");
if (envoy_config_core_v3_SocketAddress_protocol(socket_address) !=
envoy_config_core_v3_SocketAddress_TCP) {
errors->AddError("value must be TCP");
}
}
ValidationErrors::ScopedField field2(errors, ".port_value");
uint32_t port = envoy_config_core_v3_SocketAddress_port_value(socket_address);
if (port > 65535) {
errors->AddError("invalid port");
return absl::nullopt;
}
return JoinHostPort(
UpbStringToAbsl(
envoy_config_core_v3_SocketAddress_address(socket_address)),
port);
}
// An intermediate map for filter chains that we create to validate the list of
// filter chains received from the control plane and to finally create
// XdsListenerResource::FilterChainMap
struct InternalFilterChainMap {
using SourceIpMap =
std::map<std::string, XdsListenerResource::FilterChainMap::SourceIp>;
using ConnectionSourceTypesArray = std::array<SourceIpMap, 3>;
struct DestinationIp {
absl::optional<XdsListenerResource::FilterChainMap::CidrRange> prefix_range;
bool transport_protocol_raw_buffer_provided = false;
ConnectionSourceTypesArray source_types_array;
};
using DestinationIpMap = std::map<std::string, DestinationIp>;
DestinationIpMap destination_ip_map;
};
void AddFilterChainDataForSourcePort(
const FilterChain& filter_chain, uint32_t port,
XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map,
ValidationErrors* errors) {
auto insert_result = ports_map->emplace(
port, XdsListenerResource::FilterChainMap::FilterChainDataSharedPtr{
filter_chain.filter_chain_data});
if (!insert_result.second) {
errors->AddError(absl::StrCat(
"duplicate matching rules detected when adding filter chain: ",
filter_chain.filter_chain_match.ToString()));
}
}
void AddFilterChainDataForSourcePorts(
const FilterChain& filter_chain,
XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map,
ValidationErrors* errors) {
if (filter_chain.filter_chain_match.source_ports.empty()) {
AddFilterChainDataForSourcePort(filter_chain, 0, ports_map, errors);
} else {
for (uint32_t port : filter_chain.filter_chain_match.source_ports) {
AddFilterChainDataForSourcePort(filter_chain, port, ports_map, errors);
}
}
}
void AddFilterChainDataForSourceIpRange(
const FilterChain& filter_chain,
InternalFilterChainMap::SourceIpMap* source_ip_map,
ValidationErrors* errors) {
if (filter_chain.filter_chain_match.source_prefix_ranges.empty()) {
auto insert_result = source_ip_map->emplace(
"", XdsListenerResource::FilterChainMap::SourceIp());
AddFilterChainDataForSourcePorts(
filter_chain, &insert_result.first->second.ports_map, errors);
} else {
for (const auto& prefix_range :
filter_chain.filter_chain_match.source_prefix_ranges) {
auto addr_str = grpc_sockaddr_to_string(&prefix_range.address, false);
if (!addr_str.ok()) {
errors->AddError(absl::StrCat(
"error parsing source IP sockaddr (should not happen): ",
addr_str.status().message()));
continue;
}
auto insert_result = source_ip_map->emplace(
absl::StrCat(*addr_str, "/", prefix_range.prefix_len),
XdsListenerResource::FilterChainMap::SourceIp());
if (insert_result.second) {
insert_result.first->second.prefix_range.emplace(prefix_range);
}
AddFilterChainDataForSourcePorts(
filter_chain, &insert_result.first->second.ports_map, errors);
}
}
}
void AddFilterChainDataForSourceType(
const FilterChain& filter_chain,
InternalFilterChainMap::DestinationIp* destination_ip,
ValidationErrors* errors) {
CHECK(static_cast<unsigned int>(filter_chain.filter_chain_match.source_type) <
3u);
AddFilterChainDataForSourceIpRange(
filter_chain,
&destination_ip->source_types_array[static_cast<int>(
filter_chain.filter_chain_match.source_type)],
errors);
}
void AddFilterChainDataForApplicationProtocols(
const FilterChain& filter_chain,
InternalFilterChainMap::DestinationIp* destination_ip,
ValidationErrors* errors) {
// Only allow filter chains that do not mention application protocols
if (filter_chain.filter_chain_match.application_protocols.empty()) {
AddFilterChainDataForSourceType(filter_chain, destination_ip, errors);
}
}
void AddFilterChainDataForTransportProtocol(
const FilterChain& filter_chain,
InternalFilterChainMap::DestinationIp* destination_ip,
ValidationErrors* errors) {
const std::string& transport_protocol =
filter_chain.filter_chain_match.transport_protocol;
// Only allow filter chains with no transport protocol or "raw_buffer"
if (!transport_protocol.empty() && transport_protocol != "raw_buffer") {
return;
}
// If for this configuration, we've already seen filter chains that mention
// the transport protocol as "raw_buffer", we will never match filter chains
// that do not mention it.
if (destination_ip->transport_protocol_raw_buffer_provided &&
transport_protocol.empty()) {
return;
}
if (!transport_protocol.empty() &&
!destination_ip->transport_protocol_raw_buffer_provided) {
destination_ip->transport_protocol_raw_buffer_provided = true;
// Clear out the previous entries if any since those entries did not mention
// "raw_buffer"
destination_ip->source_types_array =
InternalFilterChainMap::ConnectionSourceTypesArray();
}
AddFilterChainDataForApplicationProtocols(filter_chain, destination_ip,
errors);
}
void AddFilterChainDataForServerNames(
const FilterChain& filter_chain,
InternalFilterChainMap::DestinationIp* destination_ip,
ValidationErrors* errors) {
// Don't continue adding filter chains with server names mentioned
if (filter_chain.filter_chain_match.server_names.empty()) {
AddFilterChainDataForTransportProtocol(filter_chain, destination_ip,
errors);
}
}
void AddFilterChainDataForDestinationIpRange(
const FilterChain& filter_chain,
InternalFilterChainMap::DestinationIpMap* destination_ip_map,
ValidationErrors* errors) {
if (filter_chain.filter_chain_match.prefix_ranges.empty()) {
auto insert_result = destination_ip_map->emplace(
"", InternalFilterChainMap::DestinationIp());
AddFilterChainDataForServerNames(filter_chain, &insert_result.first->second,
errors);
} else {
for (const auto& prefix_range :
filter_chain.filter_chain_match.prefix_ranges) {
auto addr_str = grpc_sockaddr_to_string(&prefix_range.address, false);
if (!addr_str.ok()) {
errors->AddError(absl::StrCat(
"error parsing destination IP sockaddr (should not happen): ",
addr_str.status().message()));
continue;
}
auto insert_result = destination_ip_map->emplace(
absl::StrCat(*addr_str, "/", prefix_range.prefix_len),
InternalFilterChainMap::DestinationIp());
if (insert_result.second) {
insert_result.first->second.prefix_range.emplace(prefix_range);
}
AddFilterChainDataForServerNames(filter_chain,
&insert_result.first->second, errors);
}
}
}
XdsListenerResource::FilterChainMap BuildFromInternalFilterChainMap(
InternalFilterChainMap* internal_filter_chain_map) {
XdsListenerResource::FilterChainMap filter_chain_map;
for (auto& destination_ip_pair :
internal_filter_chain_map->destination_ip_map) {
XdsListenerResource::FilterChainMap::DestinationIp destination_ip;
destination_ip.prefix_range = destination_ip_pair.second.prefix_range;
for (int i = 0; i < 3; i++) {
auto& source_ip_map = destination_ip_pair.second.source_types_array[i];
for (auto& source_ip_pair : source_ip_map) {
destination_ip.source_types_array[i].push_back(
std::move(source_ip_pair.second));
}
}
filter_chain_map.destination_ip_vector.push_back(std::move(destination_ip));
}
return filter_chain_map;
}
XdsListenerResource::FilterChainMap BuildFilterChainMap(
const std::vector<FilterChain>& filter_chains, ValidationErrors* errors) {
InternalFilterChainMap internal_filter_chain_map;
for (const auto& filter_chain : filter_chains) {
// Discard filter chain entries that specify destination port
if (filter_chain.filter_chain_match.destination_port != 0) continue;
AddFilterChainDataForDestinationIpRange(
filter_chain, &internal_filter_chain_map.destination_ip_map, errors);
}
return BuildFromInternalFilterChainMap(&internal_filter_chain_map);
}
absl::StatusOr<std::shared_ptr<const XdsListenerResource>>
LdsResourceParseServer(const XdsResourceType::DecodeContext& context,
const envoy_config_listener_v3_Listener* listener) {
ValidationErrors errors;
XdsListenerResource::TcpListener tcp_listener;
// address
{
ValidationErrors::ScopedField field(&errors, "address");
auto address = AddressParse(
envoy_config_listener_v3_Listener_address(listener), &errors);
if (address.has_value()) tcp_listener.address = std::move(*address);
}
// use_original_dst
{
ValidationErrors::ScopedField field(&errors, "use_original_dst");
const auto* use_original_dst =
envoy_config_listener_v3_Listener_use_original_dst(listener);
if (use_original_dst != nullptr &&
google_protobuf_BoolValue_value(use_original_dst)) {
errors.AddError("field not supported");
}
}
// filter_chains
size_t num_filter_chains = 0;
{
ValidationErrors::ScopedField field(&errors, "filter_chains");
auto* filter_chains = envoy_config_listener_v3_Listener_filter_chains(
listener, &num_filter_chains);
std::vector<FilterChain> parsed_filter_chains;
parsed_filter_chains.reserve(num_filter_chains);
for (size_t i = 0; i < num_filter_chains; i++) {
ValidationErrors::ScopedField field(&errors, absl::StrCat("[", i, "]"));
auto filter_chain = FilterChainParse(context, filter_chains[i], &errors);
if (filter_chain.has_value()) {
parsed_filter_chains.push_back(std::move(*filter_chain));
}
}
tcp_listener.filter_chain_map =
BuildFilterChainMap(parsed_filter_chains, &errors);
}
// default_filter_chain
{
ValidationErrors::ScopedField field(&errors, "default_filter_chain");
auto* default_filter_chain =
envoy_config_listener_v3_Listener_default_filter_chain(listener);
if (default_filter_chain != nullptr) {
auto filter_chain =
FilterChainParse(context, default_filter_chain, &errors);
if (filter_chain.has_value() &&
filter_chain->filter_chain_data != nullptr) {
tcp_listener.default_filter_chain =
std::move(*filter_chain->filter_chain_data);
}
} else if (num_filter_chains == 0) {
// Make sure that there is at least one filter chain to use.
errors.AddError("must be set if filter_chains is unset");
}
}
// Return result.
if (!errors.ok()) {
return errors.status(absl::StatusCode::kInvalidArgument,
"errors validating server Listener");
}
auto lds_update = std::make_shared<XdsListenerResource>();
lds_update->listener = std::move(tcp_listener);
return lds_update;
}
absl::StatusOr<std::shared_ptr<const XdsListenerResource>> LdsResourceParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_listener_v3_Listener* listener) {
// Check whether it's a client or server listener.
const envoy_config_listener_v3_ApiListener* api_listener =
envoy_config_listener_v3_Listener_api_listener(listener);
const envoy_config_core_v3_Address* address =
envoy_config_listener_v3_Listener_address(listener);
// TODO(roth): Re-enable the following check once
// github.com/istio/istio/issues/38914 is resolved.
// if (api_listener != nullptr && address != nullptr) {
// return absl::InvalidArgumentError(
// "Listener has both address and ApiListener");
// }
if (api_listener == nullptr && address == nullptr) {
return absl::InvalidArgumentError(
"Listener has neither address nor ApiListener");
}
// If api_listener is present, it's for a client; otherwise, it's
// for a server.
if (api_listener != nullptr) {
return LdsResourceParseClient(context, api_listener);
}
return LdsResourceParseServer(context, listener);
}
void MaybeLogListener(const XdsResourceType::DecodeContext& context,
const envoy_config_listener_v3_Listener* listener) {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) {
const upb_MessageDef* msg_type =
envoy_config_listener_v3_Listener_getmsgdef(context.symtab);
char buf[10240];
upb_TextEncode(reinterpret_cast<const upb_Message*>(listener), msg_type,
nullptr, 0, buf, sizeof(buf));
VLOG(2) << "[xds_client " << context.client << "] Listener: " << buf;
}
}
} // namespace
XdsResourceType::DecodeResult XdsListenerResourceType::Decode(
const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const {
DecodeResult result;
// Parse serialized proto.
auto* resource = envoy_config_listener_v3_Listener_parse(
serialized_resource.data(), serialized_resource.size(), context.arena);
if (resource == nullptr) {
result.resource =
absl::InvalidArgumentError("Can't parse Listener resource.");
return result;
}
MaybeLogListener(context, resource);
// Validate resource.
result.name =
UpbStringToStdString(envoy_config_listener_v3_Listener_name(resource));
auto listener = LdsResourceParse(context, resource);
if (!listener.ok()) {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(ERROR) << "[xds_client " << context.client << "] invalid Listener "
<< *result.name << ": " << listener.status();
}
result.resource = listener.status();
} else {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(INFO) << "[xds_client " << context.client << "] parsed Listener "
<< *result.name << ": " << (*listener)->ToString();
}
result.resource = std::move(*listener);
}
return result;
}
} // namespace grpc_core

@ -0,0 +1,60 @@
//
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_LISTENER_PARSER_H
#define GRPC_SRC_CORE_XDS_GRPC_XDS_LISTENER_PARSER_H
#include "absl/strings/string_view.h"
#include "envoy/config/listener/v3/listener.upbdefs.h"
#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h"
#include "upb/reflection/def.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_http_filter_registry.h"
#include "src/core/xds/grpc/xds_listener.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
#include "src/core/xds/xds_client/xds_resource_type_impl.h"
namespace grpc_core {
class XdsListenerResourceType final
: public XdsResourceTypeImpl<XdsListenerResourceType, XdsListenerResource> {
public:
absl::string_view type_url() const override {
return "envoy.config.listener.v3.Listener";
}
DecodeResult Decode(const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const override;
bool AllResourcesRequiredInSotW() const override { return true; }
void InitUpbSymtab(XdsClient* xds_client,
upb_DefPool* symtab) const override {
envoy_config_listener_v3_Listener_getmsgdef(symtab);
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef(
symtab);
const auto& http_filter_registry =
static_cast<const GrpcXdsBootstrap&>(xds_client->bootstrap())
.http_filter_registry();
http_filter_registry.PopulateSymtab(symtab);
}
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_LISTENER_PARSER_H

@ -16,78 +16,22 @@
#include "src/core/xds/grpc/xds_route_config.h"
#include <stddef.h>
#include <stdint.h>
#include <limits>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "envoy/config/core/v3/base.upb.h"
#include "envoy/config/core/v3/extension.upb.h"
#include "envoy/config/route/v3/route.upb.h"
#include "envoy/config/route/v3/route.upbdefs.h"
#include "envoy/config/route/v3/route_components.upb.h"
#include "envoy/type/matcher/v3/regex.upb.h"
#include "envoy/type/matcher/v3/string.upb.h"
#include "envoy/type/v3/percent.upb.h"
#include "envoy/type/v3/range.upb.h"
#include "google/protobuf/any.upb.h"
#include "google/protobuf/duration.upb.h"
#include "google/protobuf/wrappers.upb.h"
#include "re2/re2.h"
#include "upb/base/string_view.h"
#include "upb/message/map.h"
#include "upb/text/encode.h"
#include <grpc/status.h>
#include <grpc/support/port_platform.h>
#include "src/core/lib/channel/status_util.h"
#include "src/core/lib/config/core_configuration.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/gprpp/env.h"
#include "src/core/lib/gprpp/match.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/matchers/matchers.h"
#include "src/core/load_balancing/lb_policy_registry.h"
#include "src/core/util/json/json.h"
#include "src/core/util/json/json_writer.h"
#include "src/core/util/string.h"
#include "src/core/util/upb_utils.h"
#include "src/core/xds/grpc/xds_cluster_specifier_plugin.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/grpc/xds_routing.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
namespace grpc_core {
// TODO(apolcyn): remove this flag by the 1.58 release
bool XdsRlsEnabled() {
auto value = GetEnv("GRPC_EXPERIMENTAL_XDS_RLS_LB");
if (!value.has_value()) return true;
bool parsed_value;
bool parse_succeeded = gpr_parse_bool_value(value->c_str(), &parsed_value);
return parse_succeeded && parsed_value;
}
//
// XdsRouteConfigResource::RetryPolicy
//
@ -335,863 +279,4 @@ std::string XdsRouteConfigResource::ToString() const {
return absl::StrJoin(parts, "");
}
namespace {
XdsRouteConfigResource::ClusterSpecifierPluginMap ClusterSpecifierPluginParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_RouteConfiguration* route_config,
ValidationErrors* errors) {
XdsRouteConfigResource::ClusterSpecifierPluginMap
cluster_specifier_plugin_map;
const auto& cluster_specifier_plugin_registry =
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap())
.cluster_specifier_plugin_registry();
size_t num_cluster_specifier_plugins;
const envoy_config_route_v3_ClusterSpecifierPlugin* const*
cluster_specifier_plugin =
envoy_config_route_v3_RouteConfiguration_cluster_specifier_plugins(
route_config, &num_cluster_specifier_plugins);
for (size_t i = 0; i < num_cluster_specifier_plugins; ++i) {
bool is_optional = envoy_config_route_v3_ClusterSpecifierPlugin_is_optional(
cluster_specifier_plugin[i]);
ValidationErrors::ScopedField field(
errors, absl::StrCat(".cluster_specifier_plugins[", i, "].extension"));
const envoy_config_core_v3_TypedExtensionConfig* typed_extension_config =
envoy_config_route_v3_ClusterSpecifierPlugin_extension(
cluster_specifier_plugin[i]);
if (typed_extension_config == nullptr) {
errors->AddError("field not present");
continue;
}
std::string name = UpbStringToStdString(
envoy_config_core_v3_TypedExtensionConfig_name(typed_extension_config));
if (cluster_specifier_plugin_map.find(name) !=
cluster_specifier_plugin_map.end()) {
ValidationErrors::ScopedField field(errors, ".name");
errors->AddError(absl::StrCat("duplicate name \"", name, "\""));
} else {
// Add a sentinel entry in case we encounter an error later, just so we
// don't generate duplicate errors for each route that uses this plugin.
cluster_specifier_plugin_map[name] = "<sentinel>";
}
ValidationErrors::ScopedField field2(errors, ".typed_config");
const google_protobuf_Any* any =
envoy_config_core_v3_TypedExtensionConfig_typed_config(
typed_extension_config);
auto extension = ExtractXdsExtension(context, any, errors);
if (!extension.has_value()) continue;
const XdsClusterSpecifierPluginImpl* cluster_specifier_plugin_impl =
cluster_specifier_plugin_registry.GetPluginForType(extension->type);
if (cluster_specifier_plugin_impl == nullptr) {
if (is_optional) {
// Empty string indicates an optional plugin.
// This is used later when validating routes, and since we will skip
// any routes that refer to this plugin, we won't wind up including
// this plugin in the resource that we return to the watcher.
cluster_specifier_plugin_map[std::move(name)] = "";
} else {
// Not optional, report error.
errors->AddError("unsupported ClusterSpecifierPlugin type");
}
continue;
}
const size_t original_error_size = errors->size();
Json lb_policy_config =
cluster_specifier_plugin_impl->GenerateLoadBalancingPolicyConfig(
std::move(*extension), context.arena, context.symtab, errors);
if (errors->size() != original_error_size) continue;
auto config =
CoreConfiguration::Get().lb_policy_registry().ParseLoadBalancingConfig(
lb_policy_config);
if (!config.ok()) {
errors->AddError(absl::StrCat(
"ClusterSpecifierPlugin returned invalid LB policy config: ",
config.status().message()));
} else {
cluster_specifier_plugin_map[std::move(name)] =
JsonDump(lb_policy_config);
}
}
return cluster_specifier_plugin_map;
}
absl::optional<StringMatcher> RoutePathMatchParse(
const envoy_config_route_v3_RouteMatch* match, ValidationErrors* errors) {
bool case_sensitive = true;
auto* case_sensitive_ptr =
envoy_config_route_v3_RouteMatch_case_sensitive(match);
if (case_sensitive_ptr != nullptr) {
case_sensitive = google_protobuf_BoolValue_value(case_sensitive_ptr);
}
StringMatcher::Type type;
std::string match_string;
if (envoy_config_route_v3_RouteMatch_has_prefix(match)) {
absl::string_view prefix =
UpbStringToAbsl(envoy_config_route_v3_RouteMatch_prefix(match));
// For any prefix that cannot match a path of the form "/service/method",
// ignore the route.
if (!prefix.empty()) {
// Does not start with a slash.
if (prefix[0] != '/') return absl::nullopt;
std::vector<absl::string_view> prefix_elements =
absl::StrSplit(prefix.substr(1), absl::MaxSplits('/', 2));
// More than 2 slashes.
if (prefix_elements.size() > 2) return absl::nullopt;
// Two consecutive slashes.
if (prefix_elements.size() == 2 && prefix_elements[0].empty()) {
return absl::nullopt;
}
}
type = StringMatcher::Type::kPrefix;
match_string = std::string(prefix);
} else if (envoy_config_route_v3_RouteMatch_has_path(match)) {
absl::string_view path =
UpbStringToAbsl(envoy_config_route_v3_RouteMatch_path(match));
// For any path not of the form "/service/method", ignore the route.
// Empty path.
if (path.empty()) return absl::nullopt;
// Does not start with a slash.
if (path[0] != '/') return absl::nullopt;
std::vector<absl::string_view> path_elements =
absl::StrSplit(path.substr(1), absl::MaxSplits('/', 2));
// Number of slashes does not equal 2.
if (path_elements.size() != 2) return absl::nullopt;
// Empty service name.
if (path_elements[0].empty()) return absl::nullopt;
// Empty method name.
if (path_elements[1].empty()) return absl::nullopt;
type = StringMatcher::Type::kExact;
match_string = std::string(path);
} else if (envoy_config_route_v3_RouteMatch_has_safe_regex(match)) {
const envoy_type_matcher_v3_RegexMatcher* regex_matcher =
envoy_config_route_v3_RouteMatch_safe_regex(match);
CHECK_NE(regex_matcher, nullptr);
type = StringMatcher::Type::kSafeRegex;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher));
} else {
errors->AddError("invalid path specifier");
return absl::nullopt;
}
absl::StatusOr<StringMatcher> string_matcher =
StringMatcher::Create(type, match_string, case_sensitive);
if (!string_matcher.ok()) {
errors->AddError(absl::StrCat("error creating path matcher: ",
string_matcher.status().message()));
return absl::nullopt;
}
return std::move(*string_matcher);
}
void RouteHeaderMatchersParse(const envoy_config_route_v3_RouteMatch* match,
XdsRouteConfigResource::Route* route,
ValidationErrors* errors) {
size_t size;
const envoy_config_route_v3_HeaderMatcher* const* headers =
envoy_config_route_v3_RouteMatch_headers(match, &size);
for (size_t i = 0; i < size; ++i) {
ValidationErrors::ScopedField field(errors,
absl::StrCat(".headers[", i, "]"));
const envoy_config_route_v3_HeaderMatcher* header = headers[i];
CHECK_NE(header, nullptr);
const std::string name =
UpbStringToStdString(envoy_config_route_v3_HeaderMatcher_name(header));
HeaderMatcher::Type type;
std::string match_string;
int64_t range_start = 0;
int64_t range_end = 0;
bool present_match = false;
bool case_sensitive = true;
if (envoy_config_route_v3_HeaderMatcher_has_exact_match(header)) {
type = HeaderMatcher::Type::kExact;
match_string = UpbStringToStdString(
envoy_config_route_v3_HeaderMatcher_exact_match(header));
} else if (envoy_config_route_v3_HeaderMatcher_has_prefix_match(header)) {
type = HeaderMatcher::Type::kPrefix;
match_string = UpbStringToStdString(
envoy_config_route_v3_HeaderMatcher_prefix_match(header));
} else if (envoy_config_route_v3_HeaderMatcher_has_suffix_match(header)) {
type = HeaderMatcher::Type::kSuffix;
match_string = UpbStringToStdString(
envoy_config_route_v3_HeaderMatcher_suffix_match(header));
} else if (envoy_config_route_v3_HeaderMatcher_has_contains_match(header)) {
type = HeaderMatcher::Type::kContains;
match_string = UpbStringToStdString(
envoy_config_route_v3_HeaderMatcher_contains_match(header));
} else if (envoy_config_route_v3_HeaderMatcher_has_safe_regex_match(
header)) {
const envoy_type_matcher_v3_RegexMatcher* regex_matcher =
envoy_config_route_v3_HeaderMatcher_safe_regex_match(header);
CHECK_NE(regex_matcher, nullptr);
type = HeaderMatcher::Type::kSafeRegex;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher));
} else if (envoy_config_route_v3_HeaderMatcher_has_range_match(header)) {
type = HeaderMatcher::Type::kRange;
const envoy_type_v3_Int64Range* range_matcher =
envoy_config_route_v3_HeaderMatcher_range_match(header);
CHECK_NE(range_matcher, nullptr);
range_start = envoy_type_v3_Int64Range_start(range_matcher);
range_end = envoy_type_v3_Int64Range_end(range_matcher);
} else if (envoy_config_route_v3_HeaderMatcher_has_present_match(header)) {
type = HeaderMatcher::Type::kPresent;
present_match = envoy_config_route_v3_HeaderMatcher_present_match(header);
} else if (envoy_config_route_v3_HeaderMatcher_has_string_match(header)) {
ValidationErrors::ScopedField field(errors, ".string_match");
const auto* matcher =
envoy_config_route_v3_HeaderMatcher_string_match(header);
CHECK_NE(matcher, nullptr);
if (envoy_type_matcher_v3_StringMatcher_has_exact(matcher)) {
type = HeaderMatcher::Type::kExact;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_StringMatcher_exact(matcher));
} else if (envoy_type_matcher_v3_StringMatcher_has_prefix(matcher)) {
type = HeaderMatcher::Type::kPrefix;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_StringMatcher_prefix(matcher));
} else if (envoy_type_matcher_v3_StringMatcher_has_suffix(matcher)) {
type = HeaderMatcher::Type::kSuffix;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_StringMatcher_suffix(matcher));
} else if (envoy_type_matcher_v3_StringMatcher_has_contains(matcher)) {
type = HeaderMatcher::Type::kContains;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_StringMatcher_contains(matcher));
} else if (envoy_type_matcher_v3_StringMatcher_has_safe_regex(matcher)) {
type = HeaderMatcher::Type::kSafeRegex;
const auto* regex_matcher =
envoy_type_matcher_v3_StringMatcher_safe_regex(matcher);
CHECK_NE(regex_matcher, nullptr);
match_string = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher));
} else {
errors->AddError("invalid string matcher");
continue;
}
case_sensitive =
!envoy_type_matcher_v3_StringMatcher_ignore_case(matcher);
} else {
errors->AddError("invalid header matcher");
continue;
}
bool invert_match =
envoy_config_route_v3_HeaderMatcher_invert_match(header);
absl::StatusOr<HeaderMatcher> header_matcher =
HeaderMatcher::Create(name, type, match_string, range_start, range_end,
present_match, invert_match, case_sensitive);
if (!header_matcher.ok()) {
errors->AddError(absl::StrCat("cannot create header matcher: ",
header_matcher.status().message()));
} else {
route->matchers.header_matchers.emplace_back(std::move(*header_matcher));
}
}
}
void RouteRuntimeFractionParse(const envoy_config_route_v3_RouteMatch* match,
XdsRouteConfigResource::Route* route,
ValidationErrors* errors) {
const envoy_config_core_v3_RuntimeFractionalPercent* runtime_fraction =
envoy_config_route_v3_RouteMatch_runtime_fraction(match);
if (runtime_fraction != nullptr) {
const envoy_type_v3_FractionalPercent* fraction =
envoy_config_core_v3_RuntimeFractionalPercent_default_value(
runtime_fraction);
if (fraction != nullptr) {
uint32_t numerator = envoy_type_v3_FractionalPercent_numerator(fraction);
const uint32_t denominator =
envoy_type_v3_FractionalPercent_denominator(fraction);
// Normalize to million.
switch (denominator) {
case envoy_type_v3_FractionalPercent_HUNDRED:
numerator *= 10000;
break;
case envoy_type_v3_FractionalPercent_TEN_THOUSAND:
numerator *= 100;
break;
case envoy_type_v3_FractionalPercent_MILLION:
break;
default: {
ValidationErrors::ScopedField field(
errors, ".runtime_fraction.default_value.denominator");
errors->AddError("unknown denominator type");
return;
}
}
route->matchers.fraction_per_million = numerator;
}
}
}
template <typename ParentType, typename EntryType>
XdsRouteConfigResource::TypedPerFilterConfig ParseTypedPerFilterConfig(
const XdsResourceType::DecodeContext& context, const ParentType* parent,
const EntryType* (*entry_func)(const ParentType*, size_t*),
upb_StringView (*key_func)(const EntryType*),
const google_protobuf_Any* (*value_func)(const EntryType*),
ValidationErrors* errors) {
XdsRouteConfigResource::TypedPerFilterConfig typed_per_filter_config;
size_t filter_it = kUpb_Map_Begin;
while (true) {
const auto* filter_entry = entry_func(parent, &filter_it);
if (filter_entry == nullptr) break;
absl::string_view key = UpbStringToAbsl(key_func(filter_entry));
ValidationErrors::ScopedField field(errors, absl::StrCat("[", key, "]"));
if (key.empty()) errors->AddError("filter name must be non-empty");
const google_protobuf_Any* any = value_func(filter_entry);
auto extension = ExtractXdsExtension(context, any, errors);
if (!extension.has_value()) continue;
auto* extension_to_use = &*extension;
absl::optional<XdsExtension> nested_extension;
bool is_optional = false;
if (extension->type == "envoy.config.route.v3.FilterConfig") {
absl::string_view* serialized_config =
absl::get_if<absl::string_view>(&extension->value);
if (serialized_config == nullptr) {
errors->AddError("could not parse FilterConfig");
continue;
}
const auto* filter_config = envoy_config_route_v3_FilterConfig_parse(
serialized_config->data(), serialized_config->size(), context.arena);
if (filter_config == nullptr) {
errors->AddError("could not parse FilterConfig");
continue;
}
is_optional =
envoy_config_route_v3_FilterConfig_is_optional(filter_config);
any = envoy_config_route_v3_FilterConfig_config(filter_config);
extension->validation_fields.emplace_back(errors, ".config");
nested_extension = ExtractXdsExtension(context, any, errors);
if (!nested_extension.has_value()) continue;
extension_to_use = &*nested_extension;
}
const auto& http_filter_registry =
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap())
.http_filter_registry();
const XdsHttpFilterImpl* filter_impl =
http_filter_registry.GetFilterForType(extension_to_use->type);
if (filter_impl == nullptr) {
if (!is_optional) errors->AddError("unsupported filter type");
continue;
}
absl::optional<XdsHttpFilterImpl::FilterConfig> filter_config =
filter_impl->GenerateFilterConfigOverride(
context, std::move(*extension_to_use), errors);
if (filter_config.has_value()) {
typed_per_filter_config[std::string(key)] = std::move(*filter_config);
}
}
return typed_per_filter_config;
}
XdsRouteConfigResource::RetryPolicy RetryPolicyParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_RetryPolicy* retry_policy_proto,
ValidationErrors* errors) {
XdsRouteConfigResource::RetryPolicy retry_policy;
auto retry_on = UpbStringToStdString(
envoy_config_route_v3_RetryPolicy_retry_on(retry_policy_proto));
std::vector<absl::string_view> codes = absl::StrSplit(retry_on, ',');
for (const auto& code : codes) {
if (code == "cancelled") {
retry_policy.retry_on.Add(GRPC_STATUS_CANCELLED);
} else if (code == "deadline-exceeded") {
retry_policy.retry_on.Add(GRPC_STATUS_DEADLINE_EXCEEDED);
} else if (code == "internal") {
retry_policy.retry_on.Add(GRPC_STATUS_INTERNAL);
} else if (code == "resource-exhausted") {
retry_policy.retry_on.Add(GRPC_STATUS_RESOURCE_EXHAUSTED);
} else if (code == "unavailable") {
retry_policy.retry_on.Add(GRPC_STATUS_UNAVAILABLE);
} else {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(INFO) << "Unsupported retry_on policy " << code;
}
}
}
const google_protobuf_UInt32Value* num_retries =
envoy_config_route_v3_RetryPolicy_num_retries(retry_policy_proto);
if (num_retries != nullptr) {
uint32_t num_retries_value = google_protobuf_UInt32Value_value(num_retries);
if (num_retries_value == 0) {
ValidationErrors::ScopedField field(errors, ".num_retries");
errors->AddError("must be greater than 0");
} else {
retry_policy.num_retries = num_retries_value;
}
} else {
retry_policy.num_retries = 1;
}
const envoy_config_route_v3_RetryPolicy_RetryBackOff* backoff =
envoy_config_route_v3_RetryPolicy_retry_back_off(retry_policy_proto);
if (backoff != nullptr) {
ValidationErrors::ScopedField field(errors, ".retry_back_off");
{
ValidationErrors::ScopedField field(errors, ".base_interval");
const google_protobuf_Duration* base_interval =
envoy_config_route_v3_RetryPolicy_RetryBackOff_base_interval(backoff);
if (base_interval == nullptr) {
errors->AddError("field not present");
} else {
retry_policy.retry_back_off.base_interval =
ParseDuration(base_interval, errors);
}
}
{
ValidationErrors::ScopedField field(errors, ".max_interval");
const google_protobuf_Duration* max_interval =
envoy_config_route_v3_RetryPolicy_RetryBackOff_max_interval(backoff);
Duration max;
if (max_interval != nullptr) {
max = ParseDuration(max_interval, errors);
} else {
// if max interval is not set, it is 10x the base.
max = 10 * retry_policy.retry_back_off.base_interval;
}
retry_policy.retry_back_off.max_interval = max;
}
} else {
retry_policy.retry_back_off.base_interval = Duration::Milliseconds(25);
retry_policy.retry_back_off.max_interval = Duration::Milliseconds(250);
}
return retry_policy;
}
absl::optional<XdsRouteConfigResource::Route::RouteAction> RouteActionParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_RouteAction* route_action_proto,
const std::map<std::string /*cluster_specifier_plugin_name*/,
std::string /*LB policy config*/>&
cluster_specifier_plugin_map,
ValidationErrors* errors) {
XdsRouteConfigResource::Route::RouteAction route_action;
// grpc_timeout_header_max or max_stream_duration
const auto* max_stream_duration =
envoy_config_route_v3_RouteAction_max_stream_duration(route_action_proto);
if (max_stream_duration != nullptr) {
ValidationErrors::ScopedField field(errors, ".max_stream_duration");
const google_protobuf_Duration* duration =
envoy_config_route_v3_RouteAction_MaxStreamDuration_grpc_timeout_header_max(
max_stream_duration);
if (duration != nullptr) {
ValidationErrors::ScopedField field(errors, ".grpc_timeout_header_max");
route_action.max_stream_duration = ParseDuration(duration, errors);
} else {
duration =
envoy_config_route_v3_RouteAction_MaxStreamDuration_max_stream_duration(
max_stream_duration);
if (duration != nullptr) {
ValidationErrors::ScopedField field(errors, ".max_stream_duration");
route_action.max_stream_duration = ParseDuration(duration, errors);
}
}
}
// hash_policy
size_t size = 0;
const envoy_config_route_v3_RouteAction_HashPolicy* const* hash_policies =
envoy_config_route_v3_RouteAction_hash_policy(route_action_proto, &size);
for (size_t i = 0; i < size; ++i) {
ValidationErrors::ScopedField field(errors,
absl::StrCat(".hash_policy[", i, "]"));
const auto* hash_policy = hash_policies[i];
XdsRouteConfigResource::Route::RouteAction::HashPolicy policy;
policy.terminal =
envoy_config_route_v3_RouteAction_HashPolicy_terminal(hash_policy);
const envoy_config_route_v3_RouteAction_HashPolicy_Header* header;
const envoy_config_route_v3_RouteAction_HashPolicy_FilterState*
filter_state;
if ((header = envoy_config_route_v3_RouteAction_HashPolicy_header(
hash_policy)) != nullptr) {
// header
ValidationErrors::ScopedField field(errors, ".header");
XdsRouteConfigResource::Route::RouteAction::HashPolicy::Header
header_policy;
header_policy.header_name = UpbStringToStdString(
envoy_config_route_v3_RouteAction_HashPolicy_Header_header_name(
header));
if (header_policy.header_name.empty()) {
ValidationErrors::ScopedField field(errors, ".header_name");
errors->AddError("must be non-empty");
}
// regex_rewrite
const auto* regex_rewrite =
envoy_config_route_v3_RouteAction_HashPolicy_Header_regex_rewrite(
header);
if (regex_rewrite != nullptr) {
ValidationErrors::ScopedField field(errors, ".regex_rewrite.pattern");
const auto* pattern =
envoy_type_matcher_v3_RegexMatchAndSubstitute_pattern(
regex_rewrite);
if (pattern == nullptr) {
errors->AddError("field not present");
continue;
}
ValidationErrors::ScopedField field2(errors, ".regex");
std::string regex = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(pattern));
if (regex.empty()) {
errors->AddError("field not present");
continue;
}
RE2::Options options;
header_policy.regex = std::make_unique<RE2>(regex, options);
if (!header_policy.regex->ok()) {
errors->AddError(absl::StrCat("errors compiling regex: ",
header_policy.regex->error()));
continue;
}
header_policy.regex_substitution = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatchAndSubstitute_substitution(
regex_rewrite));
}
policy.policy = std::move(header_policy);
} else if ((filter_state =
envoy_config_route_v3_RouteAction_HashPolicy_filter_state(
hash_policy)) != nullptr) {
// filter_state
std::string key = UpbStringToStdString(
envoy_config_route_v3_RouteAction_HashPolicy_FilterState_key(
filter_state));
if (key != "io.grpc.channel_id") continue;
policy.policy =
XdsRouteConfigResource::Route::RouteAction::HashPolicy::ChannelId();
} else {
// Unsupported hash policy type, ignore it.
continue;
}
route_action.hash_policies.emplace_back(std::move(policy));
}
// Get retry policy
const envoy_config_route_v3_RetryPolicy* retry_policy =
envoy_config_route_v3_RouteAction_retry_policy(route_action_proto);
if (retry_policy != nullptr) {
ValidationErrors::ScopedField field(errors, ".retry_policy");
route_action.retry_policy = RetryPolicyParse(context, retry_policy, errors);
}
// Parse cluster specifier, which is one of several options.
if (envoy_config_route_v3_RouteAction_has_cluster(route_action_proto)) {
// Cluster name.
std::string cluster_name = UpbStringToStdString(
envoy_config_route_v3_RouteAction_cluster(route_action_proto));
if (cluster_name.empty()) {
ValidationErrors::ScopedField field(errors, ".cluster");
errors->AddError("must be non-empty");
}
route_action.action =
XdsRouteConfigResource::Route::RouteAction::ClusterName{
std::move(cluster_name)};
} else if (envoy_config_route_v3_RouteAction_has_weighted_clusters(
route_action_proto)) {
// WeightedClusters.
ValidationErrors::ScopedField field(errors, ".weighted_clusters");
const envoy_config_route_v3_WeightedCluster* weighted_clusters_proto =
envoy_config_route_v3_RouteAction_weighted_clusters(route_action_proto);
CHECK_NE(weighted_clusters_proto, nullptr);
std::vector<XdsRouteConfigResource::Route::RouteAction::ClusterWeight>
action_weighted_clusters;
uint64_t total_weight = 0;
size_t clusters_size;
const envoy_config_route_v3_WeightedCluster_ClusterWeight* const* clusters =
envoy_config_route_v3_WeightedCluster_clusters(weighted_clusters_proto,
&clusters_size);
for (size_t i = 0; i < clusters_size; ++i) {
ValidationErrors::ScopedField field(errors,
absl::StrCat(".clusters[", i, "]"));
const auto* cluster_proto = clusters[i];
XdsRouteConfigResource::Route::RouteAction::ClusterWeight cluster;
// typed_per_filter_config
{
ValidationErrors::ScopedField field(errors, ".typed_per_filter_config");
cluster.typed_per_filter_config = ParseTypedPerFilterConfig<
envoy_config_route_v3_WeightedCluster_ClusterWeight,
envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry>(
context, cluster_proto,
envoy_config_route_v3_WeightedCluster_ClusterWeight_typed_per_filter_config_next,
envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry_key,
envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry_value,
errors);
}
// name
cluster.name = UpbStringToStdString(
envoy_config_route_v3_WeightedCluster_ClusterWeight_name(
cluster_proto));
if (cluster.name.empty()) {
ValidationErrors::ScopedField field(errors, ".name");
errors->AddError("must be non-empty");
}
// weight
const google_protobuf_UInt32Value* weight_proto =
envoy_config_route_v3_WeightedCluster_ClusterWeight_weight(
cluster_proto);
if (weight_proto == nullptr) {
ValidationErrors::ScopedField field(errors, ".weight");
errors->AddError("field not present");
} else {
cluster.weight = google_protobuf_UInt32Value_value(weight_proto);
if (cluster.weight == 0) continue;
total_weight += cluster.weight;
}
// Add entry to WeightedClusters.
action_weighted_clusters.emplace_back(std::move(cluster));
}
if (action_weighted_clusters.empty()) {
errors->AddError("no valid clusters specified");
} else if (total_weight > std::numeric_limits<uint32_t>::max()) {
errors->AddError("sum of cluster weights exceeds uint32 max");
}
route_action.action = std::move(action_weighted_clusters);
} else if (XdsRlsEnabled() &&
envoy_config_route_v3_RouteAction_has_cluster_specifier_plugin(
route_action_proto)) {
// ClusterSpecifierPlugin
ValidationErrors::ScopedField field(errors, ".cluster_specifier_plugin");
std::string plugin_name = UpbStringToStdString(
envoy_config_route_v3_RouteAction_cluster_specifier_plugin(
route_action_proto));
if (plugin_name.empty()) {
errors->AddError("must be non-empty");
return absl::nullopt;
}
auto it = cluster_specifier_plugin_map.find(plugin_name);
if (it == cluster_specifier_plugin_map.end()) {
errors->AddError(absl::StrCat("unknown cluster specifier plugin name \"",
plugin_name, "\""));
} else {
// If the cluster specifier config is empty, that means that the
// plugin was unsupported but optional. In that case, skip this route.
if (it->second.empty()) return absl::nullopt;
}
route_action.action =
XdsRouteConfigResource::Route::RouteAction::ClusterSpecifierPluginName{
std::move(plugin_name)};
} else {
// Not a supported cluster specifier, so ignore this route.
return absl::nullopt;
}
return route_action;
}
absl::optional<XdsRouteConfigResource::Route> ParseRoute(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_Route* route_proto,
const absl::optional<XdsRouteConfigResource::RetryPolicy>&
virtual_host_retry_policy,
const XdsRouteConfigResource::ClusterSpecifierPluginMap&
cluster_specifier_plugin_map,
std::set<absl::string_view>* cluster_specifier_plugins_not_seen,
ValidationErrors* errors) {
XdsRouteConfigResource::Route route;
// Parse route match.
{
ValidationErrors::ScopedField field(errors, ".match");
const auto* match = envoy_config_route_v3_Route_match(route_proto);
if (match == nullptr) {
errors->AddError("field not present");
return absl::nullopt;
}
// Skip routes with query_parameters set.
size_t query_parameters_size;
static_cast<void>(envoy_config_route_v3_RouteMatch_query_parameters(
match, &query_parameters_size));
if (query_parameters_size > 0) return absl::nullopt;
// Parse matchers.
auto path_matcher = RoutePathMatchParse(match, errors);
if (!path_matcher.has_value()) return absl::nullopt;
route.matchers.path_matcher = std::move(*path_matcher);
RouteHeaderMatchersParse(match, &route, errors);
RouteRuntimeFractionParse(match, &route, errors);
}
// Parse route action.
const envoy_config_route_v3_RouteAction* route_action_proto =
envoy_config_route_v3_Route_route(route_proto);
if (route_action_proto != nullptr) {
ValidationErrors::ScopedField field(errors, ".route");
auto route_action = RouteActionParse(context, route_action_proto,
cluster_specifier_plugin_map, errors);
if (!route_action.has_value()) return absl::nullopt;
// If the route does not have a retry policy but the vhost does,
// use the vhost retry policy for this route.
if (!route_action->retry_policy.has_value()) {
route_action->retry_policy = virtual_host_retry_policy;
}
// Mark off plugins used in route action.
auto* cluster_specifier_action = absl::get_if<
XdsRouteConfigResource::Route::RouteAction::ClusterSpecifierPluginName>(
&route_action->action);
if (cluster_specifier_action != nullptr) {
cluster_specifier_plugins_not_seen->erase(
cluster_specifier_action->cluster_specifier_plugin_name);
}
route.action = std::move(*route_action);
} else if (envoy_config_route_v3_Route_has_non_forwarding_action(
route_proto)) {
route.action = XdsRouteConfigResource::Route::NonForwardingAction();
} else {
// Leave route.action initialized to UnknownAction (its default).
}
// Parse typed_per_filter_config.
{
ValidationErrors::ScopedField field(errors, ".typed_per_filter_config");
route.typed_per_filter_config = ParseTypedPerFilterConfig<
envoy_config_route_v3_Route,
envoy_config_route_v3_Route_TypedPerFilterConfigEntry>(
context, route_proto,
envoy_config_route_v3_Route_typed_per_filter_config_next,
envoy_config_route_v3_Route_TypedPerFilterConfigEntry_key,
envoy_config_route_v3_Route_TypedPerFilterConfigEntry_value, errors);
}
return route;
}
} // namespace
std::shared_ptr<const XdsRouteConfigResource> XdsRouteConfigResource::Parse(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_RouteConfiguration* route_config,
ValidationErrors* errors) {
auto rds_update = std::make_shared<XdsRouteConfigResource>();
// Get the cluster spcifier plugin map.
if (XdsRlsEnabled()) {
rds_update->cluster_specifier_plugin_map =
ClusterSpecifierPluginParse(context, route_config, errors);
}
// Build a set of configured cluster_specifier_plugin names to make sure
// each is actually referenced by a route action.
std::set<absl::string_view> cluster_specifier_plugins_not_seen;
for (auto& plugin : rds_update->cluster_specifier_plugin_map) {
cluster_specifier_plugins_not_seen.emplace(plugin.first);
}
// Get the virtual hosts.
size_t num_virtual_hosts;
const envoy_config_route_v3_VirtualHost* const* virtual_hosts =
envoy_config_route_v3_RouteConfiguration_virtual_hosts(
route_config, &num_virtual_hosts);
for (size_t i = 0; i < num_virtual_hosts; ++i) {
ValidationErrors::ScopedField field(
errors, absl::StrCat(".virtual_hosts[", i, "]"));
rds_update->virtual_hosts.emplace_back();
XdsRouteConfigResource::VirtualHost& vhost =
rds_update->virtual_hosts.back();
// Parse domains.
size_t domain_size;
upb_StringView const* domains = envoy_config_route_v3_VirtualHost_domains(
virtual_hosts[i], &domain_size);
for (size_t j = 0; j < domain_size; ++j) {
std::string domain_pattern = UpbStringToStdString(domains[j]);
if (!XdsRouting::IsValidDomainPattern(domain_pattern)) {
ValidationErrors::ScopedField field(errors,
absl::StrCat(".domains[", j, "]"));
errors->AddError(
absl::StrCat("invalid domain pattern \"", domain_pattern, "\""));
}
vhost.domains.emplace_back(std::move(domain_pattern));
}
if (vhost.domains.empty()) {
ValidationErrors::ScopedField field(errors, ".domains");
errors->AddError("must be non-empty");
}
// Parse typed_per_filter_config.
{
ValidationErrors::ScopedField field(errors, ".typed_per_filter_config");
vhost.typed_per_filter_config = ParseTypedPerFilterConfig<
envoy_config_route_v3_VirtualHost,
envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry>(
context, virtual_hosts[i],
envoy_config_route_v3_VirtualHost_typed_per_filter_config_next,
envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry_key,
envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry_value,
errors);
}
// Parse retry policy.
absl::optional<XdsRouteConfigResource::RetryPolicy>
virtual_host_retry_policy;
const envoy_config_route_v3_RetryPolicy* retry_policy =
envoy_config_route_v3_VirtualHost_retry_policy(virtual_hosts[i]);
if (retry_policy != nullptr) {
ValidationErrors::ScopedField field(errors, ".retry_policy");
virtual_host_retry_policy =
RetryPolicyParse(context, retry_policy, errors);
}
// Parse routes.
ValidationErrors::ScopedField field2(errors, ".routes");
size_t num_routes;
const envoy_config_route_v3_Route* const* routes =
envoy_config_route_v3_VirtualHost_routes(virtual_hosts[i], &num_routes);
for (size_t j = 0; j < num_routes; ++j) {
ValidationErrors::ScopedField field(errors, absl::StrCat("[", j, "]"));
auto route = ParseRoute(context, routes[j], virtual_host_retry_policy,
rds_update->cluster_specifier_plugin_map,
&cluster_specifier_plugins_not_seen, errors);
if (route.has_value()) vhost.routes.emplace_back(std::move(*route));
}
}
// For cluster specifier plugins that were not used in any route action,
// delete them from the update, since they will never be used.
for (auto& unused_plugin : cluster_specifier_plugins_not_seen) {
rds_update->cluster_specifier_plugin_map.erase(std::string(unused_plugin));
}
return rds_update;
}
//
// XdsRouteConfigResourceType
//
namespace {
void MaybeLogRouteConfiguration(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_RouteConfiguration* route_config) {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) {
const upb_MessageDef* msg_type =
envoy_config_route_v3_RouteConfiguration_getmsgdef(context.symtab);
char buf[10240];
upb_TextEncode(reinterpret_cast<const upb_Message*>(route_config), msg_type,
nullptr, 0, buf, sizeof(buf));
VLOG(2) << "[xds_client " << context.client
<< "] RouteConfiguration: " << buf;
}
}
} // namespace
XdsResourceType::DecodeResult XdsRouteConfigResourceType::Decode(
const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const {
DecodeResult result;
// Parse serialized proto.
auto* resource = envoy_config_route_v3_RouteConfiguration_parse(
serialized_resource.data(), serialized_resource.size(), context.arena);
if (resource == nullptr) {
result.resource =
absl::InvalidArgumentError("Can't parse RouteConfiguration resource.");
return result;
}
MaybeLogRouteConfiguration(context, resource);
// Validate resource.
result.name = UpbStringToStdString(
envoy_config_route_v3_RouteConfiguration_name(resource));
ValidationErrors errors;
auto rds_update = XdsRouteConfigResource::Parse(context, resource, &errors);
if (!errors.ok()) {
absl::Status status =
errors.status(absl::StatusCode::kInvalidArgument,
"errors validating RouteConfiguration resource");
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(ERROR) << "[xds_client " << context.client
<< "] invalid RouteConfiguration " << *result.name << ": "
<< status;
}
result.resource = std::move(status);
} else {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(INFO) << "[xds_client " << context.client
<< "] parsed RouteConfiguration " << *result.name << ": "
<< rds_update->ToString();
}
result.resource = std::move(rds_update);
}
return result;
}
} // namespace grpc_core

@ -25,24 +25,14 @@
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "envoy/config/route/v3/route.upb.h"
#include "envoy/config/route/v3/route.upbdefs.h"
#include "re2/re2.h"
#include "upb/reflection/def.h"
#include <grpc/support/port_platform.h>
#include "src/core/lib/channel/status_util.h"
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/lib/matchers/matchers.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_cluster_specifier_plugin.h"
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/core/xds/grpc/xds_http_filter.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
#include "src/core/xds/xds_client/xds_resource_type_impl.h"
@ -220,32 +210,6 @@ struct XdsRouteConfigResource : public XdsResourceType::ResourceData {
cluster_specifier_plugin_map == other.cluster_specifier_plugin_map;
}
std::string ToString() const;
static std::shared_ptr<const XdsRouteConfigResource> Parse(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_RouteConfiguration* route_config,
ValidationErrors* errors);
};
class XdsRouteConfigResourceType final
: public XdsResourceTypeImpl<XdsRouteConfigResourceType,
XdsRouteConfigResource> {
public:
absl::string_view type_url() const override {
return "envoy.config.route.v3.RouteConfiguration";
}
DecodeResult Decode(const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const override;
void InitUpbSymtab(XdsClient* xds_client,
upb_DefPool* symtab) const override {
envoy_config_route_v3_RouteConfiguration_getmsgdef(symtab);
const auto& cluster_specifier_plugin_registry =
static_cast<const GrpcXdsBootstrap&>(xds_client->bootstrap())
.cluster_specifier_plugin_registry();
cluster_specifier_plugin_registry.PopulateSymtab(symtab);
}
};
} // namespace grpc_core

@ -0,0 +1,956 @@
//
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "src/core/xds/grpc/xds_route_config_parser.h"
#include <stddef.h>
#include <stdint.h>
#include <limits>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "envoy/config/core/v3/base.upb.h"
#include "envoy/config/core/v3/extension.upb.h"
#include "envoy/config/route/v3/route.upb.h"
#include "envoy/config/route/v3/route.upbdefs.h"
#include "envoy/config/route/v3/route_components.upb.h"
#include "envoy/type/matcher/v3/regex.upb.h"
#include "envoy/type/matcher/v3/string.upb.h"
#include "envoy/type/v3/percent.upb.h"
#include "envoy/type/v3/range.upb.h"
#include "google/protobuf/any.upb.h"
#include "google/protobuf/duration.upb.h"
#include "google/protobuf/wrappers.upb.h"
#include "re2/re2.h"
#include "upb/base/string_view.h"
#include "upb/message/map.h"
#include "upb/text/encode.h"
#include <grpc/status.h>
#include <grpc/support/port_platform.h>
#include "src/core/lib/channel/status_util.h"
#include "src/core/lib/config/core_configuration.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/gprpp/env.h"
#include "src/core/lib/gprpp/match.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/matchers/matchers.h"
#include "src/core/load_balancing/lb_policy_registry.h"
#include "src/core/util/json/json.h"
#include "src/core/util/json/json_writer.h"
#include "src/core/util/string.h"
#include "src/core/util/upb_utils.h"
#include "src/core/xds/grpc/xds_cluster_specifier_plugin.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_common_types_parser.h"
#include "src/core/xds/grpc/xds_http_filter.h"
#include "src/core/xds/grpc/xds_http_filter_registry.h"
#include "src/core/xds/grpc/xds_routing.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
namespace grpc_core {
// TODO(apolcyn): remove this flag by the 1.58 release
bool XdsRlsEnabled() {
auto value = GetEnv("GRPC_EXPERIMENTAL_XDS_RLS_LB");
if (!value.has_value()) return true;
bool parsed_value;
bool parse_succeeded = gpr_parse_bool_value(value->c_str(), &parsed_value);
return parse_succeeded && parsed_value;
}
//
// XdsRouteConfigResourceParse()
//
namespace {
XdsRouteConfigResource::ClusterSpecifierPluginMap ClusterSpecifierPluginParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_RouteConfiguration* route_config,
ValidationErrors* errors) {
XdsRouteConfigResource::ClusterSpecifierPluginMap
cluster_specifier_plugin_map;
const auto& cluster_specifier_plugin_registry =
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap())
.cluster_specifier_plugin_registry();
size_t num_cluster_specifier_plugins;
const envoy_config_route_v3_ClusterSpecifierPlugin* const*
cluster_specifier_plugin =
envoy_config_route_v3_RouteConfiguration_cluster_specifier_plugins(
route_config, &num_cluster_specifier_plugins);
for (size_t i = 0; i < num_cluster_specifier_plugins; ++i) {
bool is_optional = envoy_config_route_v3_ClusterSpecifierPlugin_is_optional(
cluster_specifier_plugin[i]);
ValidationErrors::ScopedField field(
errors, absl::StrCat(".cluster_specifier_plugins[", i, "].extension"));
const envoy_config_core_v3_TypedExtensionConfig* typed_extension_config =
envoy_config_route_v3_ClusterSpecifierPlugin_extension(
cluster_specifier_plugin[i]);
if (typed_extension_config == nullptr) {
errors->AddError("field not present");
continue;
}
std::string name = UpbStringToStdString(
envoy_config_core_v3_TypedExtensionConfig_name(typed_extension_config));
if (cluster_specifier_plugin_map.find(name) !=
cluster_specifier_plugin_map.end()) {
ValidationErrors::ScopedField field(errors, ".name");
errors->AddError(absl::StrCat("duplicate name \"", name, "\""));
} else {
// Add a sentinel entry in case we encounter an error later, just so we
// don't generate duplicate errors for each route that uses this plugin.
cluster_specifier_plugin_map[name] = "<sentinel>";
}
ValidationErrors::ScopedField field2(errors, ".typed_config");
const google_protobuf_Any* any =
envoy_config_core_v3_TypedExtensionConfig_typed_config(
typed_extension_config);
auto extension = ExtractXdsExtension(context, any, errors);
if (!extension.has_value()) continue;
const XdsClusterSpecifierPluginImpl* cluster_specifier_plugin_impl =
cluster_specifier_plugin_registry.GetPluginForType(extension->type);
if (cluster_specifier_plugin_impl == nullptr) {
if (is_optional) {
// Empty string indicates an optional plugin.
// This is used later when validating routes, and since we will skip
// any routes that refer to this plugin, we won't wind up including
// this plugin in the resource that we return to the watcher.
cluster_specifier_plugin_map[std::move(name)] = "";
} else {
// Not optional, report error.
errors->AddError("unsupported ClusterSpecifierPlugin type");
}
continue;
}
const size_t original_error_size = errors->size();
Json lb_policy_config =
cluster_specifier_plugin_impl->GenerateLoadBalancingPolicyConfig(
std::move(*extension), context.arena, context.symtab, errors);
if (errors->size() != original_error_size) continue;
auto config =
CoreConfiguration::Get().lb_policy_registry().ParseLoadBalancingConfig(
lb_policy_config);
if (!config.ok()) {
errors->AddError(absl::StrCat(
"ClusterSpecifierPlugin returned invalid LB policy config: ",
config.status().message()));
} else {
cluster_specifier_plugin_map[std::move(name)] =
JsonDump(lb_policy_config);
}
}
return cluster_specifier_plugin_map;
}
absl::optional<StringMatcher> RoutePathMatchParse(
const envoy_config_route_v3_RouteMatch* match, ValidationErrors* errors) {
bool case_sensitive = true;
auto* case_sensitive_ptr =
envoy_config_route_v3_RouteMatch_case_sensitive(match);
if (case_sensitive_ptr != nullptr) {
case_sensitive = google_protobuf_BoolValue_value(case_sensitive_ptr);
}
StringMatcher::Type type;
std::string match_string;
if (envoy_config_route_v3_RouteMatch_has_prefix(match)) {
absl::string_view prefix =
UpbStringToAbsl(envoy_config_route_v3_RouteMatch_prefix(match));
// For any prefix that cannot match a path of the form "/service/method",
// ignore the route.
if (!prefix.empty()) {
// Does not start with a slash.
if (prefix[0] != '/') return absl::nullopt;
std::vector<absl::string_view> prefix_elements =
absl::StrSplit(prefix.substr(1), absl::MaxSplits('/', 2));
// More than 2 slashes.
if (prefix_elements.size() > 2) return absl::nullopt;
// Two consecutive slashes.
if (prefix_elements.size() == 2 && prefix_elements[0].empty()) {
return absl::nullopt;
}
}
type = StringMatcher::Type::kPrefix;
match_string = std::string(prefix);
} else if (envoy_config_route_v3_RouteMatch_has_path(match)) {
absl::string_view path =
UpbStringToAbsl(envoy_config_route_v3_RouteMatch_path(match));
// For any path not of the form "/service/method", ignore the route.
// Empty path.
if (path.empty()) return absl::nullopt;
// Does not start with a slash.
if (path[0] != '/') return absl::nullopt;
std::vector<absl::string_view> path_elements =
absl::StrSplit(path.substr(1), absl::MaxSplits('/', 2));
// Number of slashes does not equal 2.
if (path_elements.size() != 2) return absl::nullopt;
// Empty service name.
if (path_elements[0].empty()) return absl::nullopt;
// Empty method name.
if (path_elements[1].empty()) return absl::nullopt;
type = StringMatcher::Type::kExact;
match_string = std::string(path);
} else if (envoy_config_route_v3_RouteMatch_has_safe_regex(match)) {
const envoy_type_matcher_v3_RegexMatcher* regex_matcher =
envoy_config_route_v3_RouteMatch_safe_regex(match);
CHECK_NE(regex_matcher, nullptr);
type = StringMatcher::Type::kSafeRegex;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher));
} else {
errors->AddError("invalid path specifier");
return absl::nullopt;
}
absl::StatusOr<StringMatcher> string_matcher =
StringMatcher::Create(type, match_string, case_sensitive);
if (!string_matcher.ok()) {
errors->AddError(absl::StrCat("error creating path matcher: ",
string_matcher.status().message()));
return absl::nullopt;
}
return std::move(*string_matcher);
}
void RouteHeaderMatchersParse(const envoy_config_route_v3_RouteMatch* match,
XdsRouteConfigResource::Route* route,
ValidationErrors* errors) {
size_t size;
const envoy_config_route_v3_HeaderMatcher* const* headers =
envoy_config_route_v3_RouteMatch_headers(match, &size);
for (size_t i = 0; i < size; ++i) {
ValidationErrors::ScopedField field(errors,
absl::StrCat(".headers[", i, "]"));
const envoy_config_route_v3_HeaderMatcher* header = headers[i];
CHECK_NE(header, nullptr);
const std::string name =
UpbStringToStdString(envoy_config_route_v3_HeaderMatcher_name(header));
HeaderMatcher::Type type;
std::string match_string;
int64_t range_start = 0;
int64_t range_end = 0;
bool present_match = false;
bool case_sensitive = true;
if (envoy_config_route_v3_HeaderMatcher_has_exact_match(header)) {
type = HeaderMatcher::Type::kExact;
match_string = UpbStringToStdString(
envoy_config_route_v3_HeaderMatcher_exact_match(header));
} else if (envoy_config_route_v3_HeaderMatcher_has_prefix_match(header)) {
type = HeaderMatcher::Type::kPrefix;
match_string = UpbStringToStdString(
envoy_config_route_v3_HeaderMatcher_prefix_match(header));
} else if (envoy_config_route_v3_HeaderMatcher_has_suffix_match(header)) {
type = HeaderMatcher::Type::kSuffix;
match_string = UpbStringToStdString(
envoy_config_route_v3_HeaderMatcher_suffix_match(header));
} else if (envoy_config_route_v3_HeaderMatcher_has_contains_match(header)) {
type = HeaderMatcher::Type::kContains;
match_string = UpbStringToStdString(
envoy_config_route_v3_HeaderMatcher_contains_match(header));
} else if (envoy_config_route_v3_HeaderMatcher_has_safe_regex_match(
header)) {
const envoy_type_matcher_v3_RegexMatcher* regex_matcher =
envoy_config_route_v3_HeaderMatcher_safe_regex_match(header);
CHECK_NE(regex_matcher, nullptr);
type = HeaderMatcher::Type::kSafeRegex;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher));
} else if (envoy_config_route_v3_HeaderMatcher_has_range_match(header)) {
type = HeaderMatcher::Type::kRange;
const envoy_type_v3_Int64Range* range_matcher =
envoy_config_route_v3_HeaderMatcher_range_match(header);
CHECK_NE(range_matcher, nullptr);
range_start = envoy_type_v3_Int64Range_start(range_matcher);
range_end = envoy_type_v3_Int64Range_end(range_matcher);
} else if (envoy_config_route_v3_HeaderMatcher_has_present_match(header)) {
type = HeaderMatcher::Type::kPresent;
present_match = envoy_config_route_v3_HeaderMatcher_present_match(header);
} else if (envoy_config_route_v3_HeaderMatcher_has_string_match(header)) {
ValidationErrors::ScopedField field(errors, ".string_match");
const auto* matcher =
envoy_config_route_v3_HeaderMatcher_string_match(header);
CHECK_NE(matcher, nullptr);
if (envoy_type_matcher_v3_StringMatcher_has_exact(matcher)) {
type = HeaderMatcher::Type::kExact;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_StringMatcher_exact(matcher));
} else if (envoy_type_matcher_v3_StringMatcher_has_prefix(matcher)) {
type = HeaderMatcher::Type::kPrefix;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_StringMatcher_prefix(matcher));
} else if (envoy_type_matcher_v3_StringMatcher_has_suffix(matcher)) {
type = HeaderMatcher::Type::kSuffix;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_StringMatcher_suffix(matcher));
} else if (envoy_type_matcher_v3_StringMatcher_has_contains(matcher)) {
type = HeaderMatcher::Type::kContains;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_StringMatcher_contains(matcher));
} else if (envoy_type_matcher_v3_StringMatcher_has_safe_regex(matcher)) {
type = HeaderMatcher::Type::kSafeRegex;
const auto* regex_matcher =
envoy_type_matcher_v3_StringMatcher_safe_regex(matcher);
CHECK_NE(regex_matcher, nullptr);
match_string = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher));
} else {
errors->AddError("invalid string matcher");
continue;
}
case_sensitive =
!envoy_type_matcher_v3_StringMatcher_ignore_case(matcher);
} else {
errors->AddError("invalid header matcher");
continue;
}
bool invert_match =
envoy_config_route_v3_HeaderMatcher_invert_match(header);
absl::StatusOr<HeaderMatcher> header_matcher =
HeaderMatcher::Create(name, type, match_string, range_start, range_end,
present_match, invert_match, case_sensitive);
if (!header_matcher.ok()) {
errors->AddError(absl::StrCat("cannot create header matcher: ",
header_matcher.status().message()));
} else {
route->matchers.header_matchers.emplace_back(std::move(*header_matcher));
}
}
}
void RouteRuntimeFractionParse(const envoy_config_route_v3_RouteMatch* match,
XdsRouteConfigResource::Route* route,
ValidationErrors* errors) {
const envoy_config_core_v3_RuntimeFractionalPercent* runtime_fraction =
envoy_config_route_v3_RouteMatch_runtime_fraction(match);
if (runtime_fraction != nullptr) {
const envoy_type_v3_FractionalPercent* fraction =
envoy_config_core_v3_RuntimeFractionalPercent_default_value(
runtime_fraction);
if (fraction != nullptr) {
uint32_t numerator = envoy_type_v3_FractionalPercent_numerator(fraction);
const uint32_t denominator =
envoy_type_v3_FractionalPercent_denominator(fraction);
// Normalize to million.
switch (denominator) {
case envoy_type_v3_FractionalPercent_HUNDRED:
numerator *= 10000;
break;
case envoy_type_v3_FractionalPercent_TEN_THOUSAND:
numerator *= 100;
break;
case envoy_type_v3_FractionalPercent_MILLION:
break;
default: {
ValidationErrors::ScopedField field(
errors, ".runtime_fraction.default_value.denominator");
errors->AddError("unknown denominator type");
return;
}
}
route->matchers.fraction_per_million = numerator;
}
}
}
template <typename ParentType, typename EntryType>
XdsRouteConfigResource::TypedPerFilterConfig ParseTypedPerFilterConfig(
const XdsResourceType::DecodeContext& context, const ParentType* parent,
const EntryType* (*entry_func)(const ParentType*, size_t*),
upb_StringView (*key_func)(const EntryType*),
const google_protobuf_Any* (*value_func)(const EntryType*),
ValidationErrors* errors) {
XdsRouteConfigResource::TypedPerFilterConfig typed_per_filter_config;
size_t filter_it = kUpb_Map_Begin;
while (true) {
const auto* filter_entry = entry_func(parent, &filter_it);
if (filter_entry == nullptr) break;
absl::string_view key = UpbStringToAbsl(key_func(filter_entry));
ValidationErrors::ScopedField field(errors, absl::StrCat("[", key, "]"));
if (key.empty()) errors->AddError("filter name must be non-empty");
const google_protobuf_Any* any = value_func(filter_entry);
auto extension = ExtractXdsExtension(context, any, errors);
if (!extension.has_value()) continue;
auto* extension_to_use = &*extension;
absl::optional<XdsExtension> nested_extension;
bool is_optional = false;
if (extension->type == "envoy.config.route.v3.FilterConfig") {
absl::string_view* serialized_config =
absl::get_if<absl::string_view>(&extension->value);
if (serialized_config == nullptr) {
errors->AddError("could not parse FilterConfig");
continue;
}
const auto* filter_config = envoy_config_route_v3_FilterConfig_parse(
serialized_config->data(), serialized_config->size(), context.arena);
if (filter_config == nullptr) {
errors->AddError("could not parse FilterConfig");
continue;
}
is_optional =
envoy_config_route_v3_FilterConfig_is_optional(filter_config);
any = envoy_config_route_v3_FilterConfig_config(filter_config);
extension->validation_fields.emplace_back(errors, ".config");
nested_extension = ExtractXdsExtension(context, any, errors);
if (!nested_extension.has_value()) continue;
extension_to_use = &*nested_extension;
}
const auto& http_filter_registry =
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap())
.http_filter_registry();
const XdsHttpFilterImpl* filter_impl =
http_filter_registry.GetFilterForType(extension_to_use->type);
if (filter_impl == nullptr) {
if (!is_optional) errors->AddError("unsupported filter type");
continue;
}
absl::optional<XdsHttpFilterImpl::FilterConfig> filter_config =
filter_impl->GenerateFilterConfigOverride(
context, std::move(*extension_to_use), errors);
if (filter_config.has_value()) {
typed_per_filter_config[std::string(key)] = std::move(*filter_config);
}
}
return typed_per_filter_config;
}
XdsRouteConfigResource::RetryPolicy RetryPolicyParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_RetryPolicy* retry_policy_proto,
ValidationErrors* errors) {
XdsRouteConfigResource::RetryPolicy retry_policy;
auto retry_on = UpbStringToStdString(
envoy_config_route_v3_RetryPolicy_retry_on(retry_policy_proto));
std::vector<absl::string_view> codes = absl::StrSplit(retry_on, ',');
for (const auto& code : codes) {
if (code == "cancelled") {
retry_policy.retry_on.Add(GRPC_STATUS_CANCELLED);
} else if (code == "deadline-exceeded") {
retry_policy.retry_on.Add(GRPC_STATUS_DEADLINE_EXCEEDED);
} else if (code == "internal") {
retry_policy.retry_on.Add(GRPC_STATUS_INTERNAL);
} else if (code == "resource-exhausted") {
retry_policy.retry_on.Add(GRPC_STATUS_RESOURCE_EXHAUSTED);
} else if (code == "unavailable") {
retry_policy.retry_on.Add(GRPC_STATUS_UNAVAILABLE);
} else {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(INFO) << "Unsupported retry_on policy " << code;
}
}
}
const google_protobuf_UInt32Value* num_retries =
envoy_config_route_v3_RetryPolicy_num_retries(retry_policy_proto);
if (num_retries != nullptr) {
uint32_t num_retries_value = google_protobuf_UInt32Value_value(num_retries);
if (num_retries_value == 0) {
ValidationErrors::ScopedField field(errors, ".num_retries");
errors->AddError("must be greater than 0");
} else {
retry_policy.num_retries = num_retries_value;
}
} else {
retry_policy.num_retries = 1;
}
const envoy_config_route_v3_RetryPolicy_RetryBackOff* backoff =
envoy_config_route_v3_RetryPolicy_retry_back_off(retry_policy_proto);
if (backoff != nullptr) {
ValidationErrors::ScopedField field(errors, ".retry_back_off");
{
ValidationErrors::ScopedField field(errors, ".base_interval");
const google_protobuf_Duration* base_interval =
envoy_config_route_v3_RetryPolicy_RetryBackOff_base_interval(backoff);
if (base_interval == nullptr) {
errors->AddError("field not present");
} else {
retry_policy.retry_back_off.base_interval =
ParseDuration(base_interval, errors);
}
}
{
ValidationErrors::ScopedField field(errors, ".max_interval");
const google_protobuf_Duration* max_interval =
envoy_config_route_v3_RetryPolicy_RetryBackOff_max_interval(backoff);
Duration max;
if (max_interval != nullptr) {
max = ParseDuration(max_interval, errors);
} else {
// if max interval is not set, it is 10x the base.
max = 10 * retry_policy.retry_back_off.base_interval;
}
retry_policy.retry_back_off.max_interval = max;
}
} else {
retry_policy.retry_back_off.base_interval = Duration::Milliseconds(25);
retry_policy.retry_back_off.max_interval = Duration::Milliseconds(250);
}
return retry_policy;
}
absl::optional<XdsRouteConfigResource::Route::RouteAction> RouteActionParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_RouteAction* route_action_proto,
const std::map<std::string /*cluster_specifier_plugin_name*/,
std::string /*LB policy config*/>&
cluster_specifier_plugin_map,
ValidationErrors* errors) {
XdsRouteConfigResource::Route::RouteAction route_action;
// grpc_timeout_header_max or max_stream_duration
const auto* max_stream_duration =
envoy_config_route_v3_RouteAction_max_stream_duration(route_action_proto);
if (max_stream_duration != nullptr) {
ValidationErrors::ScopedField field(errors, ".max_stream_duration");
const google_protobuf_Duration* duration =
envoy_config_route_v3_RouteAction_MaxStreamDuration_grpc_timeout_header_max(
max_stream_duration);
if (duration != nullptr) {
ValidationErrors::ScopedField field(errors, ".grpc_timeout_header_max");
route_action.max_stream_duration = ParseDuration(duration, errors);
} else {
duration =
envoy_config_route_v3_RouteAction_MaxStreamDuration_max_stream_duration(
max_stream_duration);
if (duration != nullptr) {
ValidationErrors::ScopedField field(errors, ".max_stream_duration");
route_action.max_stream_duration = ParseDuration(duration, errors);
}
}
}
// hash_policy
size_t size = 0;
const envoy_config_route_v3_RouteAction_HashPolicy* const* hash_policies =
envoy_config_route_v3_RouteAction_hash_policy(route_action_proto, &size);
for (size_t i = 0; i < size; ++i) {
ValidationErrors::ScopedField field(errors,
absl::StrCat(".hash_policy[", i, "]"));
const auto* hash_policy = hash_policies[i];
XdsRouteConfigResource::Route::RouteAction::HashPolicy policy;
policy.terminal =
envoy_config_route_v3_RouteAction_HashPolicy_terminal(hash_policy);
const envoy_config_route_v3_RouteAction_HashPolicy_Header* header;
const envoy_config_route_v3_RouteAction_HashPolicy_FilterState*
filter_state;
if ((header = envoy_config_route_v3_RouteAction_HashPolicy_header(
hash_policy)) != nullptr) {
// header
ValidationErrors::ScopedField field(errors, ".header");
XdsRouteConfigResource::Route::RouteAction::HashPolicy::Header
header_policy;
header_policy.header_name = UpbStringToStdString(
envoy_config_route_v3_RouteAction_HashPolicy_Header_header_name(
header));
if (header_policy.header_name.empty()) {
ValidationErrors::ScopedField field(errors, ".header_name");
errors->AddError("must be non-empty");
}
// regex_rewrite
const auto* regex_rewrite =
envoy_config_route_v3_RouteAction_HashPolicy_Header_regex_rewrite(
header);
if (regex_rewrite != nullptr) {
ValidationErrors::ScopedField field(errors, ".regex_rewrite.pattern");
const auto* pattern =
envoy_type_matcher_v3_RegexMatchAndSubstitute_pattern(
regex_rewrite);
if (pattern == nullptr) {
errors->AddError("field not present");
continue;
}
ValidationErrors::ScopedField field2(errors, ".regex");
std::string regex = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(pattern));
if (regex.empty()) {
errors->AddError("field not present");
continue;
}
RE2::Options options;
header_policy.regex = std::make_unique<RE2>(regex, options);
if (!header_policy.regex->ok()) {
errors->AddError(absl::StrCat("errors compiling regex: ",
header_policy.regex->error()));
continue;
}
header_policy.regex_substitution = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatchAndSubstitute_substitution(
regex_rewrite));
}
policy.policy = std::move(header_policy);
} else if ((filter_state =
envoy_config_route_v3_RouteAction_HashPolicy_filter_state(
hash_policy)) != nullptr) {
// filter_state
std::string key = UpbStringToStdString(
envoy_config_route_v3_RouteAction_HashPolicy_FilterState_key(
filter_state));
if (key != "io.grpc.channel_id") continue;
policy.policy =
XdsRouteConfigResource::Route::RouteAction::HashPolicy::ChannelId();
} else {
// Unsupported hash policy type, ignore it.
continue;
}
route_action.hash_policies.emplace_back(std::move(policy));
}
// Get retry policy
const envoy_config_route_v3_RetryPolicy* retry_policy =
envoy_config_route_v3_RouteAction_retry_policy(route_action_proto);
if (retry_policy != nullptr) {
ValidationErrors::ScopedField field(errors, ".retry_policy");
route_action.retry_policy = RetryPolicyParse(context, retry_policy, errors);
}
// Parse cluster specifier, which is one of several options.
if (envoy_config_route_v3_RouteAction_has_cluster(route_action_proto)) {
// Cluster name.
std::string cluster_name = UpbStringToStdString(
envoy_config_route_v3_RouteAction_cluster(route_action_proto));
if (cluster_name.empty()) {
ValidationErrors::ScopedField field(errors, ".cluster");
errors->AddError("must be non-empty");
}
route_action.action =
XdsRouteConfigResource::Route::RouteAction::ClusterName{
std::move(cluster_name)};
} else if (envoy_config_route_v3_RouteAction_has_weighted_clusters(
route_action_proto)) {
// WeightedClusters.
ValidationErrors::ScopedField field(errors, ".weighted_clusters");
const envoy_config_route_v3_WeightedCluster* weighted_clusters_proto =
envoy_config_route_v3_RouteAction_weighted_clusters(route_action_proto);
CHECK_NE(weighted_clusters_proto, nullptr);
std::vector<XdsRouteConfigResource::Route::RouteAction::ClusterWeight>
action_weighted_clusters;
uint64_t total_weight = 0;
size_t clusters_size;
const envoy_config_route_v3_WeightedCluster_ClusterWeight* const* clusters =
envoy_config_route_v3_WeightedCluster_clusters(weighted_clusters_proto,
&clusters_size);
for (size_t i = 0; i < clusters_size; ++i) {
ValidationErrors::ScopedField field(errors,
absl::StrCat(".clusters[", i, "]"));
const auto* cluster_proto = clusters[i];
XdsRouteConfigResource::Route::RouteAction::ClusterWeight cluster;
// typed_per_filter_config
{
ValidationErrors::ScopedField field(errors, ".typed_per_filter_config");
cluster.typed_per_filter_config = ParseTypedPerFilterConfig<
envoy_config_route_v3_WeightedCluster_ClusterWeight,
envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry>(
context, cluster_proto,
envoy_config_route_v3_WeightedCluster_ClusterWeight_typed_per_filter_config_next,
envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry_key,
envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry_value,
errors);
}
// name
cluster.name = UpbStringToStdString(
envoy_config_route_v3_WeightedCluster_ClusterWeight_name(
cluster_proto));
if (cluster.name.empty()) {
ValidationErrors::ScopedField field(errors, ".name");
errors->AddError("must be non-empty");
}
// weight
const google_protobuf_UInt32Value* weight_proto =
envoy_config_route_v3_WeightedCluster_ClusterWeight_weight(
cluster_proto);
if (weight_proto == nullptr) {
ValidationErrors::ScopedField field(errors, ".weight");
errors->AddError("field not present");
} else {
cluster.weight = google_protobuf_UInt32Value_value(weight_proto);
if (cluster.weight == 0) continue;
total_weight += cluster.weight;
}
// Add entry to WeightedClusters.
action_weighted_clusters.emplace_back(std::move(cluster));
}
if (action_weighted_clusters.empty()) {
errors->AddError("no valid clusters specified");
} else if (total_weight > std::numeric_limits<uint32_t>::max()) {
errors->AddError("sum of cluster weights exceeds uint32 max");
}
route_action.action = std::move(action_weighted_clusters);
} else if (XdsRlsEnabled() &&
envoy_config_route_v3_RouteAction_has_cluster_specifier_plugin(
route_action_proto)) {
// ClusterSpecifierPlugin
ValidationErrors::ScopedField field(errors, ".cluster_specifier_plugin");
std::string plugin_name = UpbStringToStdString(
envoy_config_route_v3_RouteAction_cluster_specifier_plugin(
route_action_proto));
if (plugin_name.empty()) {
errors->AddError("must be non-empty");
return absl::nullopt;
}
auto it = cluster_specifier_plugin_map.find(plugin_name);
if (it == cluster_specifier_plugin_map.end()) {
errors->AddError(absl::StrCat("unknown cluster specifier plugin name \"",
plugin_name, "\""));
} else {
// If the cluster specifier config is empty, that means that the
// plugin was unsupported but optional. In that case, skip this route.
if (it->second.empty()) return absl::nullopt;
}
route_action.action =
XdsRouteConfigResource::Route::RouteAction::ClusterSpecifierPluginName{
std::move(plugin_name)};
} else {
// Not a supported cluster specifier, so ignore this route.
return absl::nullopt;
}
return route_action;
}
absl::optional<XdsRouteConfigResource::Route> ParseRoute(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_Route* route_proto,
const absl::optional<XdsRouteConfigResource::RetryPolicy>&
virtual_host_retry_policy,
const XdsRouteConfigResource::ClusterSpecifierPluginMap&
cluster_specifier_plugin_map,
std::set<absl::string_view>* cluster_specifier_plugins_not_seen,
ValidationErrors* errors) {
XdsRouteConfigResource::Route route;
// Parse route match.
{
ValidationErrors::ScopedField field(errors, ".match");
const auto* match = envoy_config_route_v3_Route_match(route_proto);
if (match == nullptr) {
errors->AddError("field not present");
return absl::nullopt;
}
// Skip routes with query_parameters set.
size_t query_parameters_size;
static_cast<void>(envoy_config_route_v3_RouteMatch_query_parameters(
match, &query_parameters_size));
if (query_parameters_size > 0) return absl::nullopt;
// Parse matchers.
auto path_matcher = RoutePathMatchParse(match, errors);
if (!path_matcher.has_value()) return absl::nullopt;
route.matchers.path_matcher = std::move(*path_matcher);
RouteHeaderMatchersParse(match, &route, errors);
RouteRuntimeFractionParse(match, &route, errors);
}
// Parse route action.
const envoy_config_route_v3_RouteAction* route_action_proto =
envoy_config_route_v3_Route_route(route_proto);
if (route_action_proto != nullptr) {
ValidationErrors::ScopedField field(errors, ".route");
auto route_action = RouteActionParse(context, route_action_proto,
cluster_specifier_plugin_map, errors);
if (!route_action.has_value()) return absl::nullopt;
// If the route does not have a retry policy but the vhost does,
// use the vhost retry policy for this route.
if (!route_action->retry_policy.has_value()) {
route_action->retry_policy = virtual_host_retry_policy;
}
// Mark off plugins used in route action.
auto* cluster_specifier_action = absl::get_if<
XdsRouteConfigResource::Route::RouteAction::ClusterSpecifierPluginName>(
&route_action->action);
if (cluster_specifier_action != nullptr) {
cluster_specifier_plugins_not_seen->erase(
cluster_specifier_action->cluster_specifier_plugin_name);
}
route.action = std::move(*route_action);
} else if (envoy_config_route_v3_Route_has_non_forwarding_action(
route_proto)) {
route.action = XdsRouteConfigResource::Route::NonForwardingAction();
} else {
// Leave route.action initialized to UnknownAction (its default).
}
// Parse typed_per_filter_config.
{
ValidationErrors::ScopedField field(errors, ".typed_per_filter_config");
route.typed_per_filter_config = ParseTypedPerFilterConfig<
envoy_config_route_v3_Route,
envoy_config_route_v3_Route_TypedPerFilterConfigEntry>(
context, route_proto,
envoy_config_route_v3_Route_typed_per_filter_config_next,
envoy_config_route_v3_Route_TypedPerFilterConfigEntry_key,
envoy_config_route_v3_Route_TypedPerFilterConfigEntry_value, errors);
}
return route;
}
} // namespace
std::shared_ptr<const XdsRouteConfigResource> XdsRouteConfigResourceParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_RouteConfiguration* route_config,
ValidationErrors* errors) {
auto rds_update = std::make_shared<XdsRouteConfigResource>();
// Get the cluster spcifier plugin map.
if (XdsRlsEnabled()) {
rds_update->cluster_specifier_plugin_map =
ClusterSpecifierPluginParse(context, route_config, errors);
}
// Build a set of configured cluster_specifier_plugin names to make sure
// each is actually referenced by a route action.
std::set<absl::string_view> cluster_specifier_plugins_not_seen;
for (auto& plugin : rds_update->cluster_specifier_plugin_map) {
cluster_specifier_plugins_not_seen.emplace(plugin.first);
}
// Get the virtual hosts.
size_t num_virtual_hosts;
const envoy_config_route_v3_VirtualHost* const* virtual_hosts =
envoy_config_route_v3_RouteConfiguration_virtual_hosts(
route_config, &num_virtual_hosts);
for (size_t i = 0; i < num_virtual_hosts; ++i) {
ValidationErrors::ScopedField field(
errors, absl::StrCat(".virtual_hosts[", i, "]"));
rds_update->virtual_hosts.emplace_back();
XdsRouteConfigResource::VirtualHost& vhost =
rds_update->virtual_hosts.back();
// Parse domains.
size_t domain_size;
upb_StringView const* domains = envoy_config_route_v3_VirtualHost_domains(
virtual_hosts[i], &domain_size);
for (size_t j = 0; j < domain_size; ++j) {
std::string domain_pattern = UpbStringToStdString(domains[j]);
if (!XdsRouting::IsValidDomainPattern(domain_pattern)) {
ValidationErrors::ScopedField field(errors,
absl::StrCat(".domains[", j, "]"));
errors->AddError(
absl::StrCat("invalid domain pattern \"", domain_pattern, "\""));
}
vhost.domains.emplace_back(std::move(domain_pattern));
}
if (vhost.domains.empty()) {
ValidationErrors::ScopedField field(errors, ".domains");
errors->AddError("must be non-empty");
}
// Parse typed_per_filter_config.
{
ValidationErrors::ScopedField field(errors, ".typed_per_filter_config");
vhost.typed_per_filter_config = ParseTypedPerFilterConfig<
envoy_config_route_v3_VirtualHost,
envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry>(
context, virtual_hosts[i],
envoy_config_route_v3_VirtualHost_typed_per_filter_config_next,
envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry_key,
envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry_value,
errors);
}
// Parse retry policy.
absl::optional<XdsRouteConfigResource::RetryPolicy>
virtual_host_retry_policy;
const envoy_config_route_v3_RetryPolicy* retry_policy =
envoy_config_route_v3_VirtualHost_retry_policy(virtual_hosts[i]);
if (retry_policy != nullptr) {
ValidationErrors::ScopedField field(errors, ".retry_policy");
virtual_host_retry_policy =
RetryPolicyParse(context, retry_policy, errors);
}
// Parse routes.
ValidationErrors::ScopedField field2(errors, ".routes");
size_t num_routes;
const envoy_config_route_v3_Route* const* routes =
envoy_config_route_v3_VirtualHost_routes(virtual_hosts[i], &num_routes);
for (size_t j = 0; j < num_routes; ++j) {
ValidationErrors::ScopedField field(errors, absl::StrCat("[", j, "]"));
auto route = ParseRoute(context, routes[j], virtual_host_retry_policy,
rds_update->cluster_specifier_plugin_map,
&cluster_specifier_plugins_not_seen, errors);
if (route.has_value()) vhost.routes.emplace_back(std::move(*route));
}
}
// For cluster specifier plugins that were not used in any route action,
// delete them from the update, since they will never be used.
for (auto& unused_plugin : cluster_specifier_plugins_not_seen) {
rds_update->cluster_specifier_plugin_map.erase(std::string(unused_plugin));
}
return rds_update;
}
//
// XdsRouteConfigResourceType
//
namespace {
void MaybeLogRouteConfiguration(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_RouteConfiguration* route_config) {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) {
const upb_MessageDef* msg_type =
envoy_config_route_v3_RouteConfiguration_getmsgdef(context.symtab);
char buf[10240];
upb_TextEncode(reinterpret_cast<const upb_Message*>(route_config), msg_type,
nullptr, 0, buf, sizeof(buf));
VLOG(2) << "[xds_client " << context.client
<< "] RouteConfiguration: " << buf;
}
}
} // namespace
XdsResourceType::DecodeResult XdsRouteConfigResourceType::Decode(
const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const {
DecodeResult result;
// Parse serialized proto.
auto* resource = envoy_config_route_v3_RouteConfiguration_parse(
serialized_resource.data(), serialized_resource.size(), context.arena);
if (resource == nullptr) {
result.resource =
absl::InvalidArgumentError("Can't parse RouteConfiguration resource.");
return result;
}
MaybeLogRouteConfiguration(context, resource);
// Validate resource.
result.name = UpbStringToStdString(
envoy_config_route_v3_RouteConfiguration_name(resource));
ValidationErrors errors;
auto rds_update = XdsRouteConfigResourceParse(context, resource, &errors);
if (!errors.ok()) {
absl::Status status =
errors.status(absl::StatusCode::kInvalidArgument,
"errors validating RouteConfiguration resource");
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(ERROR) << "[xds_client " << context.client
<< "] invalid RouteConfiguration " << *result.name << ": "
<< status;
}
result.resource = std::move(status);
} else {
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) {
LOG(INFO) << "[xds_client " << context.client
<< "] parsed RouteConfiguration " << *result.name << ": "
<< rds_update->ToString();
}
result.resource = std::move(rds_update);
}
return result;
}
} // namespace grpc_core

@ -0,0 +1,80 @@
//
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_ROUTE_CONFIG_PARSER_H
#define GRPC_SRC_CORE_XDS_GRPC_XDS_ROUTE_CONFIG_PARSER_H
#include <stdint.h>
#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "envoy/config/route/v3/route.upb.h"
#include "envoy/config/route/v3/route.upbdefs.h"
#include "re2/re2.h"
#include "upb/reflection/def.h"
#include <grpc/support/port_platform.h>
#include "src/core/lib/channel/status_util.h"
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/lib/matchers/matchers.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_cluster_specifier_plugin.h"
#include "src/core/xds/grpc/xds_http_filter.h"
#include "src/core/xds/grpc/xds_route_config.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
#include "src/core/xds/xds_client/xds_resource_type_impl.h"
namespace grpc_core {
std::shared_ptr<const XdsRouteConfigResource> XdsRouteConfigResourceParse(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_RouteConfiguration* route_config,
ValidationErrors* errors);
class XdsRouteConfigResourceType final
: public XdsResourceTypeImpl<XdsRouteConfigResourceType,
XdsRouteConfigResource> {
public:
absl::string_view type_url() const override {
return "envoy.config.route.v3.RouteConfiguration";
}
DecodeResult Decode(const XdsResourceType::DecodeContext& context,
absl::string_view serialized_resource) const override;
void InitUpbSymtab(XdsClient* xds_client,
upb_DefPool* symtab) const override {
envoy_config_route_v3_RouteConfiguration_getmsgdef(symtab);
const auto& cluster_specifier_plugin_registry =
static_cast<const GrpcXdsBootstrap&>(xds_client->bootstrap())
.cluster_specifier_plugin_registry();
cluster_specifier_plugin_registry.PopulateSymtab(symtab);
}
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_ROUTE_CONFIG_PARSER_H

@ -36,7 +36,7 @@
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/matchers/matchers.h"
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/grpc/xds_http_filter.h"
namespace grpc_core {

@ -33,7 +33,7 @@
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/transport/metadata_batch.h"
#include "src/core/xds/grpc/xds_http_filters.h"
#include "src/core/xds/grpc/xds_http_filter_registry.h"
#include "src/core/xds/grpc/xds_listener.h"
#include "src/core/xds/grpc/xds_route_config.h"

@ -0,0 +1,152 @@
//
// Copyright 2019 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "src/core/xds/grpc/xds_server_grpc.h"
#include <stdlib.h>
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "src/core/lib/config/core_configuration.h"
#include "src/core/util/json/json_reader.h"
#include "src/core/util/json/json_writer.h"
namespace grpc_core {
namespace {
constexpr absl::string_view kServerFeatureIgnoreResourceDeletion =
"ignore_resource_deletion";
} // namespace
bool GrpcXdsServer::IgnoreResourceDeletion() const {
return server_features_.find(std::string(
kServerFeatureIgnoreResourceDeletion)) != server_features_.end();
}
bool GrpcXdsServer::Equals(const XdsServer& other) const {
const auto& o = static_cast<const GrpcXdsServer&>(other);
return (server_uri_ == o.server_uri_ &&
channel_creds_config_->type() == o.channel_creds_config_->type() &&
channel_creds_config_->Equals(*o.channel_creds_config_) &&
server_features_ == o.server_features_);
}
std::string GrpcXdsServer::Key() const { return JsonDump(ToJson()); }
const JsonLoaderInterface* GrpcXdsServer::JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<GrpcXdsServer>()
.Field("server_uri", &GrpcXdsServer::server_uri_)
.Finish();
return loader;
}
namespace {
struct ChannelCreds {
std::string type;
Json::Object config;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<ChannelCreds>()
.Field("type", &ChannelCreds::type)
.OptionalField("config", &ChannelCreds::config)
.Finish();
return loader;
}
};
} // namespace
void GrpcXdsServer::JsonPostLoad(const Json& json, const JsonArgs& args,
ValidationErrors* errors) {
// Parse "channel_creds".
auto channel_creds_list = LoadJsonObjectField<std::vector<ChannelCreds>>(
json.object(), args, "channel_creds", errors);
if (channel_creds_list.has_value()) {
ValidationErrors::ScopedField field(errors, ".channel_creds");
for (size_t i = 0; i < channel_creds_list->size(); ++i) {
ValidationErrors::ScopedField field(errors, absl::StrCat("[", i, "]"));
auto& creds = (*channel_creds_list)[i];
// Select the first channel creds type that we support, but
// validate all entries.
if (CoreConfiguration::Get().channel_creds_registry().IsSupported(
creds.type)) {
ValidationErrors::ScopedField field(errors, ".config");
auto config =
CoreConfiguration::Get().channel_creds_registry().ParseConfig(
creds.type, Json::FromObject(creds.config), args, errors);
if (channel_creds_config_ == nullptr) {
channel_creds_config_ = std::move(config);
}
}
}
if (channel_creds_config_ == nullptr) {
errors->AddError("no known creds type found");
}
}
// Parse "server_features".
{
ValidationErrors::ScopedField field(errors, ".server_features");
auto it = json.object().find("server_features");
if (it != json.object().end()) {
if (it->second.type() != Json::Type::kArray) {
errors->AddError("is not an array");
} else {
const Json::Array& array = it->second.array();
for (const Json& feature_json : array) {
if (feature_json.type() == Json::Type::kString &&
(feature_json.string() == kServerFeatureIgnoreResourceDeletion)) {
server_features_.insert(feature_json.string());
}
}
}
}
}
}
Json GrpcXdsServer::ToJson() const {
Json::Object channel_creds_json{
{"type", Json::FromString(std::string(channel_creds_config_->type()))},
};
if (channel_creds_config_ != nullptr) {
channel_creds_json["config"] = channel_creds_config_->ToJson();
}
Json::Object json{
{"server_uri", Json::FromString(server_uri_)},
{"channel_creds",
Json::FromArray({Json::FromObject(std::move(channel_creds_json))})},
};
if (!server_features_.empty()) {
Json::Array server_features_json;
for (auto& feature : server_features_) {
server_features_json.emplace_back(Json::FromString(feature));
}
json["server_features"] = Json::FromArray(std::move(server_features_json));
}
return Json::FromObject(std::move(json));
}
} // namespace grpc_core

@ -0,0 +1,61 @@
//
// Copyright 2019 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_SERVER_GRPC_H
#define GRPC_SRC_CORE_XDS_GRPC_XDS_SERVER_GRPC_H
#include <set>
#include <string>
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/lib/security/credentials/channel_creds_registry.h"
#include "src/core/util/json/json.h"
#include "src/core/util/json/json_args.h"
#include "src/core/util/json/json_object_loader.h"
#include "src/core/xds/xds_client/xds_bootstrap.h"
namespace grpc_core {
class GrpcXdsServer final : public XdsBootstrap::XdsServer {
public:
const std::string& server_uri() const override { return server_uri_; }
bool IgnoreResourceDeletion() const override;
bool Equals(const XdsServer& other) const override;
std::string Key() const override;
RefCountedPtr<ChannelCredsConfig> channel_creds_config() const {
return channel_creds_config_;
}
static const JsonLoaderInterface* JsonLoader(const JsonArgs&);
void JsonPostLoad(const Json& json, const JsonArgs& args,
ValidationErrors* errors);
Json ToJson() const;
private:
std::string server_uri_;
RefCountedPtr<ChannelCredsConfig> channel_creds_config_;
std::set<std::string> server_features_;
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_SERVER_GRPC_H

@ -253,8 +253,8 @@ class GrpcXdsTransportFactory::GrpcXdsTransport::StateWatcher final
namespace {
RefCountedPtr<Channel> CreateXdsChannel(
const ChannelArgs& args, const GrpcXdsBootstrap::GrpcXdsServer& server) {
RefCountedPtr<Channel> CreateXdsChannel(const ChannelArgs& args,
const GrpcXdsServer& server) {
RefCountedPtr<grpc_channel_credentials> channel_creds =
CoreConfiguration::Get().channel_creds_registry().CreateChannelCreds(
server.channel_creds_config());
@ -269,9 +269,8 @@ GrpcXdsTransportFactory::GrpcXdsTransport::GrpcXdsTransport(
std::function<void(absl::Status)> on_connectivity_failure,
absl::Status* status)
: factory_(factory) {
channel_ = CreateXdsChannel(
factory->args_,
static_cast<const GrpcXdsBootstrap::GrpcXdsServer&>(server));
channel_ = CreateXdsChannel(factory->args_,
static_cast<const GrpcXdsServer&>(server));
CHECK(channel_ != nullptr);
if (channel_->IsLame()) {
*status = absl::UnavailableError("xds client has a lame channel");

@ -840,18 +840,24 @@ CORE_SOURCE_FILES = [
'src/core/xds/grpc/xds_certificate_provider.cc',
'src/core/xds/grpc/xds_client_grpc.cc',
'src/core/xds/grpc/xds_cluster.cc',
'src/core/xds/grpc/xds_cluster_parser.cc',
'src/core/xds/grpc/xds_cluster_specifier_plugin.cc',
'src/core/xds/grpc/xds_common_types.cc',
'src/core/xds/grpc/xds_common_types_parser.cc',
'src/core/xds/grpc/xds_endpoint.cc',
'src/core/xds/grpc/xds_endpoint_parser.cc',
'src/core/xds/grpc/xds_health_status.cc',
'src/core/xds/grpc/xds_http_fault_filter.cc',
'src/core/xds/grpc/xds_http_filters.cc',
'src/core/xds/grpc/xds_http_filter_registry.cc',
'src/core/xds/grpc/xds_http_rbac_filter.cc',
'src/core/xds/grpc/xds_http_stateful_session_filter.cc',
'src/core/xds/grpc/xds_lb_policy_registry.cc',
'src/core/xds/grpc/xds_listener.cc',
'src/core/xds/grpc/xds_listener_parser.cc',
'src/core/xds/grpc/xds_route_config.cc',
'src/core/xds/grpc/xds_route_config_parser.cc',
'src/core/xds/grpc/xds_routing.cc',
'src/core/xds/grpc/xds_server_grpc.cc',
'src/core/xds/grpc/xds_transport_grpc.cc',
'src/core/xds/xds_client/xds_api.cc',
'src/core/xds/xds_client/xds_bootstrap.cc',

@ -158,6 +158,7 @@ grpc_cc_test(
deps = [
":lb_policy_test_lib",
"//src/core:grpc_lb_policy_xds_override_host",
"//src/core:xds_health_status",
"//test/core/test_util:grpc_test_util",
],
)

@ -36,6 +36,7 @@ grpc_cc_test(
deps = [
"//:gpr",
"//src/core:grpc_xds_client",
"//src/core:xds_server_grpc",
"//test/core/test_util:grpc_test_util",
"//test/core/test_util:scoped_env_var",
],
@ -309,6 +310,7 @@ grpc_cc_test(
"//:gpr",
"//:grpc",
"//src/core:grpc_xds_client",
"//src/core:xds_health_status",
"//src/proto/grpc/testing/xds/v3:aggregate_cluster_proto",
"//src/proto/grpc/testing/xds/v3:cluster_proto",
"//src/proto/grpc/testing/xds/v3:http_protocol_options_proto",
@ -332,6 +334,7 @@ grpc_cc_test(
"//:grpc",
"//src/core:channel_args",
"//src/core:grpc_xds_client",
"//src/core:xds_health_status",
"//src/proto/grpc/testing/xds/v3:endpoint_proto",
"//test/core/test_util:grpc_test_util",
"//test/core/test_util:scoped_env_var",

@ -66,9 +66,8 @@ absl::StatusOr<std::string> ConvertAuditLoggerConfig(
std::string serialized_config = config.SerializeAsString();
upb::Arena arena;
upb::DefPool def_pool;
XdsResourceType::DecodeContext context = {
nullptr, GrpcXdsBootstrap::GrpcXdsServer(), nullptr, def_pool.ptr(),
arena.ptr()};
XdsResourceType::DecodeContext context = {nullptr, GrpcXdsServer(), nullptr,
def_pool.ptr(), arena.ptr()};
auto* upb_config =
envoy_config_rbac_v3_RBAC_AuditLoggingOptions_AuditLoggerConfig_parse(
serialized_config.data(), serialized_config.size(), arena.ptr());

@ -49,6 +49,7 @@
#include "src/core/util/tmpfile.h"
#include "src/core/xds/grpc/certificate_provider_store.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_server_grpc.h"
#include "test/core/test_util/scoped_env_var.h"
#include "test/core/test_util/test_config.h"
@ -57,7 +58,7 @@ namespace testing {
namespace {
MATCHER_P2(EqXdsServer, name, creds_config_type, "equals XdsServer") {
auto* server = static_cast<const GrpcXdsBootstrap::GrpcXdsServer*>(arg);
auto* server = static_cast<const GrpcXdsServer*>(arg);
if (!::testing::ExplainMatchResult(::testing::Ne(nullptr), server,
result_listener)) {
return false;
@ -715,11 +716,10 @@ TEST(XdsBootstrapTest, XdsServerToJsonAndParse) {
" }";
auto json = JsonParse(json_str);
ASSERT_TRUE(json.ok()) << json.status();
auto xds_server = LoadFromJson<GrpcXdsBootstrap::GrpcXdsServer>(*json);
auto xds_server = LoadFromJson<GrpcXdsServer>(*json);
ASSERT_TRUE(xds_server.ok()) << xds_server.status();
Json output = xds_server->ToJson();
auto output_xds_server =
LoadFromJson<GrpcXdsBootstrap::GrpcXdsServer>(output);
auto output_xds_server = LoadFromJson<GrpcXdsServer>(output);
ASSERT_TRUE(output_xds_server.ok()) << output_xds_server.status();
EXPECT_EQ(*xds_server, *output_xds_server);
}

@ -35,9 +35,13 @@
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_cluster.h"
#include "src/core/xds/grpc/xds_cluster_parser.h"
#include "src/core/xds/grpc/xds_endpoint.h"
#include "src/core/xds/grpc/xds_endpoint_parser.h"
#include "src/core/xds/grpc/xds_listener.h"
#include "src/core/xds/grpc/xds_listener_parser.h"
#include "src/core/xds/grpc/xds_route_config.h"
#include "src/core/xds/grpc/xds_route_config_parser.h"
#include "src/core/xds/xds_client/xds_bootstrap.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/libfuzzer/libfuzzer_macro.h"

@ -45,6 +45,7 @@
#include "src/core/util/json/json_writer.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_cluster.h"
#include "src/core/xds/grpc/xds_cluster_parser.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_health_status.h"
#include "src/core/xds/xds_client/xds_bootstrap.h"

@ -47,6 +47,7 @@
#include "src/core/util/json/json_writer.h"
#include "src/core/util/upb_utils.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_common_types_parser.h"
#include "src/core/xds/xds_client/xds_bootstrap.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/core/xds/xds_client/xds_resource_type.h"
@ -196,7 +197,7 @@ class CommonTlsConfigTest : public XdsCommonTypesTest {
upb_proto) {
ValidationErrors errors;
CommonTlsContext common_tls_context =
CommonTlsContext::Parse(decode_context_, upb_proto, &errors);
CommonTlsContextParse(decode_context_, upb_proto, &errors);
if (!errors.ok()) {
return errors.status(absl::StatusCode::kInvalidArgument,
"validation failed");

@ -45,6 +45,7 @@
#include "src/core/resolver/endpoint_addresses.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_endpoint.h"
#include "src/core/xds/grpc/xds_endpoint_parser.h"
#include "src/core/xds/grpc/xds_health_status.h"
#include "src/core/xds/xds_client/xds_bootstrap.h"
#include "src/core/xds/xds_client/xds_client.h"

@ -14,8 +14,6 @@
// limitations under the License.
//
#include "src/core/xds/grpc/xds_http_filters.h"
#include <string>
#include <utility>
#include <vector>
@ -50,6 +48,8 @@
#include "src/core/lib/iomgr/error.h"
#include "src/core/util/json/json_writer.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_http_filter.h"
#include "src/core/xds/grpc/xds_http_filter_registry.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/proto/grpc/testing/xds/v3/address.pb.h"
#include "src/proto/grpc/testing/xds/v3/cookie.pb.h"
@ -143,7 +143,7 @@ class XdsHttpFilterTest : public ::testing::Test {
absl::StripPrefix(type, "type.googleapis.com/"));
}
GrpcXdsBootstrap::GrpcXdsServer xds_server_;
GrpcXdsServer xds_server_;
RefCountedPtr<XdsClient> xds_client_;
upb::DefPool upb_def_pool_;
upb::Arena upb_arena_;

@ -75,9 +75,8 @@ absl::StatusOr<std::string> ConvertXdsPolicy(
std::string serialized_policy = policy.SerializeAsString();
upb::Arena arena;
upb::DefPool def_pool;
XdsResourceType::DecodeContext context = {
nullptr, GrpcXdsBootstrap::GrpcXdsServer(), nullptr, def_pool.ptr(),
arena.ptr()};
XdsResourceType::DecodeContext context = {nullptr, GrpcXdsServer(), nullptr,
def_pool.ptr(), arena.ptr()};
auto* upb_policy = envoy_config_cluster_v3_LoadBalancingPolicy_parse(
serialized_policy.data(), serialized_policy.size(), arena.ptr());
ValidationErrors errors;

@ -49,6 +49,7 @@
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_common_types.h"
#include "src/core/xds/grpc/xds_listener.h"
#include "src/core/xds/grpc/xds_listener_parser.h"
#include "src/core/xds/xds_client/xds_bootstrap.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/core/xds/xds_client/xds_resource_type.h"

@ -53,6 +53,7 @@
#include "src/core/util/json/json_writer.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_route_config.h"
#include "src/core/xds/grpc/xds_route_config_parser.h"
#include "src/core/xds/xds_client/xds_bootstrap.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "src/core/xds/xds_client/xds_resource_type.h"

@ -2981,19 +2981,26 @@ src/core/xds/grpc/xds_client_grpc.cc \
src/core/xds/grpc/xds_client_grpc.h \
src/core/xds/grpc/xds_cluster.cc \
src/core/xds/grpc/xds_cluster.h \
src/core/xds/grpc/xds_cluster_parser.cc \
src/core/xds/grpc/xds_cluster_parser.h \
src/core/xds/grpc/xds_cluster_specifier_plugin.cc \
src/core/xds/grpc/xds_cluster_specifier_plugin.h \
src/core/xds/grpc/xds_common_types.cc \
src/core/xds/grpc/xds_common_types.h \
src/core/xds/grpc/xds_common_types_parser.cc \
src/core/xds/grpc/xds_common_types_parser.h \
src/core/xds/grpc/xds_enabled_server.h \
src/core/xds/grpc/xds_endpoint.cc \
src/core/xds/grpc/xds_endpoint.h \
src/core/xds/grpc/xds_endpoint_parser.cc \
src/core/xds/grpc/xds_endpoint_parser.h \
src/core/xds/grpc/xds_health_status.cc \
src/core/xds/grpc/xds_health_status.h \
src/core/xds/grpc/xds_http_fault_filter.cc \
src/core/xds/grpc/xds_http_fault_filter.h \
src/core/xds/grpc/xds_http_filters.cc \
src/core/xds/grpc/xds_http_filters.h \
src/core/xds/grpc/xds_http_filter.h \
src/core/xds/grpc/xds_http_filter_registry.cc \
src/core/xds/grpc/xds_http_filter_registry.h \
src/core/xds/grpc/xds_http_rbac_filter.cc \
src/core/xds/grpc/xds_http_rbac_filter.h \
src/core/xds/grpc/xds_http_stateful_session_filter.cc \
@ -3002,10 +3009,16 @@ src/core/xds/grpc/xds_lb_policy_registry.cc \
src/core/xds/grpc/xds_lb_policy_registry.h \
src/core/xds/grpc/xds_listener.cc \
src/core/xds/grpc/xds_listener.h \
src/core/xds/grpc/xds_listener_parser.cc \
src/core/xds/grpc/xds_listener_parser.h \
src/core/xds/grpc/xds_route_config.cc \
src/core/xds/grpc/xds_route_config.h \
src/core/xds/grpc/xds_route_config_parser.cc \
src/core/xds/grpc/xds_route_config_parser.h \
src/core/xds/grpc/xds_routing.cc \
src/core/xds/grpc/xds_routing.h \
src/core/xds/grpc/xds_server_grpc.cc \
src/core/xds/grpc/xds_server_grpc.h \
src/core/xds/grpc/xds_transport_grpc.cc \
src/core/xds/grpc/xds_transport_grpc.h \
src/core/xds/xds_client/xds_api.cc \

@ -2761,18 +2761,25 @@ src/core/xds/grpc/xds_client_grpc.cc \
src/core/xds/grpc/xds_client_grpc.h \
src/core/xds/grpc/xds_cluster.cc \
src/core/xds/grpc/xds_cluster.h \
src/core/xds/grpc/xds_cluster_parser.cc \
src/core/xds/grpc/xds_cluster_parser.h \
src/core/xds/grpc/xds_cluster_specifier_plugin.cc \
src/core/xds/grpc/xds_cluster_specifier_plugin.h \
src/core/xds/grpc/xds_common_types.cc \
src/core/xds/grpc/xds_common_types.h \
src/core/xds/grpc/xds_common_types_parser.cc \
src/core/xds/grpc/xds_common_types_parser.h \
src/core/xds/grpc/xds_endpoint.cc \
src/core/xds/grpc/xds_endpoint.h \
src/core/xds/grpc/xds_endpoint_parser.cc \
src/core/xds/grpc/xds_endpoint_parser.h \
src/core/xds/grpc/xds_health_status.cc \
src/core/xds/grpc/xds_health_status.h \
src/core/xds/grpc/xds_http_fault_filter.cc \
src/core/xds/grpc/xds_http_fault_filter.h \
src/core/xds/grpc/xds_http_filters.cc \
src/core/xds/grpc/xds_http_filters.h \
src/core/xds/grpc/xds_http_filter.h \
src/core/xds/grpc/xds_http_filter_registry.cc \
src/core/xds/grpc/xds_http_filter_registry.h \
src/core/xds/grpc/xds_http_rbac_filter.cc \
src/core/xds/grpc/xds_http_rbac_filter.h \
src/core/xds/grpc/xds_http_stateful_session_filter.cc \
@ -2781,10 +2788,16 @@ src/core/xds/grpc/xds_lb_policy_registry.cc \
src/core/xds/grpc/xds_lb_policy_registry.h \
src/core/xds/grpc/xds_listener.cc \
src/core/xds/grpc/xds_listener.h \
src/core/xds/grpc/xds_listener_parser.cc \
src/core/xds/grpc/xds_listener_parser.h \
src/core/xds/grpc/xds_route_config.cc \
src/core/xds/grpc/xds_route_config.h \
src/core/xds/grpc/xds_route_config_parser.cc \
src/core/xds/grpc/xds_route_config_parser.h \
src/core/xds/grpc/xds_routing.cc \
src/core/xds/grpc/xds_routing.h \
src/core/xds/grpc/xds_server_grpc.cc \
src/core/xds/grpc/xds_server_grpc.h \
src/core/xds/grpc/xds_transport_grpc.cc \
src/core/xds/grpc/xds_transport_grpc.h \
src/core/xds/xds_client/xds_api.cc \

Loading…
Cancel
Save