async-grpc-xds 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/fixtures/async/grpc/test_interface.rb +79 -0
- data/fixtures/async/grpc/test_message.rb +56 -0
- data/lib/async/grpc/xds/ads_stream.rb +70 -0
- data/lib/async/grpc/xds/client.rb +255 -0
- data/lib/async/grpc/xds/context.rb +201 -0
- data/lib/async/grpc/xds/control_plane.rb +143 -0
- data/lib/async/grpc/xds/discovery_client.rb +356 -0
- data/lib/async/grpc/xds/health_checker.rb +88 -0
- data/lib/async/grpc/xds/load_balancer.rb +196 -0
- data/lib/async/grpc/xds/resource_builder.rb +138 -0
- data/lib/async/grpc/xds/resource_cache.rb +55 -0
- data/lib/async/grpc/xds/resources.rb +270 -0
- data/lib/async/grpc/xds/server.rb +34 -0
- data/lib/async/grpc/xds/service.rb +117 -0
- data/lib/async/grpc/xds/version.rb +12 -0
- data/lib/async/grpc/xds.rb +42 -0
- data/lib/envoy/annotations/deprecation_pb.rb +19 -0
- data/lib/envoy/config/cluster/v3/circuit_breaker_pb.rb +31 -0
- data/lib/envoy/config/cluster/v3/cluster_pb.rb +80 -0
- data/lib/envoy/config/cluster/v3/filter_pb.rb +28 -0
- data/lib/envoy/config/cluster/v3/outlier_detection_pb.rb +29 -0
- data/lib/envoy/config/core/v3/address_pb.rb +38 -0
- data/lib/envoy/config/core/v3/backoff_pb.rb +27 -0
- data/lib/envoy/config/core/v3/base_pb.rb +68 -0
- data/lib/envoy/config/core/v3/cel_pb.rb +24 -0
- data/lib/envoy/config/core/v3/config_source_pb.rb +42 -0
- data/lib/envoy/config/core/v3/event_service_config_pb.rb +27 -0
- data/lib/envoy/config/core/v3/extension_pb.rb +26 -0
- data/lib/envoy/config/core/v3/grpc_method_list_pb.rb +27 -0
- data/lib/envoy/config/core/v3/grpc_service_pb.rb +45 -0
- data/lib/envoy/config/core/v3/health_check_pb.rb +47 -0
- data/lib/envoy/config/core/v3/http_service_pb.rb +27 -0
- data/lib/envoy/config/core/v3/http_uri_pb.rb +27 -0
- data/lib/envoy/config/core/v3/protocol_pb.rb +51 -0
- data/lib/envoy/config/core/v3/proxy_protocol_pb.rb +31 -0
- data/lib/envoy/config/core/v3/resolver_pb.rb +27 -0
- data/lib/envoy/config/core/v3/socket_cmsg_headers_pb.rb +25 -0
- data/lib/envoy/config/core/v3/socket_option_pb.rb +31 -0
- data/lib/envoy/config/core/v3/substitution_format_string_pb.rb +30 -0
- data/lib/envoy/config/core/v3/udp_socket_config_pb.rb +26 -0
- data/lib/envoy/config/endpoint/v3/endpoint_components_pb.rb +40 -0
- data/lib/envoy/config/endpoint/v3/endpoint_pb.rb +32 -0
- data/lib/envoy/config/endpoint/v3/load_report_pb.rb +36 -0
- data/lib/envoy/service/discovery/v3/ads_pb.rb +26 -0
- data/lib/envoy/service/discovery/v3/aggregated_discovery_service.rb +64 -0
- data/lib/envoy/service/discovery/v3/discovery_pb.rb +42 -0
- data/lib/envoy/type/matcher/v3/address_pb.rb +25 -0
- data/lib/envoy/type/matcher/v3/filter_state_pb.rb +27 -0
- data/lib/envoy/type/matcher/v3/http_inputs_pb.rb +29 -0
- data/lib/envoy/type/matcher/v3/metadata_pb.rb +28 -0
- data/lib/envoy/type/matcher/v3/node_pb.rb +27 -0
- data/lib/envoy/type/matcher/v3/number_pb.rb +27 -0
- data/lib/envoy/type/matcher/v3/path_pb.rb +27 -0
- data/lib/envoy/type/matcher/v3/regex_pb.rb +30 -0
- data/lib/envoy/type/matcher/v3/status_code_input_pb.rb +25 -0
- data/lib/envoy/type/matcher/v3/string_pb.rb +29 -0
- data/lib/envoy/type/matcher/v3/struct_pb.rb +28 -0
- data/lib/envoy/type/matcher/v3/value_pb.rb +31 -0
- data/lib/envoy/type/metadata/v3/metadata_pb.rb +32 -0
- data/lib/envoy/type/v3/hash_policy_pb.rb +26 -0
- data/lib/envoy/type/v3/http_pb.rb +22 -0
- data/lib/envoy/type/v3/http_status_pb.rb +25 -0
- data/lib/envoy/type/v3/percent_pb.rb +26 -0
- data/lib/envoy/type/v3/range_pb.rb +25 -0
- data/lib/envoy/type/v3/ratelimit_strategy_pb.rb +28 -0
- data/lib/envoy/type/v3/ratelimit_unit_pb.rb +22 -0
- data/lib/envoy/type/v3/semantic_version_pb.rb +23 -0
- data/lib/envoy/type/v3/token_bucket_pb.rb +26 -0
- data/lib/envoy.rb +83 -0
- data/lib/google/protobuf/any_pb.rb +18 -0
- data/lib/google/protobuf/duration_pb.rb +18 -0
- data/lib/google/protobuf/empty_pb.rb +18 -0
- data/lib/google/protobuf/struct_pb.rb +21 -0
- data/lib/google/protobuf/timestamp_pb.rb +18 -0
- data/lib/google/protobuf/wrappers_pb.rb +26 -0
- data/lib/google/rpc/status_pb.rb +20 -0
- data/lib/udpa/annotations/migrate_pb.rb +22 -0
- data/lib/udpa/annotations/security_pb.rb +23 -0
- data/lib/udpa/annotations/sensitive_pb.rb +19 -0
- data/lib/udpa/annotations/status_pb.rb +21 -0
- data/lib/udpa/annotations/versioning_pb.rb +20 -0
- data/lib/validate/validate_pb.rb +43 -0
- data/lib/xds/annotations/v3/status_pb.rb +26 -0
- data/lib/xds/core/v3/authority_pb.rb +23 -0
- data/lib/xds/core/v3/cidr_pb.rb +24 -0
- data/lib/xds/core/v3/collection_entry_pb.rb +26 -0
- data/lib/xds/core/v3/context_params_pb.rb +22 -0
- data/lib/xds/core/v3/extension_pb.rb +23 -0
- data/lib/xds/core/v3/resource_locator_pb.rb +26 -0
- data/lib/xds/core/v3/resource_name_pb.rb +24 -0
- data/lib/xds/core/v3/resource_pb.rb +24 -0
- data/lib/xds/type/matcher/v3/domain_pb.rb +27 -0
- data/lib/xds/type/matcher/v3/http_inputs_pb.rb +22 -0
- data/lib/xds/type/matcher/v3/ip_pb.rb +28 -0
- data/lib/xds/type/matcher/v3/matcher_pb.rb +34 -0
- data/lib/xds/type/matcher/v3/range_pb.rb +31 -0
- data/lib/xds/type/matcher/v3/regex_pb.rb +25 -0
- data/lib/xds/type/matcher/v3/string_pb.rb +27 -0
- data/license.md +21 -0
- data/plan.md +156 -0
- data/proto/envoy/annotations/deprecation.proto +34 -0
- data/proto/envoy/annotations/resource.proto +19 -0
- data/proto/envoy/config/README.md +3 -0
- data/proto/envoy/config/cluster/v3/BUILD +18 -0
- data/proto/envoy/config/cluster/v3/circuit_breaker.proto +121 -0
- data/proto/envoy/config/cluster/v3/cluster.proto +1407 -0
- data/proto/envoy/config/cluster/v3/filter.proto +40 -0
- data/proto/envoy/config/cluster/v3/outlier_detection.proto +180 -0
- data/proto/envoy/config/core/v3/BUILD +16 -0
- data/proto/envoy/config/core/v3/address.proto +214 -0
- data/proto/envoy/config/core/v3/backoff.proto +37 -0
- data/proto/envoy/config/core/v3/base.proto +662 -0
- data/proto/envoy/config/core/v3/cel.proto +63 -0
- data/proto/envoy/config/core/v3/config_source.proto +283 -0
- data/proto/envoy/config/core/v3/event_service_config.proto +29 -0
- data/proto/envoy/config/core/v3/extension.proto +32 -0
- data/proto/envoy/config/core/v3/grpc_method_list.proto +33 -0
- data/proto/envoy/config/core/v3/grpc_service.proto +355 -0
- data/proto/envoy/config/core/v3/health_check.proto +443 -0
- data/proto/envoy/config/core/v3/http_service.proto +35 -0
- data/proto/envoy/config/core/v3/http_uri.proto +58 -0
- data/proto/envoy/config/core/v3/protocol.proto +807 -0
- data/proto/envoy/config/core/v3/proxy_protocol.proto +114 -0
- data/proto/envoy/config/core/v3/resolver.proto +36 -0
- data/proto/envoy/config/core/v3/socket_cmsg_headers.proto +28 -0
- data/proto/envoy/config/core/v3/socket_option.proto +108 -0
- data/proto/envoy/config/core/v3/substitution_format_string.proto +136 -0
- data/proto/envoy/config/core/v3/udp_socket_config.proto +32 -0
- data/proto/envoy/config/endpoint/v3/BUILD +16 -0
- data/proto/envoy/config/endpoint/v3/endpoint.proto +137 -0
- data/proto/envoy/config/endpoint/v3/endpoint_components.proto +229 -0
- data/proto/envoy/config/endpoint/v3/load_report.proto +220 -0
- data/proto/envoy/config/listener/v3/BUILD +18 -0
- data/proto/envoy/config/listener/v3/api_listener.proto +34 -0
- data/proto/envoy/config/listener/v3/listener.proto +455 -0
- data/proto/envoy/config/listener/v3/listener_components.proto +353 -0
- data/proto/envoy/config/listener/v3/quic_config.proto +108 -0
- data/proto/envoy/config/listener/v3/udp_listener_config.proto +52 -0
- data/proto/envoy/config/route/v3/BUILD +19 -0
- data/proto/envoy/config/route/v3/route.proto +172 -0
- data/proto/envoy/config/route/v3/route_components.proto +2918 -0
- data/proto/envoy/config/route/v3/scoped_route.proto +133 -0
- data/proto/envoy/extensions/transport_sockets/tls/v3/BUILD +14 -0
- data/proto/envoy/extensions/transport_sockets/tls/v3/cert.proto +12 -0
- data/proto/envoy/extensions/transport_sockets/tls/v3/common.proto +597 -0
- data/proto/envoy/extensions/transport_sockets/tls/v3/secret.proto +61 -0
- data/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto +366 -0
- data/proto/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto +67 -0
- data/proto/envoy/service/README.md +3 -0
- data/proto/envoy/service/discovery/v3/BUILD +13 -0
- data/proto/envoy/service/discovery/v3/ads.proto +44 -0
- data/proto/envoy/service/discovery/v3/discovery.proto +443 -0
- data/proto/envoy/type/BUILD +9 -0
- data/proto/envoy/type/hash_policy.proto +28 -0
- data/proto/envoy/type/http.proto +24 -0
- data/proto/envoy/type/http_status.proto +140 -0
- data/proto/envoy/type/matcher/v3/address.proto +22 -0
- data/proto/envoy/type/matcher/v3/filter_state.proto +33 -0
- data/proto/envoy/type/matcher/v3/http_inputs.proto +71 -0
- data/proto/envoy/type/matcher/v3/metadata.proto +110 -0
- data/proto/envoy/type/matcher/v3/node.proto +29 -0
- data/proto/envoy/type/matcher/v3/number.proto +33 -0
- data/proto/envoy/type/matcher/v3/path.proto +31 -0
- data/proto/envoy/type/matcher/v3/regex.proto +97 -0
- data/proto/envoy/type/matcher/v3/status_code_input.proto +23 -0
- data/proto/envoy/type/matcher/v3/string.proto +94 -0
- data/proto/envoy/type/matcher/v3/struct.proto +91 -0
- data/proto/envoy/type/matcher/v3/value.proto +80 -0
- data/proto/envoy/type/metadata/v3/metadata.proto +117 -0
- data/proto/envoy/type/percent.proto +52 -0
- data/proto/envoy/type/range.proto +43 -0
- data/proto/envoy/type/semantic_version.proto +24 -0
- data/proto/envoy/type/token_bucket.proto +36 -0
- data/proto/envoy/type/v3/BUILD +12 -0
- data/proto/envoy/type/v3/hash_policy.proto +43 -0
- data/proto/envoy/type/v3/http.proto +24 -0
- data/proto/envoy/type/v3/http_status.proto +199 -0
- data/proto/envoy/type/v3/percent.proto +57 -0
- data/proto/envoy/type/v3/range.proto +50 -0
- data/proto/envoy/type/v3/ratelimit_strategy.proto +79 -0
- data/proto/envoy/type/v3/ratelimit_unit.proto +37 -0
- data/proto/envoy/type/v3/semantic_version.proto +27 -0
- data/proto/envoy/type/v3/token_bucket.proto +39 -0
- data/proto/google/protobuf/any.proto +162 -0
- data/proto/google/protobuf/duration.proto +115 -0
- data/proto/google/protobuf/empty.proto +51 -0
- data/proto/google/protobuf/struct.proto +95 -0
- data/proto/google/protobuf/timestamp.proto +145 -0
- data/proto/google/protobuf/wrappers.proto +157 -0
- data/proto/google/rpc/status.proto +47 -0
- data/proto/readme.md +70 -0
- data/proto/udpa/annotations/migrate.proto +49 -0
- data/proto/udpa/annotations/security.proto +31 -0
- data/proto/udpa/annotations/sensitive.proto +14 -0
- data/proto/udpa/annotations/status.proto +34 -0
- data/proto/udpa/annotations/versioning.proto +17 -0
- data/proto/validate/validate.proto +862 -0
- data/proto/xds/annotations/v3/migrate.proto +46 -0
- data/proto/xds/annotations/v3/security.proto +30 -0
- data/proto/xds/annotations/v3/sensitive.proto +16 -0
- data/proto/xds/annotations/v3/status.proto +59 -0
- data/proto/xds/annotations/v3/versioning.proto +20 -0
- data/proto/xds/core/v3/authority.proto +22 -0
- data/proto/xds/core/v3/cidr.proto +25 -0
- data/proto/xds/core/v3/collection_entry.proto +55 -0
- data/proto/xds/core/v3/context_params.proto +23 -0
- data/proto/xds/core/v3/extension.proto +26 -0
- data/proto/xds/core/v3/resource.proto +29 -0
- data/proto/xds/core/v3/resource_locator.proto +118 -0
- data/proto/xds/core/v3/resource_name.proto +42 -0
- data/proto/xds/type/matcher/v3/cel.proto +37 -0
- data/proto/xds/type/matcher/v3/domain.proto +46 -0
- data/proto/xds/type/matcher/v3/http_inputs.proto +23 -0
- data/proto/xds/type/matcher/v3/ip.proto +53 -0
- data/proto/xds/type/matcher/v3/matcher.proto +144 -0
- data/proto/xds/type/matcher/v3/range.proto +69 -0
- data/proto/xds/type/matcher/v3/regex.proto +46 -0
- data/proto/xds/type/matcher/v3/string.proto +71 -0
- data/proto/xds/type/v3/cel.proto +77 -0
- data/proto/xds/type/v3/range.proto +40 -0
- data/proto/xds/type/v3/typed_struct.proto +44 -0
- data/readme.md +37 -0
- data/releases.md +5 -0
- data/xds/Dockerfile.backend +24 -0
- data/xds/Dockerfile.control-plane +22 -0
- data/xds/backend_server.rb +68 -0
- data/xds/docker-compose.yaml +89 -0
- data/xds/go.mod +22 -0
- data/xds/go.sum +82 -0
- data/xds/readme.md +122 -0
- data/xds/test/async/grpc/xds/client.rb +294 -0
- data/xds/test/async/grpc/xds/control_plane.rb +94 -0
- data/xds/test_server.go +355 -0
- data/xds/update_protos.sh +123 -0
- data.tar.gz.sig +0 -0
- metadata +386 -0
- metadata.gz.sig +2 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Released under the MIT License.
|
|
5
|
+
# Copyright, 2026, by Samuel Williams.
|
|
6
|
+
|
|
7
|
+
require "async"
|
|
8
|
+
require "async/http/server"
|
|
9
|
+
require "async/http/endpoint"
|
|
10
|
+
require "async/grpc/dispatcher"
|
|
11
|
+
require "async/grpc/service"
|
|
12
|
+
require_relative "../fixtures/async/grpc/test_interface"
|
|
13
|
+
|
|
14
|
+
class TestBackendService < Async::GRPC::Service
|
|
15
|
+
def initialize(interface_class, service_name, backend_id)
|
|
16
|
+
super(interface_class, service_name)
|
|
17
|
+
@backend_id = backend_id
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def unary_call(input, output, call)
|
|
21
|
+
request = input.read
|
|
22
|
+
|
|
23
|
+
# Include backend ID in response metadata (trailers)
|
|
24
|
+
call.response.headers["backend-id"] = @backend_id
|
|
25
|
+
|
|
26
|
+
response = Protocol::GRPC::Fixtures::TestMessage.new(
|
|
27
|
+
value: "Response from #{@backend_id}: #{request&.value || 'no value'}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
output.write(response)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def say_hello(input, output, call)
|
|
34
|
+
request = input.read
|
|
35
|
+
|
|
36
|
+
call.response.headers["backend-id"] = @backend_id
|
|
37
|
+
|
|
38
|
+
response = Protocol::GRPC::Fixtures::TestMessage.new(
|
|
39
|
+
value: "Hello from #{@backend_id}, #{request.value}!"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
output.write(response)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
port = ENV["PORT"] || "50051"
|
|
47
|
+
backend_id = ENV["BACKEND_ID"] || "backend-unknown"
|
|
48
|
+
service_name = ENV["SERVICE_NAME"] || "test.Service"
|
|
49
|
+
|
|
50
|
+
Async do
|
|
51
|
+
# Create gRPC dispatcher
|
|
52
|
+
dispatcher = Async::GRPC::Dispatcher.new
|
|
53
|
+
service = TestBackendService.new(Async::GRPC::Fixtures::TestInterface, service_name, backend_id)
|
|
54
|
+
dispatcher.register(service)
|
|
55
|
+
|
|
56
|
+
# Create endpoint (http for h2c - gRPC without TLS in docker)
|
|
57
|
+
endpoint = Async::HTTP::Endpoint.parse(
|
|
58
|
+
"http://0.0.0.0:#{port}",
|
|
59
|
+
protocol: Async::HTTP::Protocol::HTTP2
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Start server
|
|
63
|
+
server = Async::HTTP::Server.new(dispatcher, endpoint)
|
|
64
|
+
|
|
65
|
+
Console.info{"Starting backend server #{backend_id} on port #{port}"}
|
|
66
|
+
|
|
67
|
+
server.run
|
|
68
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
services:
|
|
2
|
+
# xDS control plane (using custom test server built with go-control-plane)
|
|
3
|
+
# This provides a simple xDS server for testing
|
|
4
|
+
xds-control-plane:
|
|
5
|
+
build:
|
|
6
|
+
context: .
|
|
7
|
+
dockerfile: Dockerfile.control-plane
|
|
8
|
+
ports:
|
|
9
|
+
- "18000:18000"
|
|
10
|
+
- "18001:18001"
|
|
11
|
+
environment:
|
|
12
|
+
- UPSTREAM=backend-1:50051,backend-2:50052,backend-3:50053
|
|
13
|
+
healthcheck:
|
|
14
|
+
test: ["CMD", "nc", "-z", "localhost", "18000"]
|
|
15
|
+
interval: 1s
|
|
16
|
+
timeout: 3s
|
|
17
|
+
retries: 30
|
|
18
|
+
|
|
19
|
+
# Backend gRPC server 1
|
|
20
|
+
backend-1:
|
|
21
|
+
build:
|
|
22
|
+
context: ..
|
|
23
|
+
dockerfile: xds/Dockerfile.backend
|
|
24
|
+
command: bundle exec ruby xds/backend_server.rb
|
|
25
|
+
environment:
|
|
26
|
+
- BUNDLE_WITHOUT=maintenance
|
|
27
|
+
- PORT=50051
|
|
28
|
+
- SERVICE_NAME=myservice
|
|
29
|
+
- BACKEND_ID=backend-1
|
|
30
|
+
ports:
|
|
31
|
+
- "50051:50051"
|
|
32
|
+
depends_on:
|
|
33
|
+
xds-control-plane:
|
|
34
|
+
condition: service_healthy
|
|
35
|
+
|
|
36
|
+
# Backend gRPC server 2
|
|
37
|
+
backend-2:
|
|
38
|
+
build:
|
|
39
|
+
context: ..
|
|
40
|
+
dockerfile: xds/Dockerfile.backend
|
|
41
|
+
command: bundle exec ruby xds/backend_server.rb
|
|
42
|
+
environment:
|
|
43
|
+
- BUNDLE_WITHOUT=maintenance
|
|
44
|
+
- PORT=50052
|
|
45
|
+
- SERVICE_NAME=myservice
|
|
46
|
+
- BACKEND_ID=backend-2
|
|
47
|
+
ports:
|
|
48
|
+
- "50052:50052"
|
|
49
|
+
depends_on:
|
|
50
|
+
xds-control-plane:
|
|
51
|
+
condition: service_healthy
|
|
52
|
+
|
|
53
|
+
# Backend gRPC server 3
|
|
54
|
+
backend-3:
|
|
55
|
+
build:
|
|
56
|
+
context: ..
|
|
57
|
+
dockerfile: xds/Dockerfile.backend
|
|
58
|
+
command: bundle exec ruby xds/backend_server.rb
|
|
59
|
+
environment:
|
|
60
|
+
- BUNDLE_WITHOUT=maintenance
|
|
61
|
+
- PORT=50053
|
|
62
|
+
- SERVICE_NAME=myservice
|
|
63
|
+
- BACKEND_ID=backend-3
|
|
64
|
+
ports:
|
|
65
|
+
- "50053:50053"
|
|
66
|
+
depends_on:
|
|
67
|
+
xds-control-plane:
|
|
68
|
+
condition: service_healthy
|
|
69
|
+
|
|
70
|
+
# Test runner
|
|
71
|
+
tests:
|
|
72
|
+
image: ruby:${RUBY_VERSION:-latest}
|
|
73
|
+
volumes:
|
|
74
|
+
- ../:/code
|
|
75
|
+
working_dir: /code
|
|
76
|
+
command: bash -c "bundle install && bundle exec sus xds/test"
|
|
77
|
+
environment:
|
|
78
|
+
- BUNDLE_GEMFILE=/code/gems.rb
|
|
79
|
+
- BUNDLE_WITHOUT=maintenance
|
|
80
|
+
- CONSOLE_OUTPUT=XTerm
|
|
81
|
+
- COVERAGE=${COVERAGE}
|
|
82
|
+
- XDS_ADMIN_URI=http://xds-control-plane:18001
|
|
83
|
+
- XDS_SERVER_URI=xds-control-plane:18000
|
|
84
|
+
- XDS_ENDPOINT_SCHEME=http
|
|
85
|
+
depends_on:
|
|
86
|
+
- xds-control-plane
|
|
87
|
+
- backend-1
|
|
88
|
+
- backend-2
|
|
89
|
+
- backend-3
|
data/xds/go.mod
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module xds-test-server
|
|
2
|
+
|
|
3
|
+
go 1.21
|
|
4
|
+
|
|
5
|
+
require (
|
|
6
|
+
github.com/envoyproxy/go-control-plane v0.12.0
|
|
7
|
+
google.golang.org/grpc v1.60.0
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
require (
|
|
11
|
+
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
|
|
12
|
+
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect
|
|
13
|
+
github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect
|
|
14
|
+
github.com/golang/protobuf v1.5.3 // indirect
|
|
15
|
+
golang.org/x/net v0.17.0 // indirect
|
|
16
|
+
golang.org/x/sys v0.13.0 // indirect
|
|
17
|
+
golang.org/x/text v0.13.0 // indirect
|
|
18
|
+
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
|
|
19
|
+
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect
|
|
20
|
+
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect
|
|
21
|
+
google.golang.org/protobuf v1.32.0 // indirect
|
|
22
|
+
)
|
data/xds/go.sum
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
2
|
+
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
3
|
+
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
4
|
+
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
|
|
5
|
+
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
|
6
|
+
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
7
|
+
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
|
|
8
|
+
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
9
|
+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
10
|
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
11
|
+
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
12
|
+
github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI=
|
|
13
|
+
github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0=
|
|
14
|
+
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
|
15
|
+
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
|
|
16
|
+
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
|
|
17
|
+
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
18
|
+
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
19
|
+
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
20
|
+
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
21
|
+
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
22
|
+
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
|
23
|
+
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
24
|
+
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
25
|
+
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
26
|
+
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
|
27
|
+
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
28
|
+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
29
|
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
30
|
+
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
31
|
+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
|
32
|
+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
33
|
+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
34
|
+
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
35
|
+
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
36
|
+
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
37
|
+
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
38
|
+
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
39
|
+
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
40
|
+
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
41
|
+
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
42
|
+
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
|
43
|
+
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
|
44
|
+
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
45
|
+
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
46
|
+
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
47
|
+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
48
|
+
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
49
|
+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
50
|
+
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
|
51
|
+
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
52
|
+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
53
|
+
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
|
54
|
+
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|
55
|
+
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
56
|
+
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
57
|
+
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
58
|
+
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
59
|
+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
60
|
+
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
61
|
+
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
62
|
+
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
63
|
+
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
64
|
+
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0=
|
|
65
|
+
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
|
|
66
|
+
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU=
|
|
67
|
+
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0=
|
|
68
|
+
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0=
|
|
69
|
+
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY=
|
|
70
|
+
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
71
|
+
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
72
|
+
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
73
|
+
google.golang.org/grpc v1.60.0 h1:6FQAR0kM31P6MRdeluor2w2gPaS4SVNrD/DNTxrQ15k=
|
|
74
|
+
google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
|
75
|
+
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
76
|
+
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
77
|
+
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
|
78
|
+
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
|
79
|
+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
80
|
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
81
|
+
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
82
|
+
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
data/xds/readme.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# xDS Integration Tests
|
|
2
|
+
|
|
3
|
+
This directory contains Docker Compose configuration and test files for xDS integration testing, following the same pattern as `async-redis` (Sentinel and Cluster tests).
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
The Docker Compose setup includes:
|
|
8
|
+
- **xds-control-plane**: xDS control plane server (using go-control-plane)
|
|
9
|
+
- **backend-1, backend-2, backend-3**: Multiple gRPC backend servers
|
|
10
|
+
- **tests**: Test runner container
|
|
11
|
+
|
|
12
|
+
## Running Tests
|
|
13
|
+
|
|
14
|
+
### Start the environment
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd xds
|
|
18
|
+
bundle install # generates gems.locked for Docker build
|
|
19
|
+
docker compose up -d
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Wait for services to be ready
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
docker compose ps
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
All services should show as "healthy" or "running".
|
|
29
|
+
|
|
30
|
+
### Run tests
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Run tests in docker compose
|
|
34
|
+
docker compose run --rm tests
|
|
35
|
+
|
|
36
|
+
# Or run tests locally (if services are accessible)
|
|
37
|
+
# Set XDS_SERVER_URI environment variable
|
|
38
|
+
export XDS_SERVER_URI=xds-control-plane:18000
|
|
39
|
+
bundle exec sus xds/test
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Cleanup
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
docker compose down
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Test Structure
|
|
49
|
+
|
|
50
|
+
Tests are located in `xds/test/async/grpc/xds/` and follow the same pattern as other async-grpc tests:
|
|
51
|
+
|
|
52
|
+
- `client.rb`: Tests for `Async::GRPC::XDS::Client`
|
|
53
|
+
- Tests use `Sus::Fixtures::Async::ReactorContext` for async test support
|
|
54
|
+
- Tests connect to docker compose services using service names (e.g., `xds-control-plane:18000`)
|
|
55
|
+
|
|
56
|
+
## Environment Variables
|
|
57
|
+
|
|
58
|
+
- `XDS_SERVER_URI`: xDS control plane server URI (default: `xds-control-plane:18000`)
|
|
59
|
+
- `RUBY_VERSION`: Ruby version for test container (default: `latest`)
|
|
60
|
+
- `COVERAGE`: Enable code coverage reporting
|
|
61
|
+
|
|
62
|
+
## Backend Servers
|
|
63
|
+
|
|
64
|
+
The backend servers (`backend-1`, `backend-2`, `backend-3`) are simple gRPC servers that:
|
|
65
|
+
- Implement the test service interface
|
|
66
|
+
- Include backend ID in response metadata
|
|
67
|
+
- Can be used to test load balancing and failover
|
|
68
|
+
|
|
69
|
+
See `backend_server.rb` for implementation details.
|
|
70
|
+
|
|
71
|
+
## Mock xDS Control Plane
|
|
72
|
+
|
|
73
|
+
For simpler unit testing, you can use a mock xDS server instead of the full Docker Compose setup. See the test files for examples of mocking xDS responses.
|
|
74
|
+
|
|
75
|
+
## Troubleshooting
|
|
76
|
+
|
|
77
|
+
### Services not starting
|
|
78
|
+
|
|
79
|
+
Check logs:
|
|
80
|
+
```bash
|
|
81
|
+
docker compose logs xds-control-plane
|
|
82
|
+
docker compose logs backend-1
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Tests failing to connect
|
|
86
|
+
|
|
87
|
+
Ensure services are healthy:
|
|
88
|
+
```bash
|
|
89
|
+
docker compose ps
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Check network connectivity:
|
|
93
|
+
```bash
|
|
94
|
+
docker compose exec tests ping xds-control-plane
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Protobuf Setup
|
|
98
|
+
|
|
99
|
+
The xDS implementation uses Envoy protobuf definitions. Protos come from [envoyproxy/envoy](https://github.com/envoyproxy/envoy) (`api/`) or [envoyproxy/data-plane-api](https://github.com/envoyproxy/data-plane-api). Use **xDS v3** (v2 is deprecated).
|
|
100
|
+
|
|
101
|
+
### Required protobuf files
|
|
102
|
+
|
|
103
|
+
- `envoy/service/discovery/v3/discovery.proto` - DiscoveryRequest/Response.
|
|
104
|
+
- `envoy/service/discovery/v3/ads.proto` - AggregatedDiscoveryService.
|
|
105
|
+
- `envoy/config/cluster/v3/cluster.proto` - Cluster (CDS).
|
|
106
|
+
- `envoy/config/endpoint/v3/endpoint.proto` - ClusterLoadAssignment (EDS).
|
|
107
|
+
- `envoy/config/core/v3/base.proto` - Node, Locality, etc.
|
|
108
|
+
- `google/protobuf/any.proto` - For Any type in DiscoveryResponse.
|
|
109
|
+
|
|
110
|
+
### Generating Ruby code
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
protoc --ruby_out=lib \
|
|
114
|
+
--proto_path=vendor/envoy-api \
|
|
115
|
+
envoy/service/discovery/v3/discovery.proto \
|
|
116
|
+
envoy/service/discovery/v3/ads.proto \
|
|
117
|
+
envoy/config/cluster/v3/cluster.proto \
|
|
118
|
+
envoy/config/endpoint/v3/endpoint.proto \
|
|
119
|
+
envoy/config/core/v3/base.proto
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Lock the Envoy API version (submodule tag or commit) for compatibility.
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "async/grpc/xds/client"
|
|
7
|
+
require "async/grpc/xds/ads_stream"
|
|
8
|
+
require "async/grpc/service"
|
|
9
|
+
require "sus/fixtures/async"
|
|
10
|
+
require "async/http/endpoint"
|
|
11
|
+
require_relative "../../../../../fixtures/async/grpc/test_interface"
|
|
12
|
+
require "json"
|
|
13
|
+
require "net/http"
|
|
14
|
+
require "set"
|
|
15
|
+
require "uri"
|
|
16
|
+
|
|
17
|
+
describe Async::GRPC::XDS::Client do
|
|
18
|
+
include Sus::Fixtures::Async::ReactorContext
|
|
19
|
+
|
|
20
|
+
let(:xds_server_uri) {ENV["XDS_SERVER_URI"] || "xds-control-plane:18000"}
|
|
21
|
+
let(:xds_admin_uri) {ENV["XDS_ADMIN_URI"] || "http://xds-control-plane:18001"}
|
|
22
|
+
let(:service_name) {"myservice"}
|
|
23
|
+
|
|
24
|
+
let(:bootstrap) do
|
|
25
|
+
{
|
|
26
|
+
xds_servers: [
|
|
27
|
+
{
|
|
28
|
+
server_uri: xds_server_uri,
|
|
29
|
+
channel_creds: [{type: "insecure"}]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
node: {
|
|
33
|
+
id: "test-client-#{Process.pid}",
|
|
34
|
+
cluster: "test"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
let(:client) {subject.new(service_name, bootstrap: bootstrap)}
|
|
40
|
+
|
|
41
|
+
def backend_id(response)
|
|
42
|
+
response.value[/Response from ([^:]+)/, 1]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def call_backend_ids(stub, count: 6)
|
|
46
|
+
count.times.map do
|
|
47
|
+
request = Protocol::GRPC::Fixtures::TestMessage.new(value: "test")
|
|
48
|
+
backend_id(stub.unary_call(request))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def post_admin(path, payload = {})
|
|
53
|
+
uri = URI.join("#{xds_admin_uri}/", path)
|
|
54
|
+
request = Net::HTTP::Post.new(uri)
|
|
55
|
+
request["Content-Type"] = "application/json"
|
|
56
|
+
request.body = JSON.dump(payload)
|
|
57
|
+
|
|
58
|
+
response = Net::HTTP.start(uri.host, uri.port) do |http|
|
|
59
|
+
http.request(request)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
63
|
+
raise "Admin request failed: #{response.code} #{response.body}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
JSON.parse(response.body)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def set_control_plane_endpoints(*endpoints)
|
|
70
|
+
post_admin("endpoints", endpoints: endpoints)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def reset_control_plane_streams
|
|
74
|
+
post_admin("reset-streams")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def restore_control_plane_endpoints
|
|
78
|
+
set_control_plane_endpoints(
|
|
79
|
+
"backend-1:50051",
|
|
80
|
+
"backend-2:50052",
|
|
81
|
+
"backend-3:50053"
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def eventually(timeout: 15, interval: 0.1)
|
|
86
|
+
deadline = Time.now + timeout
|
|
87
|
+
last_error = nil
|
|
88
|
+
|
|
89
|
+
while Time.now < deadline
|
|
90
|
+
begin
|
|
91
|
+
result = yield
|
|
92
|
+
return result if result
|
|
93
|
+
rescue => error
|
|
94
|
+
last_error = error
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
sleep(interval)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
raise last_error if last_error
|
|
101
|
+
raise "Timed out waiting for condition"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "can stream updates" do
|
|
105
|
+
skip "Requires xDS control plane (XDS_SERVER_URI)" unless ENV["XDS_SERVER_URI"]
|
|
106
|
+
|
|
107
|
+
received = []
|
|
108
|
+
delegate = Object.new
|
|
109
|
+
delegate.define_singleton_method(:discovery_response){|response, _stream| received << response}
|
|
110
|
+
|
|
111
|
+
endpoint = Async::HTTP::Endpoint.parse(
|
|
112
|
+
"http://#{xds_server_uri}",
|
|
113
|
+
protocol: Async::HTTP::Protocol::HTTP2
|
|
114
|
+
)
|
|
115
|
+
http_client = Async::HTTP::Client.new(endpoint)
|
|
116
|
+
grpc_client = Async::GRPC::Client.new(http_client)
|
|
117
|
+
node = Envoy::Config::Core::V3::Node.new(id: "test-#{Process.pid}", cluster: "test")
|
|
118
|
+
|
|
119
|
+
initial = Envoy::Service::Discovery::V3::DiscoveryRequest.new(
|
|
120
|
+
type_url: "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
|
121
|
+
resource_names: [service_name],
|
|
122
|
+
node: node
|
|
123
|
+
)
|
|
124
|
+
stream = Async::GRPC::XDS::ADSStream.new(grpc_client, node, delegate: delegate)
|
|
125
|
+
|
|
126
|
+
stream_task = Async{stream.run(initial: initial)}
|
|
127
|
+
deadline = Time.now + 10
|
|
128
|
+
while received.empty? && Time.now < deadline
|
|
129
|
+
sleep(0.1)
|
|
130
|
+
end
|
|
131
|
+
stream_task.stop
|
|
132
|
+
|
|
133
|
+
expect(received.size).to be >= 1
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "can resolve endpoints" do
|
|
137
|
+
skip "Requires docker compose environment (XDS_SERVER_URI)" unless ENV["XDS_SERVER_URI"]
|
|
138
|
+
|
|
139
|
+
endpoints = client.resolve_endpoints
|
|
140
|
+
|
|
141
|
+
expect(endpoints.size).to be >= 1
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it "can make RPC calls through xDS" do
|
|
145
|
+
skip "Requires docker compose environment (XDS_SERVER_URI)" unless ENV["XDS_SERVER_URI"]
|
|
146
|
+
|
|
147
|
+
stub = client.stub(Async::GRPC::Fixtures::TestInterface, service_name)
|
|
148
|
+
|
|
149
|
+
request = Protocol::GRPC::Fixtures::TestMessage.new(value: "test")
|
|
150
|
+
response = stub.unary_call(request)
|
|
151
|
+
|
|
152
|
+
expect(response).to be_a(Protocol::GRPC::Fixtures::TestMessage)
|
|
153
|
+
expect(response.value).to be(:include?, "test")
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it "load balances across multiple endpoints" do
|
|
157
|
+
skip "Requires docker compose environment (XDS_SERVER_URI)" unless ENV["XDS_SERVER_URI"]
|
|
158
|
+
|
|
159
|
+
stub = client.stub(Async::GRPC::Fixtures::TestInterface, service_name)
|
|
160
|
+
endpoints_used = Set.new
|
|
161
|
+
|
|
162
|
+
10.times do
|
|
163
|
+
request = Protocol::GRPC::Fixtures::TestMessage.new(value: "test")
|
|
164
|
+
response = stub.unary_call(request)
|
|
165
|
+
endpoints_used << response.value
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
expect(endpoints_used.size).to be >= 1
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "handles bootstrap configuration errors" do
|
|
172
|
+
expect{subject.new(service_name, bootstrap: {invalid: "config" })}.to raise_exception(Async::GRPC::XDS::Client::ConfigurationError)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it "handles no endpoints available" do
|
|
176
|
+
skip "Requires docker compose environment (XDS_SERVER_URI)" unless ENV["XDS_SERVER_URI"]
|
|
177
|
+
|
|
178
|
+
invalid_client = subject.new("nonexistent-service", bootstrap: bootstrap)
|
|
179
|
+
|
|
180
|
+
expect{invalid_client.resolve_endpoints}.to raise_exception(Async::GRPC::XDS::Client::NoEndpointsError)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it "evicts resolved promises to prevent unbounded growth" do
|
|
184
|
+
skip "Requires docker compose environment (XDS_SERVER_URI)" unless ENV["XDS_SERVER_URI"]
|
|
185
|
+
|
|
186
|
+
xds_client = subject.new(service_name, bootstrap: bootstrap)
|
|
187
|
+
xds_client.resolve_endpoints
|
|
188
|
+
|
|
189
|
+
context = xds_client.instance_variable_get(:@context)
|
|
190
|
+
# Resolved promises are evicted immediately; hashes stay bounded
|
|
191
|
+
expect(context.instance_variable_get(:@cluster_promises)).to be(:empty?)
|
|
192
|
+
expect(context.instance_variable_get(:@endpoint_promises)).to be(:empty?)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "clears promise caches on close to prevent memory growth" do
|
|
196
|
+
skip "Requires docker compose environment (XDS_SERVER_URI)" unless ENV["XDS_SERVER_URI"]
|
|
197
|
+
|
|
198
|
+
xds_client = subject.new(service_name, bootstrap: bootstrap)
|
|
199
|
+
xds_client.resolve_endpoints
|
|
200
|
+
|
|
201
|
+
context = xds_client.instance_variable_get(:@context)
|
|
202
|
+
xds_client.close
|
|
203
|
+
|
|
204
|
+
# Close clears any remaining promises (e.g. unresolved for nonexistent services)
|
|
205
|
+
expect(context.instance_variable_get(:@cluster_promises)).to be(:empty?)
|
|
206
|
+
expect(context.instance_variable_get(:@endpoint_promises)).to be(:empty?)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it "applies endpoint updates from the control plane" do
|
|
210
|
+
skip "Requires docker compose environment (XDS_SERVER_URI)" unless ENV["XDS_SERVER_URI"]
|
|
211
|
+
|
|
212
|
+
begin
|
|
213
|
+
set_control_plane_endpoints("backend-1:50051")
|
|
214
|
+
|
|
215
|
+
xds_client = subject.new(service_name, bootstrap: bootstrap)
|
|
216
|
+
stub = xds_client.stub(Async::GRPC::Fixtures::TestInterface, service_name)
|
|
217
|
+
|
|
218
|
+
eventually do
|
|
219
|
+
ids = call_backend_ids(stub, count: 3)
|
|
220
|
+
ids.all?{|id| id == "backend-1"}
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
set_control_plane_endpoints("backend-1:50051", "backend-2:50052")
|
|
224
|
+
|
|
225
|
+
eventually do
|
|
226
|
+
ids = call_backend_ids(stub, count: 6)
|
|
227
|
+
ids.include?("backend-2")
|
|
228
|
+
end
|
|
229
|
+
ensure
|
|
230
|
+
xds_client&.close
|
|
231
|
+
restore_control_plane_endpoints
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "removes and recovers endpoints from control plane updates" do
|
|
236
|
+
skip "Requires docker compose environment (XDS_SERVER_URI)" unless ENV["XDS_SERVER_URI"]
|
|
237
|
+
|
|
238
|
+
begin
|
|
239
|
+
set_control_plane_endpoints("backend-1:50051", "backend-2:50052")
|
|
240
|
+
|
|
241
|
+
xds_client = subject.new(service_name, bootstrap: bootstrap)
|
|
242
|
+
stub = xds_client.stub(Async::GRPC::Fixtures::TestInterface, service_name)
|
|
243
|
+
|
|
244
|
+
eventually do
|
|
245
|
+
ids = call_backend_ids(stub, count: 6)
|
|
246
|
+
ids.include?("backend-1") && ids.include?("backend-2")
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
set_control_plane_endpoints("backend-2:50052")
|
|
250
|
+
|
|
251
|
+
eventually do
|
|
252
|
+
ids = call_backend_ids(stub, count: 4)
|
|
253
|
+
ids.all?{|id| id == "backend-2"}
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
set_control_plane_endpoints("backend-1:50051", "backend-2:50052")
|
|
257
|
+
|
|
258
|
+
eventually do
|
|
259
|
+
ids = call_backend_ids(stub, count: 6)
|
|
260
|
+
ids.include?("backend-1")
|
|
261
|
+
end
|
|
262
|
+
ensure
|
|
263
|
+
xds_client&.close
|
|
264
|
+
restore_control_plane_endpoints
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it "reconnects after the ADS stream is reset" do
|
|
269
|
+
skip "Requires docker compose environment (XDS_SERVER_URI)" unless ENV["XDS_SERVER_URI"]
|
|
270
|
+
|
|
271
|
+
begin
|
|
272
|
+
set_control_plane_endpoints("backend-1:50051")
|
|
273
|
+
|
|
274
|
+
xds_client = subject.new(service_name, bootstrap: bootstrap)
|
|
275
|
+
stub = xds_client.stub(Async::GRPC::Fixtures::TestInterface, service_name)
|
|
276
|
+
|
|
277
|
+
eventually do
|
|
278
|
+
ids = call_backend_ids(stub, count: 3)
|
|
279
|
+
ids.all?{|id| id == "backend-1"}
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
reset_control_plane_streams
|
|
283
|
+
set_control_plane_endpoints("backend-2:50052")
|
|
284
|
+
|
|
285
|
+
eventually(timeout: 20) do
|
|
286
|
+
ids = call_backend_ids(stub, count: 4)
|
|
287
|
+
ids.all?{|id| id == "backend-2"}
|
|
288
|
+
end
|
|
289
|
+
ensure
|
|
290
|
+
xds_client&.close
|
|
291
|
+
restore_control_plane_endpoints
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|