async-grpc-xds 0.0.1 → 0.2.0
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/grpc/xds/ads_stream.rb +4 -0
- data/lib/async/grpc/xds/client_side_weighted_round_robin.rb +53 -0
- data/lib/async/grpc/xds/cluster.rb +71 -0
- data/lib/async/grpc/xds/control_plane.rb +47 -6
- data/lib/async/grpc/xds/discovery_client.rb +12 -2
- data/lib/async/grpc/xds/endpoint.rb +102 -0
- data/lib/async/grpc/xds/resource_cache.rb +1 -0
- data/lib/async/grpc/xds/resources.rb +9 -0
- data/lib/async/grpc/xds/server.rb +7 -0
- data/lib/async/grpc/xds/service.rb +24 -0
- data/lib/async/grpc/xds/version.rb +4 -1
- data/lib/async/grpc/xds.rb +3 -1
- data/lib/envoy/config/common/mutation_rules/v3/mutation_rules_pb.rb +33 -0
- data/lib/envoy/config/route/v3/route_components_pb.rb +108 -0
- data/lib/envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3/client_side_weighted_round_robin_pb.rb +30 -0
- data/lib/envoy/extensions/load_balancing_policies/common/v3/common_pb.rb +41 -0
- data/lib/envoy/type/tracing/v3/custom_tag_pb.rb +31 -0
- data/lib/xds/data/orca/v3/orca_load_report_pb.rb +24 -0
- data/lib/xds/service/orca/v3/open_rca_service.rb +23 -0
- data/lib/xds/service/orca/v3/orca_pb.rb +25 -0
- data/proto/envoy/config/common/mutation_rules/v3/mutation_rules.proto +113 -0
- data/proto/envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3/client_side_weighted_round_robin.proto +96 -0
- data/proto/envoy/extensions/load_balancing_policies/common/v3/common.proto +188 -0
- data/proto/envoy/type/tracing/v3/custom_tag.proto +109 -0
- data/proto/readme.md +3 -0
- data/proto/xds/data/orca/v3/orca_load_report.proto +58 -0
- data/proto/xds/service/orca/v3/orca.proto +36 -0
- data/readme.md +16 -2
- data/releases.md +12 -0
- data/xds/readme.md +2 -29
- data/xds/test/async/grpc/xds/control_plane.rb +5 -6
- data.tar.gz.sig +0 -0
- metadata +18 -2
- metadata.gz.sig +0 -0
- data/lib/async/grpc/xds/resource_builder.rb +0 -138
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 49d59879d476dd4d2e5f134a637371d29e1d0f352b35f22494630e424919e64d
|
|
4
|
+
data.tar.gz: 8a6898813f97f84ef507a4bd001f8fc224912d297296cc3439f523853bf43f44
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0f19714dcf5dd780cdfa9e7df25dcc52aa921ece5f168d10b0e8b2b34896a171098f4c907590e0ca27f8e43bf01e0e737faeb41bb2a746770762bb7dd807d1dd
|
|
7
|
+
data.tar.gz: a5c9b8d7e1cfff638f626c03f7643c5e27385365e30cb9bdaaf7802c3ba8655fd71dff1523506f2cb96ab70d9230a86ec9040bccf1c446b4de9ea15eb6788990
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -24,6 +24,10 @@ module Async
|
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# Initialize an ADS stream.
|
|
28
|
+
# @parameter client [Async::GRPC::Client] The gRPC client used to open the stream.
|
|
29
|
+
# @parameter node [Envoy::Config::Core::V3::Node] The xDS node identity.
|
|
30
|
+
# @parameter delegate [Delegate] The object that receives stream events.
|
|
27
31
|
def initialize(client, node, delegate:)
|
|
28
32
|
@client = client
|
|
29
33
|
@node = node
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "google/protobuf/any_pb"
|
|
7
|
+
require "google/protobuf/duration_pb"
|
|
8
|
+
require "google/protobuf/wrappers_pb"
|
|
9
|
+
|
|
10
|
+
require "envoy/config/cluster/v3/cluster_pb"
|
|
11
|
+
require "envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3/client_side_weighted_round_robin_pb"
|
|
12
|
+
|
|
13
|
+
module Async
|
|
14
|
+
module GRPC
|
|
15
|
+
module XDS
|
|
16
|
+
# Builds Envoy's client-side weighted-round-robin policy with out-of-band ORCA reporting.
|
|
17
|
+
module ClientSideWeightedRoundRobin
|
|
18
|
+
# Build the typed load-balancing policy.
|
|
19
|
+
# @parameter port [Integer] The alternative TCP port hosting the ORCA service.
|
|
20
|
+
# @parameter reporting_period [Numeric] The requested ORCA reporting interval in seconds.
|
|
21
|
+
# @returns [Envoy::Config::Cluster::V3::LoadBalancingPolicy] The typed load-balancing policy.
|
|
22
|
+
def self.build(port, reporting_period: 1)
|
|
23
|
+
seconds = reporting_period.to_i
|
|
24
|
+
duration = Google::Protobuf::Duration.new(
|
|
25
|
+
seconds: seconds,
|
|
26
|
+
nanos: ((reporting_period.to_f - seconds) * 1_000_000_000).to_i
|
|
27
|
+
)
|
|
28
|
+
configuration = Envoy::Extensions::LoadBalancingPolicies::ClientSideWeightedRoundRobin::V3::ClientSideWeightedRoundRobin.new(
|
|
29
|
+
enable_oob_load_report: Google::Protobuf::BoolValue.new(value: true),
|
|
30
|
+
oob_reporting_period: duration,
|
|
31
|
+
oob_reporting_config: Envoy::Extensions::LoadBalancingPolicies::Common::V3::OrcaOobReportingConfig.new(
|
|
32
|
+
port_value: Integer(port)
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
Envoy::Config::Cluster::V3::LoadBalancingPolicy.new(
|
|
37
|
+
policies: [
|
|
38
|
+
Envoy::Config::Cluster::V3::LoadBalancingPolicy::Policy.new(
|
|
39
|
+
typed_extension_config: Envoy::Config::Core::V3::TypedExtensionConfig.new(
|
|
40
|
+
name: "envoy.load_balancing_policies.client_side_weighted_round_robin",
|
|
41
|
+
typed_config: Google::Protobuf::Any.new(
|
|
42
|
+
type_url: "type.googleapis.com/#{configuration.class.descriptor.name}",
|
|
43
|
+
value: configuration.to_proto
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "google/protobuf/duration_pb"
|
|
7
|
+
|
|
8
|
+
require "envoy/config/cluster/v3/cluster_pb"
|
|
9
|
+
require "envoy/config/core/v3/config_source_pb"
|
|
10
|
+
require "envoy/config/core/v3/protocol_pb"
|
|
11
|
+
|
|
12
|
+
module Async
|
|
13
|
+
module GRPC
|
|
14
|
+
module XDS
|
|
15
|
+
# Builds Envoy cluster resources.
|
|
16
|
+
module Cluster
|
|
17
|
+
extend self
|
|
18
|
+
|
|
19
|
+
TYPE_URL = "type.googleapis.com/envoy.config.cluster.v3.Cluster"
|
|
20
|
+
|
|
21
|
+
# Build an EDS cluster resource.
|
|
22
|
+
# @parameter name [String] The cluster name.
|
|
23
|
+
# @parameter service_name [String] The EDS service name.
|
|
24
|
+
# @parameter load_balancing_policy [Envoy::Config::Cluster::V3::LoadBalancingPolicy | Nil] The typed Envoy load-balancing policy.
|
|
25
|
+
# @parameter connect_timeout [Numeric] The upstream connection timeout in seconds.
|
|
26
|
+
# @parameter protocol [Symbol] The canonical upstream protocol, either `:http1` or `:http2`.
|
|
27
|
+
# @returns [Envoy::Config::Cluster::V3::Cluster] The generated cluster resource.
|
|
28
|
+
# @raises [ArgumentError] If the upstream protocol is unsupported.
|
|
29
|
+
def build(name, service_name: name, load_balancing_policy: nil, connect_timeout: 5, protocol: :http2)
|
|
30
|
+
options = {
|
|
31
|
+
name: name.to_s,
|
|
32
|
+
type: Envoy::Config::Cluster::V3::Cluster::DiscoveryType::EDS,
|
|
33
|
+
eds_cluster_config: Envoy::Config::Cluster::V3::Cluster::EdsClusterConfig.new(
|
|
34
|
+
service_name: service_name.to_s,
|
|
35
|
+
eds_config: Envoy::Config::Core::V3::ConfigSource.new(
|
|
36
|
+
ads: Envoy::Config::Core::V3::AggregatedConfigSource.new
|
|
37
|
+
)
|
|
38
|
+
),
|
|
39
|
+
connect_timeout: duration(connect_timeout),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
options[:load_balancing_policy] = load_balancing_policy if load_balancing_policy
|
|
43
|
+
|
|
44
|
+
case protocol
|
|
45
|
+
when :http1
|
|
46
|
+
# Envoy uses HTTP/1 by default.
|
|
47
|
+
when :http2
|
|
48
|
+
options[:http2_protocol_options] = Envoy::Config::Core::V3::Http2ProtocolOptions.new
|
|
49
|
+
else
|
|
50
|
+
raise ArgumentError, "Unsupported upstream protocol: #{protocol.inspect}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
Envoy::Config::Cluster::V3::Cluster.new(**options)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# Convert seconds to a protobuf duration.
|
|
59
|
+
# @parameter seconds [Numeric] The duration in seconds.
|
|
60
|
+
# @returns [Google::Protobuf::Duration] The protobuf duration.
|
|
61
|
+
# @private
|
|
62
|
+
def duration(seconds)
|
|
63
|
+
whole_seconds = seconds.to_i
|
|
64
|
+
nanos = ((seconds.to_f - whole_seconds) * 1_000_000_000).to_i
|
|
65
|
+
|
|
66
|
+
Google::Protobuf::Duration.new(seconds: whole_seconds, nanos: nanos)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -10,17 +10,22 @@ require "async/queue"
|
|
|
10
10
|
|
|
11
11
|
require "envoy/config/core/v3/base_pb"
|
|
12
12
|
require "envoy/service/discovery/v3/discovery_pb"
|
|
13
|
+
require "google/protobuf/any_pb"
|
|
14
|
+
require "google/protobuf/well_known_types"
|
|
13
15
|
|
|
14
|
-
require_relative "
|
|
16
|
+
require_relative "cluster"
|
|
17
|
+
require_relative "endpoint"
|
|
15
18
|
|
|
16
19
|
module Async
|
|
17
20
|
module GRPC
|
|
18
21
|
module XDS
|
|
19
22
|
# Maintains xDS resource snapshots and notifies ADS streams when resources change.
|
|
20
23
|
class ControlPlane
|
|
21
|
-
CLUSTER_TYPE =
|
|
22
|
-
ENDPOINT_TYPE =
|
|
24
|
+
CLUSTER_TYPE = Cluster::TYPE_URL
|
|
25
|
+
ENDPOINT_TYPE = Endpoint::TYPE_URL
|
|
23
26
|
|
|
27
|
+
# Initialize an empty control plane.
|
|
28
|
+
# @parameter identifier [String] The identifier reported in discovery responses.
|
|
24
29
|
def initialize(identifier: "async-grpc-xds")
|
|
25
30
|
@identifier = identifier
|
|
26
31
|
@resources = Hash.new{|hash, type_url| hash[type_url] = {}}
|
|
@@ -31,27 +36,42 @@ module Async
|
|
|
31
36
|
|
|
32
37
|
attr :identifier
|
|
33
38
|
|
|
39
|
+
# Add or replace a cluster resource.
|
|
40
|
+
# @parameter name [String] The cluster name.
|
|
41
|
+
# @parameter resource [Envoy::Config::Cluster::V3::Cluster | Nil] An existing cluster resource, or `nil` to build one from `options`.
|
|
42
|
+
# @parameter options [Hash] Options forwarded to {Cluster.build}.
|
|
34
43
|
def update_cluster(name, resource = nil, **options)
|
|
35
|
-
resource ||=
|
|
44
|
+
resource ||= Cluster.build(name, **options)
|
|
36
45
|
update_resource(CLUSTER_TYPE, name.to_s, resource)
|
|
37
46
|
end
|
|
38
47
|
|
|
48
|
+
# Add or replace the endpoint assignment for a cluster.
|
|
49
|
+
# @parameter cluster_name [String] The cluster name.
|
|
50
|
+
# @parameter endpoints [Array(Hash)] The normalized endpoint states.
|
|
39
51
|
def update_endpoints(cluster_name, endpoints)
|
|
40
52
|
update_resource(
|
|
41
53
|
ENDPOINT_TYPE,
|
|
42
54
|
cluster_name.to_s,
|
|
43
|
-
|
|
55
|
+
Endpoint.build(cluster_name, endpoints)
|
|
44
56
|
)
|
|
45
57
|
end
|
|
46
58
|
|
|
59
|
+
# Remove a cluster resource.
|
|
60
|
+
# @parameter name [String] The cluster name.
|
|
47
61
|
def remove_cluster(name)
|
|
48
62
|
remove_resource(CLUSTER_TYPE, name.to_s)
|
|
49
63
|
end
|
|
50
64
|
|
|
65
|
+
# Remove the endpoint assignment for a cluster.
|
|
66
|
+
# @parameter cluster_name [String] The cluster name.
|
|
51
67
|
def remove_endpoints(cluster_name)
|
|
52
68
|
remove_resource(ENDPOINT_TYPE, cluster_name.to_s)
|
|
53
69
|
end
|
|
54
70
|
|
|
71
|
+
# Add or replace an xDS resource and notify subscribed streams.
|
|
72
|
+
# @parameter type_url [String] The xDS resource type URL.
|
|
73
|
+
# @parameter name [String] The resource name.
|
|
74
|
+
# @parameter resource [Google::Protobuf::MessageExts] The protobuf resource.
|
|
55
75
|
def update_resource(type_url, name, resource)
|
|
56
76
|
notify = false
|
|
57
77
|
|
|
@@ -64,6 +84,9 @@ module Async
|
|
|
64
84
|
notify_streams(type_url) if notify
|
|
65
85
|
end
|
|
66
86
|
|
|
87
|
+
# Remove an xDS resource and notify subscribed streams.
|
|
88
|
+
# @parameter type_url [String] The xDS resource type URL.
|
|
89
|
+
# @parameter name [String] The resource name.
|
|
67
90
|
def remove_resource(type_url, name)
|
|
68
91
|
notify = false
|
|
69
92
|
|
|
@@ -77,12 +100,19 @@ module Async
|
|
|
77
100
|
notify_streams(type_url) if notify
|
|
78
101
|
end
|
|
79
102
|
|
|
103
|
+
# Get the available resource names for a type.
|
|
104
|
+
# @parameter type_url [String] The xDS resource type URL.
|
|
105
|
+
# @returns [Array(String)] The resource names.
|
|
80
106
|
def resource_names(type_url)
|
|
81
107
|
@mutex.synchronize do
|
|
82
108
|
@resources[type_url].keys
|
|
83
109
|
end
|
|
84
110
|
end
|
|
85
111
|
|
|
112
|
+
# Get resources of a given type.
|
|
113
|
+
# @parameter type_url [String] The xDS resource type URL.
|
|
114
|
+
# @parameter names [Array(String) | Nil] The requested resource names, or `nil` for all resources.
|
|
115
|
+
# @returns [Array(Google::Protobuf::MessageExts)] The matching resources.
|
|
86
116
|
def resources(type_url, names = nil)
|
|
87
117
|
@mutex.synchronize do
|
|
88
118
|
resources = @resources[type_url]
|
|
@@ -95,31 +125,42 @@ module Async
|
|
|
95
125
|
end
|
|
96
126
|
end
|
|
97
127
|
|
|
128
|
+
# Get the current version for a resource type.
|
|
129
|
+
# @parameter type_url [String] The xDS resource type URL.
|
|
130
|
+
# @returns [String] The monotonically increasing version.
|
|
98
131
|
def version(type_url)
|
|
99
132
|
@mutex.synchronize do
|
|
100
133
|
@versions[type_url].to_s
|
|
101
134
|
end
|
|
102
135
|
end
|
|
103
136
|
|
|
137
|
+
# Build a discovery response for a resource type.
|
|
138
|
+
# @parameter type_url [String] The xDS resource type URL.
|
|
139
|
+
# @parameter names [Array(String) | Nil] The requested resource names, or `nil` for all resources.
|
|
140
|
+
# @returns [Envoy::Service::Discovery::V3::DiscoveryResponse] The current discovery response.
|
|
104
141
|
def response(type_url, names = nil)
|
|
105
142
|
resources = self.resources(type_url, names)
|
|
106
143
|
version = self.version(type_url)
|
|
107
144
|
|
|
108
145
|
Envoy::Service::Discovery::V3::DiscoveryResponse.new(
|
|
109
146
|
version_info: version,
|
|
110
|
-
resources: resources.map{|resource|
|
|
147
|
+
resources: resources.map{|resource| Google::Protobuf::Any.pack(resource)},
|
|
111
148
|
type_url: type_url,
|
|
112
149
|
nonce: "#{type_url}:#{version}:#{SecureRandom.hex(8)}",
|
|
113
150
|
control_plane: Envoy::Config::Core::V3::ControlPlane.new(identifier: @identifier)
|
|
114
151
|
)
|
|
115
152
|
end
|
|
116
153
|
|
|
154
|
+
# Register a stream to receive resource-change notifications.
|
|
155
|
+
# @parameter stream [Service::Stream] The stream to register.
|
|
117
156
|
def register_stream(stream)
|
|
118
157
|
@mutex.synchronize do
|
|
119
158
|
@streams.add(stream)
|
|
120
159
|
end
|
|
121
160
|
end
|
|
122
161
|
|
|
162
|
+
# Remove a registered stream.
|
|
163
|
+
# @parameter stream [Service::Stream] The stream to remove.
|
|
123
164
|
def remove_stream(stream)
|
|
124
165
|
@mutex.synchronize do
|
|
125
166
|
@streams.delete(stream)
|
|
@@ -16,6 +16,9 @@ require "envoy/config/cluster/v3/cluster_pb"
|
|
|
16
16
|
require "envoy/config/endpoint/v3/endpoint_pb"
|
|
17
17
|
require "google/protobuf/any_pb"
|
|
18
18
|
|
|
19
|
+
require_relative "cluster"
|
|
20
|
+
require_relative "endpoint"
|
|
21
|
+
|
|
19
22
|
module Async
|
|
20
23
|
module GRPC
|
|
21
24
|
module XDS
|
|
@@ -27,8 +30,8 @@ module Async
|
|
|
27
30
|
# xDS API type URLs (v3 API)
|
|
28
31
|
LISTENER_TYPE = "type.googleapis.com/envoy.config.listener.v3.Listener"
|
|
29
32
|
ROUTE_TYPE = "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"
|
|
30
|
-
CLUSTER_TYPE =
|
|
31
|
-
ENDPOINT_TYPE =
|
|
33
|
+
CLUSTER_TYPE = Cluster::TYPE_URL
|
|
34
|
+
ENDPOINT_TYPE = Endpoint::TYPE_URL
|
|
32
35
|
SECRET_TYPE = "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret"
|
|
33
36
|
|
|
34
37
|
# Initialize xDS discovery client
|
|
@@ -152,15 +155,22 @@ module Async
|
|
|
152
155
|
# ADSStream::Delegate interface - must be public for ADSStream to call
|
|
153
156
|
public
|
|
154
157
|
|
|
158
|
+
# Record that an ADS stream has opened and unblock pending subscriptions.
|
|
159
|
+
# @parameter stream [ADSStream] The opened stream.
|
|
155
160
|
def stream_opened(stream)
|
|
156
161
|
@mutex.synchronize{@ads_stream = stream}
|
|
157
162
|
@stream_ready_promise&.resolve(stream)
|
|
158
163
|
end
|
|
159
164
|
|
|
165
|
+
# Record that an ADS stream has closed.
|
|
166
|
+
# @parameter stream [ADSStream] The closed stream.
|
|
160
167
|
def stream_closed(stream)
|
|
161
168
|
@mutex.synchronize{@ads_stream = nil}
|
|
162
169
|
end
|
|
163
170
|
|
|
171
|
+
# Process a discovery response received by an ADS stream.
|
|
172
|
+
# @parameter response [Envoy::Service::Discovery::V3::DiscoveryResponse] The received response.
|
|
173
|
+
# @parameter stream [ADSStream] The stream that received the response.
|
|
164
174
|
def discovery_response(response, stream)
|
|
165
175
|
process_response(response, stream)
|
|
166
176
|
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "envoy/config/core/v3/address_pb"
|
|
7
|
+
require "envoy/config/endpoint/v3/endpoint_pb"
|
|
8
|
+
require "envoy/config/endpoint/v3/endpoint_components_pb"
|
|
9
|
+
|
|
10
|
+
module Async
|
|
11
|
+
module GRPC
|
|
12
|
+
module XDS
|
|
13
|
+
# Builds Envoy endpoint resources.
|
|
14
|
+
module Endpoint
|
|
15
|
+
extend self
|
|
16
|
+
|
|
17
|
+
TYPE_URL = "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"
|
|
18
|
+
|
|
19
|
+
# Build an EDS cluster load assignment from normalized endpoint state.
|
|
20
|
+
# @parameter cluster_name [String] The cluster name.
|
|
21
|
+
# @parameter endpoints [Array(Hash)] The endpoints, each containing `:addresses` and `:healthy`.
|
|
22
|
+
# @returns [Envoy::Config::Endpoint::V3::ClusterLoadAssignment] The generated endpoint resource.
|
|
23
|
+
def build(cluster_name, endpoints)
|
|
24
|
+
Envoy::Config::Endpoint::V3::ClusterLoadAssignment.new(
|
|
25
|
+
cluster_name: cluster_name.to_s,
|
|
26
|
+
endpoints: [
|
|
27
|
+
Envoy::Config::Endpoint::V3::LocalityLbEndpoints.new(
|
|
28
|
+
lb_endpoints: endpoints.map{|endpoint| load_balancer_endpoint(endpoint)}
|
|
29
|
+
)
|
|
30
|
+
]
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# Build an Envoy load-balancer endpoint from normalized endpoint state.
|
|
37
|
+
# @parameter endpoint [Hash] The endpoint containing `:addresses` and `:healthy`.
|
|
38
|
+
# @returns [Envoy::Config::Endpoint::V3::LbEndpoint] The generated load-balancer endpoint.
|
|
39
|
+
# @raises [KeyError] If required endpoint state is missing.
|
|
40
|
+
# @raises [ArgumentError] If the endpoint has no addresses.
|
|
41
|
+
# @private
|
|
42
|
+
def load_balancer_endpoint(endpoint)
|
|
43
|
+
addresses, healthy = endpoint.fetch_values(:addresses, :healthy)
|
|
44
|
+
raise ArgumentError, "An endpoint requires at least one address!" if addresses.empty?
|
|
45
|
+
|
|
46
|
+
address, *additional_addresses = addresses
|
|
47
|
+
|
|
48
|
+
Envoy::Config::Endpoint::V3::LbEndpoint.new(
|
|
49
|
+
endpoint: Envoy::Config::Endpoint::V3::Endpoint.new(
|
|
50
|
+
address: build_address(address),
|
|
51
|
+
hostname: endpoint[:hostname],
|
|
52
|
+
additional_addresses: additional_addresses.map do |additional_address|
|
|
53
|
+
Envoy::Config::Endpoint::V3::Endpoint::AdditionalAddress.new(
|
|
54
|
+
address: build_address(additional_address)
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
),
|
|
58
|
+
health_status: health_status_value(healthy)
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Build an Envoy address from a normalized IP or Unix address.
|
|
63
|
+
# @parameter address [Hash] An IP `:address` and `:port`, or a Unix `:path`.
|
|
64
|
+
# @returns [Envoy::Config::Core::V3::Address] The generated Envoy address.
|
|
65
|
+
# @raises [KeyError] If required IP address state is missing.
|
|
66
|
+
# @private
|
|
67
|
+
def build_address(address)
|
|
68
|
+
if path = address[:path]
|
|
69
|
+
Envoy::Config::Core::V3::Address.new(
|
|
70
|
+
pipe: Envoy::Config::Core::V3::Pipe.new(path: path)
|
|
71
|
+
)
|
|
72
|
+
else
|
|
73
|
+
Envoy::Config::Core::V3::Address.new(
|
|
74
|
+
socket_address: Envoy::Config::Core::V3::SocketAddress.new(
|
|
75
|
+
protocol: Envoy::Config::Core::V3::SocketAddress::Protocol::TCP,
|
|
76
|
+
address: address.fetch(:address),
|
|
77
|
+
port_value: address.fetch(:port)
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Convert an endpoint health status to its Envoy enum value.
|
|
84
|
+
# @parameter healthy [Boolean | Symbol | String] The normalized health status.
|
|
85
|
+
# @returns [Integer] The Envoy health-status enum value.
|
|
86
|
+
# @private
|
|
87
|
+
def health_status_value(healthy)
|
|
88
|
+
case healthy
|
|
89
|
+
when :healthy, :HEALTHY, "healthy", "HEALTHY", true
|
|
90
|
+
Envoy::Config::Core::V3::HealthStatus::HEALTHY
|
|
91
|
+
when :unhealthy, :UNHEALTHY, "unhealthy", "UNHEALTHY", false
|
|
92
|
+
Envoy::Config::Core::V3::HealthStatus::UNHEALTHY
|
|
93
|
+
when :degraded, :DEGRADED, "degraded", "DEGRADED"
|
|
94
|
+
Envoy::Config::Core::V3::HealthStatus::DEGRADED
|
|
95
|
+
else
|
|
96
|
+
Envoy::Config::Core::V3::HealthStatus::UNKNOWN
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
module Async
|
|
7
7
|
module GRPC
|
|
8
8
|
module XDS
|
|
9
|
+
# @namespace
|
|
9
10
|
module Resources
|
|
10
11
|
# Represents a discovered cluster
|
|
11
12
|
# Based on envoy.config.cluster.v3.Cluster
|
|
@@ -40,6 +41,8 @@ module Async
|
|
|
40
41
|
new(proto)
|
|
41
42
|
end
|
|
42
43
|
|
|
44
|
+
# Determine whether this cluster uses the Endpoint Discovery Service.
|
|
45
|
+
# @returns [Boolean] `true` if the cluster uses EDS.
|
|
43
46
|
def eds_cluster?
|
|
44
47
|
@type == :EDS
|
|
45
48
|
end
|
|
@@ -216,6 +219,8 @@ module Async
|
|
|
216
219
|
class Endpoint
|
|
217
220
|
attr_reader :address, :port, :health_status, :metadata
|
|
218
221
|
|
|
222
|
+
# Initialize an endpoint from a protobuf message or hash.
|
|
223
|
+
# @parameter load_balancer_endpoint [Envoy::Config::Endpoint::V3::LbEndpoint | Hash] The load-balancer endpoint representation.
|
|
219
224
|
def initialize(load_balancer_endpoint)
|
|
220
225
|
if load_balancer_endpoint.is_a?(Hash)
|
|
221
226
|
endpoint_data = load_balancer_endpoint[:endpoint] || {}
|
|
@@ -235,10 +240,14 @@ module Async
|
|
|
235
240
|
end
|
|
236
241
|
end
|
|
237
242
|
|
|
243
|
+
# Determine whether this endpoint is eligible to receive traffic.
|
|
244
|
+
# @returns [Boolean] `true` if the endpoint is healthy or has unknown health.
|
|
238
245
|
def healthy?
|
|
239
246
|
@health_status == :HEALTHY || @health_status == :UNKNOWN
|
|
240
247
|
end
|
|
241
248
|
|
|
249
|
+
# Build the HTTP URI for this endpoint.
|
|
250
|
+
# @returns [String] The endpoint URI.
|
|
242
251
|
def uri
|
|
243
252
|
# Use http for insecure/docker environments (gRPC h2c)
|
|
244
253
|
scheme = ENV["XDS_ENDPOINT_SCHEME"] || "http"
|
|
@@ -14,6 +14,9 @@ module Async
|
|
|
14
14
|
module XDS
|
|
15
15
|
# Convenience wrapper for serving an xDS control plane over gRPC.
|
|
16
16
|
class Server
|
|
17
|
+
# Initialize an xDS server.
|
|
18
|
+
# @parameter control_plane [ControlPlane] The control plane to serve.
|
|
19
|
+
# @parameter options [Hash] Default options forwarded to `Async::HTTP::Server`.
|
|
17
20
|
def initialize(control_plane = ControlPlane.new, **options)
|
|
18
21
|
@control_plane = control_plane
|
|
19
22
|
@dispatcher = Async::GRPC::Dispatcher.new
|
|
@@ -24,6 +27,10 @@ module Async
|
|
|
24
27
|
attr :control_plane
|
|
25
28
|
attr :dispatcher
|
|
26
29
|
|
|
30
|
+
# Run the xDS server on an endpoint.
|
|
31
|
+
# @parameter endpoint [Async::HTTP::Endpoint] The endpoint to bind.
|
|
32
|
+
# @parameter options [Hash] Options forwarded to `Async::HTTP::Server`.
|
|
33
|
+
# @asynchronous
|
|
27
34
|
def run(endpoint, **options)
|
|
28
35
|
server = Async::HTTP::Server.new(@dispatcher, endpoint, **@options, **options)
|
|
29
36
|
server.run
|
|
@@ -21,11 +21,18 @@ module Async
|
|
|
21
21
|
class Service < Async::GRPC::Service
|
|
22
22
|
SERVICE_NAME = "envoy.service.discovery.v3.AggregatedDiscoveryService"
|
|
23
23
|
|
|
24
|
+
# Initialize an Aggregated Discovery Service.
|
|
25
|
+
# @parameter control_plane [ControlPlane] The control plane that provides resources.
|
|
24
26
|
def initialize(control_plane)
|
|
25
27
|
super(Envoy::Service::Discovery::V3::AggregatedDiscoveryService, SERVICE_NAME)
|
|
26
28
|
@control_plane = control_plane
|
|
27
29
|
end
|
|
28
30
|
|
|
31
|
+
# Serve a state-of-the-world Aggregated Discovery Service stream.
|
|
32
|
+
# @parameter input [Enumerable] The stream of discovery requests.
|
|
33
|
+
# @parameter output [Interface(:write)] The discovery response stream.
|
|
34
|
+
# @parameter call [Object] The gRPC call context.
|
|
35
|
+
# @asynchronous
|
|
29
36
|
def stream_aggregated_resources(input, output, call)
|
|
30
37
|
stream = Stream.new(@control_plane, output)
|
|
31
38
|
@control_plane.register_stream(stream)
|
|
@@ -48,6 +55,11 @@ module Async
|
|
|
48
55
|
@control_plane.remove_stream(stream) if stream
|
|
49
56
|
end
|
|
50
57
|
|
|
58
|
+
# Reject a delta Aggregated Discovery Service stream, which is not supported.
|
|
59
|
+
# @parameter input [Enumerable] The stream of delta discovery requests.
|
|
60
|
+
# @parameter output [Interface(:write)] The delta discovery response stream.
|
|
61
|
+
# @parameter call [Object] The gRPC call context.
|
|
62
|
+
# @raises [Protocol::GRPC::Error] Always raised because delta xDS is not implemented.
|
|
51
63
|
def delta_aggregated_resources(input, output, call)
|
|
52
64
|
raise Protocol::GRPC::Error.new(
|
|
53
65
|
Protocol::GRPC::Status::UNIMPLEMENTED,
|
|
@@ -57,6 +69,9 @@ module Async
|
|
|
57
69
|
|
|
58
70
|
# Represents one ADS stream and its subscribed resources.
|
|
59
71
|
class Stream
|
|
72
|
+
# Initialize an ADS stream.
|
|
73
|
+
# @parameter control_plane [ControlPlane] The control plane that provides resources.
|
|
74
|
+
# @parameter output [Interface(:write)] The discovery response stream.
|
|
60
75
|
def initialize(control_plane, output)
|
|
61
76
|
@control_plane = control_plane
|
|
62
77
|
@output = output
|
|
@@ -66,6 +81,8 @@ module Async
|
|
|
66
81
|
@closed = false
|
|
67
82
|
end
|
|
68
83
|
|
|
84
|
+
# Process a discovery request and update the stream's subscriptions.
|
|
85
|
+
# @parameter request [Envoy::Service::Discovery::V3::DiscoveryRequest] The discovery request.
|
|
69
86
|
def request(request)
|
|
70
87
|
return if request.type_url.nil? || request.type_url.empty?
|
|
71
88
|
|
|
@@ -83,10 +100,14 @@ module Async
|
|
|
83
100
|
@queue << request.type_url
|
|
84
101
|
end
|
|
85
102
|
|
|
103
|
+
# Schedule a resource type for delivery after it changes.
|
|
104
|
+
# @parameter type_url [String] The changed xDS resource type URL.
|
|
86
105
|
def changed(type_url)
|
|
87
106
|
@queue << type_url unless @closed
|
|
88
107
|
end
|
|
89
108
|
|
|
109
|
+
# Deliver scheduled resource updates until the stream closes.
|
|
110
|
+
# @asynchronous
|
|
90
111
|
def run
|
|
91
112
|
until @closed
|
|
92
113
|
type_url = @queue.dequeue
|
|
@@ -94,6 +115,8 @@ module Async
|
|
|
94
115
|
end
|
|
95
116
|
end
|
|
96
117
|
|
|
118
|
+
# Deliver the latest resource version for a subscribed type.
|
|
119
|
+
# @parameter type_url [String] The xDS resource type URL.
|
|
97
120
|
def flush(type_url)
|
|
98
121
|
names = @subscriptions[type_url]
|
|
99
122
|
return unless names
|
|
@@ -106,6 +129,7 @@ module Async
|
|
|
106
129
|
@versions[type_url] = version
|
|
107
130
|
end
|
|
108
131
|
|
|
132
|
+
# Close the stream and stop waiting for changes.
|
|
109
133
|
def close
|
|
110
134
|
@closed = true
|
|
111
135
|
@queue.close
|
data/lib/async/grpc/xds.rb
CHANGED
|
@@ -13,7 +13,9 @@ require_relative "xds/health_checker"
|
|
|
13
13
|
require_relative "xds/load_balancer"
|
|
14
14
|
require_relative "xds/context"
|
|
15
15
|
require_relative "xds/client"
|
|
16
|
-
require_relative "xds/
|
|
16
|
+
require_relative "xds/cluster"
|
|
17
|
+
require_relative "xds/endpoint"
|
|
18
|
+
require_relative "xds/client_side_weighted_round_robin"
|
|
17
19
|
require_relative "xds/control_plane"
|
|
18
20
|
require_relative "xds/service"
|
|
19
21
|
require_relative "xds/server"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
4
|
+
# source: envoy/config/common/mutation_rules/v3/mutation_rules.proto
|
|
5
|
+
|
|
6
|
+
require "google/protobuf"
|
|
7
|
+
|
|
8
|
+
require "envoy/config/core/v3/base_pb"
|
|
9
|
+
require "envoy/type/matcher/v3/regex_pb"
|
|
10
|
+
require "envoy/type/matcher/v3/string_pb"
|
|
11
|
+
require "google/protobuf/wrappers_pb"
|
|
12
|
+
require "udpa/annotations/status_pb"
|
|
13
|
+
require "validate/validate_pb"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
descriptor_data = "\n:envoy/config/common/mutation_rules/v3/mutation_rules.proto\x12%envoy.config.common.mutation_rules.v3\x1a\x1f\x65nvoy/config/core/v3/base.proto\x1a!envoy/type/matcher/v3/regex.proto\x1a\"envoy/type/matcher/v3/string.proto\x1a\x1egoogle/protobuf/wrappers.proto\x1a\x1dudpa/annotations/status.proto\x1a\x17validate/validate.proto\"\x9c\x03\n\x13HeaderMutationRules\x12\x35\n\x11\x61llow_all_routing\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12/\n\x0b\x61llow_envoy\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12\x33\n\x0f\x64isallow_system\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12\x30\n\x0c\x64isallow_all\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12=\n\x10\x61llow_expression\x18\x05 \x01(\x0b\x32#.envoy.type.matcher.v3.RegexMatcher\x12@\n\x13\x64isallow_expression\x18\x06 \x01(\x0b\x32#.envoy.type.matcher.v3.RegexMatcher\x12\x35\n\x11\x64isallow_is_error\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\"\xaf\x02\n\x0eHeaderMutation\x12\x1d\n\x06remove\x18\x01 \x01(\tB\x0b\xfa\x42\x08r\x06\xc0\x01\x02\xc8\x01\x00H\x00\x12\x39\n\x06\x61ppend\x18\x02 \x01(\x0b\x32\'.envoy.config.core.v3.HeaderValueOptionH\x00\x12^\n\x0fremove_on_match\x18\x03 \x01(\x0b\x32\x43.envoy.config.common.mutation_rules.v3.HeaderMutation.RemoveOnMatchH\x00\x1aT\n\rRemoveOnMatch\x12\x43\n\x0bkey_matcher\x18\x01 \x01(\x0b\x32$.envoy.type.matcher.v3.StringMatcherB\x08\xfa\x42\x05\x8a\x01\x02\x10\x01\x42\r\n\x06\x61\x63tion\x12\x03\xf8\x42\x01\x42\xb2\x01\n3io.envoyproxy.envoy.config.common.mutation_rules.v3B\x12MutationRulesProtoP\x01Z]github.com/envoyproxy/go-control-plane/envoy/config/common/mutation_rules/v3;mutation_rulesv3\xba\x80\xc8\xd1\x06\x02\x10\x02\x62\x06proto3"
|
|
17
|
+
|
|
18
|
+
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
19
|
+
pool.add_serialized_file(descriptor_data)
|
|
20
|
+
|
|
21
|
+
module Envoy
|
|
22
|
+
module Config
|
|
23
|
+
module Common
|
|
24
|
+
module MutationRules
|
|
25
|
+
module V3
|
|
26
|
+
HeaderMutationRules = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("envoy.config.common.mutation_rules.v3.HeaderMutationRules").msgclass
|
|
27
|
+
HeaderMutation = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("envoy.config.common.mutation_rules.v3.HeaderMutation").msgclass
|
|
28
|
+
HeaderMutation::RemoveOnMatch = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("envoy.config.common.mutation_rules.v3.HeaderMutation.RemoveOnMatch").msgclass
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|