async-grpc-xds 0.0.1 → 0.1.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/control_plane.rb +38 -0
- data/lib/async/grpc/xds/discovery_client.rb +7 -0
- data/lib/async/grpc/xds/resource_builder.rb +79 -40
- 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/readme.md +10 -2
- data/releases.md +6 -0
- data/xds/readme.md +2 -29
- data/xds/test/async/grpc/xds/control_plane.rb +5 -5
- data.tar.gz.sig +0 -0
- metadata +1 -1
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 668ae495babdb4aa2afd890a356bf9f7d80f4e1649213af63e5e0804432983ba
|
|
4
|
+
data.tar.gz: 3634be8ae4e830a673c5706de0c7ed2f08462de1edcee0c7ffc401f9b1d3b2ab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4596e7e891e0a716f3cf8126f1801975114090c52c75d7c288550c4ce40109d51f14c6afb6e729a01d1d6f24ead8da17b2f3b74bcff3157b76c443cd60edebb5
|
|
7
|
+
data.tar.gz: 14f57efc4e56588c8ff1819838ae1894ddd33a54b823c8c577c7addf30e0efed2742a4cd11cceefb559e1615d9198065860534b981b14bd7b0b91aae8eb13391
|
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
|
|
@@ -21,6 +21,8 @@ module Async
|
|
|
21
21
|
CLUSTER_TYPE = ResourceBuilder::CLUSTER_TYPE
|
|
22
22
|
ENDPOINT_TYPE = ResourceBuilder::ENDPOINT_TYPE
|
|
23
23
|
|
|
24
|
+
# Initialize an empty control plane.
|
|
25
|
+
# @parameter identifier [String] The identifier reported in discovery responses.
|
|
24
26
|
def initialize(identifier: "async-grpc-xds")
|
|
25
27
|
@identifier = identifier
|
|
26
28
|
@resources = Hash.new{|hash, type_url| hash[type_url] = {}}
|
|
@@ -31,11 +33,18 @@ module Async
|
|
|
31
33
|
|
|
32
34
|
attr :identifier
|
|
33
35
|
|
|
36
|
+
# Add or replace a cluster resource.
|
|
37
|
+
# @parameter name [String] The cluster name.
|
|
38
|
+
# @parameter resource [Envoy::Config::Cluster::V3::Cluster | Nil] An existing cluster resource, or `nil` to build one from `options`.
|
|
39
|
+
# @parameter options [Hash] Options forwarded to {ResourceBuilder.cluster}.
|
|
34
40
|
def update_cluster(name, resource = nil, **options)
|
|
35
41
|
resource ||= ResourceBuilder.cluster(name, **options)
|
|
36
42
|
update_resource(CLUSTER_TYPE, name.to_s, resource)
|
|
37
43
|
end
|
|
38
44
|
|
|
45
|
+
# Add or replace the endpoint assignment for a cluster.
|
|
46
|
+
# @parameter cluster_name [String] The cluster name.
|
|
47
|
+
# @parameter endpoints [Array(Hash)] The normalized endpoint states.
|
|
39
48
|
def update_endpoints(cluster_name, endpoints)
|
|
40
49
|
update_resource(
|
|
41
50
|
ENDPOINT_TYPE,
|
|
@@ -44,14 +53,22 @@ module Async
|
|
|
44
53
|
)
|
|
45
54
|
end
|
|
46
55
|
|
|
56
|
+
# Remove a cluster resource.
|
|
57
|
+
# @parameter name [String] The cluster name.
|
|
47
58
|
def remove_cluster(name)
|
|
48
59
|
remove_resource(CLUSTER_TYPE, name.to_s)
|
|
49
60
|
end
|
|
50
61
|
|
|
62
|
+
# Remove the endpoint assignment for a cluster.
|
|
63
|
+
# @parameter cluster_name [String] The cluster name.
|
|
51
64
|
def remove_endpoints(cluster_name)
|
|
52
65
|
remove_resource(ENDPOINT_TYPE, cluster_name.to_s)
|
|
53
66
|
end
|
|
54
67
|
|
|
68
|
+
# Add or replace an xDS resource and notify subscribed streams.
|
|
69
|
+
# @parameter type_url [String] The xDS resource type URL.
|
|
70
|
+
# @parameter name [String] The resource name.
|
|
71
|
+
# @parameter resource [Google::Protobuf::MessageExts] The protobuf resource.
|
|
55
72
|
def update_resource(type_url, name, resource)
|
|
56
73
|
notify = false
|
|
57
74
|
|
|
@@ -64,6 +81,9 @@ module Async
|
|
|
64
81
|
notify_streams(type_url) if notify
|
|
65
82
|
end
|
|
66
83
|
|
|
84
|
+
# Remove an xDS resource and notify subscribed streams.
|
|
85
|
+
# @parameter type_url [String] The xDS resource type URL.
|
|
86
|
+
# @parameter name [String] The resource name.
|
|
67
87
|
def remove_resource(type_url, name)
|
|
68
88
|
notify = false
|
|
69
89
|
|
|
@@ -77,12 +97,19 @@ module Async
|
|
|
77
97
|
notify_streams(type_url) if notify
|
|
78
98
|
end
|
|
79
99
|
|
|
100
|
+
# Get the available resource names for a type.
|
|
101
|
+
# @parameter type_url [String] The xDS resource type URL.
|
|
102
|
+
# @returns [Array(String)] The resource names.
|
|
80
103
|
def resource_names(type_url)
|
|
81
104
|
@mutex.synchronize do
|
|
82
105
|
@resources[type_url].keys
|
|
83
106
|
end
|
|
84
107
|
end
|
|
85
108
|
|
|
109
|
+
# Get resources of a given type.
|
|
110
|
+
# @parameter type_url [String] The xDS resource type URL.
|
|
111
|
+
# @parameter names [Array(String) | Nil] The requested resource names, or `nil` for all resources.
|
|
112
|
+
# @returns [Array(Google::Protobuf::MessageExts)] The matching resources.
|
|
86
113
|
def resources(type_url, names = nil)
|
|
87
114
|
@mutex.synchronize do
|
|
88
115
|
resources = @resources[type_url]
|
|
@@ -95,12 +122,19 @@ module Async
|
|
|
95
122
|
end
|
|
96
123
|
end
|
|
97
124
|
|
|
125
|
+
# Get the current version for a resource type.
|
|
126
|
+
# @parameter type_url [String] The xDS resource type URL.
|
|
127
|
+
# @returns [String] The monotonically increasing version.
|
|
98
128
|
def version(type_url)
|
|
99
129
|
@mutex.synchronize do
|
|
100
130
|
@versions[type_url].to_s
|
|
101
131
|
end
|
|
102
132
|
end
|
|
103
133
|
|
|
134
|
+
# Build a discovery response for a resource type.
|
|
135
|
+
# @parameter type_url [String] The xDS resource type URL.
|
|
136
|
+
# @parameter names [Array(String) | Nil] The requested resource names, or `nil` for all resources.
|
|
137
|
+
# @returns [Envoy::Service::Discovery::V3::DiscoveryResponse] The current discovery response.
|
|
104
138
|
def response(type_url, names = nil)
|
|
105
139
|
resources = self.resources(type_url, names)
|
|
106
140
|
version = self.version(type_url)
|
|
@@ -114,12 +148,16 @@ module Async
|
|
|
114
148
|
)
|
|
115
149
|
end
|
|
116
150
|
|
|
151
|
+
# Register a stream to receive resource-change notifications.
|
|
152
|
+
# @parameter stream [Service::Stream] The stream to register.
|
|
117
153
|
def register_stream(stream)
|
|
118
154
|
@mutex.synchronize do
|
|
119
155
|
@streams.add(stream)
|
|
120
156
|
end
|
|
121
157
|
end
|
|
122
158
|
|
|
159
|
+
# Remove a registered stream.
|
|
160
|
+
# @parameter stream [Service::Stream] The stream to remove.
|
|
123
161
|
def remove_stream(stream)
|
|
124
162
|
@mutex.synchronize do
|
|
125
163
|
@streams.delete(stream)
|
|
@@ -152,15 +152,22 @@ module Async
|
|
|
152
152
|
# ADSStream::Delegate interface - must be public for ADSStream to call
|
|
153
153
|
public
|
|
154
154
|
|
|
155
|
+
# Record that an ADS stream has opened and unblock pending subscriptions.
|
|
156
|
+
# @parameter stream [ADSStream] The opened stream.
|
|
155
157
|
def stream_opened(stream)
|
|
156
158
|
@mutex.synchronize{@ads_stream = stream}
|
|
157
159
|
@stream_ready_promise&.resolve(stream)
|
|
158
160
|
end
|
|
159
161
|
|
|
162
|
+
# Record that an ADS stream has closed.
|
|
163
|
+
# @parameter stream [ADSStream] The closed stream.
|
|
160
164
|
def stream_closed(stream)
|
|
161
165
|
@mutex.synchronize{@ads_stream = nil}
|
|
162
166
|
end
|
|
163
167
|
|
|
168
|
+
# Process a discovery response received by an ADS stream.
|
|
169
|
+
# @parameter response [Envoy::Service::Discovery::V3::DiscoveryResponse] The received response.
|
|
170
|
+
# @parameter stream [ADSStream] The stream that received the response.
|
|
164
171
|
def discovery_response(response, stream)
|
|
165
172
|
process_response(response, stream)
|
|
166
173
|
end
|
|
@@ -24,6 +24,9 @@ module Async
|
|
|
24
24
|
CLUSTER_TYPE = "#{TYPE_URL_PREFIX}/envoy.config.cluster.v3.Cluster"
|
|
25
25
|
ENDPOINT_TYPE = "#{TYPE_URL_PREFIX}/envoy.config.endpoint.v3.ClusterLoadAssignment"
|
|
26
26
|
|
|
27
|
+
# Pack a protobuf resource into a `google.protobuf.Any` message.
|
|
28
|
+
# @parameter resource [Google::Protobuf::MessageExts] The protobuf resource to pack.
|
|
29
|
+
# @returns [Google::Protobuf::Any] The packed resource.
|
|
27
30
|
def self.pack(resource)
|
|
28
31
|
Google::Protobuf::Any.new(
|
|
29
32
|
type_url: "#{TYPE_URL_PREFIX}/#{resource.class.descriptor.name}",
|
|
@@ -31,8 +34,16 @@ module Async
|
|
|
31
34
|
)
|
|
32
35
|
end
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
# Build an EDS cluster resource.
|
|
38
|
+
# @parameter name [String] The cluster name.
|
|
39
|
+
# @parameter service_name [String] The EDS service name.
|
|
40
|
+
# @parameter load_balancer_policy [Symbol] The Envoy load-balancing policy.
|
|
41
|
+
# @parameter connect_timeout [Numeric] The upstream connection timeout in seconds.
|
|
42
|
+
# @parameter protocol [Symbol] The canonical upstream protocol, either `:http1` or `:http2`.
|
|
43
|
+
# @returns [Envoy::Config::Cluster::V3::Cluster] The generated cluster resource.
|
|
44
|
+
# @raises [ArgumentError] If the upstream protocol is unsupported.
|
|
45
|
+
def self.cluster(name, service_name: name, load_balancer_policy: :round_robin, connect_timeout: 5, protocol: :http2)
|
|
46
|
+
options = {
|
|
36
47
|
name: name.to_s,
|
|
37
48
|
type: Envoy::Config::Cluster::V3::Cluster::DiscoveryType::EDS,
|
|
38
49
|
eds_cluster_config: Envoy::Config::Cluster::V3::Cluster::EdsClusterConfig.new(
|
|
@@ -43,10 +54,24 @@ module Async
|
|
|
43
54
|
),
|
|
44
55
|
connect_timeout: duration(connect_timeout),
|
|
45
56
|
lb_policy: load_balancer_policy_value(load_balancer_policy),
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
case protocol
|
|
60
|
+
when :http1
|
|
61
|
+
# Envoy uses HTTP/1 by default.
|
|
62
|
+
when :http2
|
|
63
|
+
options[:http2_protocol_options] = Envoy::Config::Core::V3::Http2ProtocolOptions.new
|
|
64
|
+
else
|
|
65
|
+
raise ArgumentError, "Unsupported upstream protocol: #{protocol.inspect}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
Envoy::Config::Cluster::V3::Cluster.new(**options)
|
|
48
69
|
end
|
|
49
70
|
|
|
71
|
+
# Build an EDS cluster load assignment from normalized endpoint state.
|
|
72
|
+
# @parameter cluster_name [String] The cluster name.
|
|
73
|
+
# @parameter endpoints [Array(Hash)] The endpoints, each containing `:addresses` and `:healthy`.
|
|
74
|
+
# @returns [Envoy::Config::Endpoint::V3::ClusterLoadAssignment] The generated load assignment.
|
|
50
75
|
def self.cluster_load_assignment(cluster_name, endpoints)
|
|
51
76
|
Envoy::Config::Endpoint::V3::ClusterLoadAssignment.new(
|
|
52
77
|
cluster_name: cluster_name.to_s,
|
|
@@ -57,55 +82,67 @@ module Async
|
|
|
57
82
|
]
|
|
58
83
|
)
|
|
59
84
|
end
|
|
60
|
-
|
|
85
|
+
|
|
86
|
+
# Build an Envoy load-balancer endpoint from normalized endpoint state.
|
|
87
|
+
# @parameter endpoint [Hash] The endpoint containing `:addresses` and `:healthy`.
|
|
88
|
+
# @returns [Envoy::Config::Endpoint::V3::LbEndpoint] The generated load-balancer endpoint.
|
|
89
|
+
# @raises [KeyError] If required endpoint state is missing.
|
|
90
|
+
# @raises [ArgumentError] If the endpoint has no addresses.
|
|
61
91
|
def self.load_balancer_endpoint(endpoint)
|
|
62
|
-
|
|
63
|
-
|
|
92
|
+
addresses, healthy = endpoint.fetch_values(:addresses, :healthy)
|
|
93
|
+
raise ArgumentError, "An endpoint requires at least one address!" if addresses.empty?
|
|
94
|
+
|
|
95
|
+
address, *additional_addresses = addresses
|
|
96
|
+
|
|
64
97
|
Envoy::Config::Endpoint::V3::LbEndpoint.new(
|
|
65
98
|
endpoint: Envoy::Config::Endpoint::V3::Endpoint.new(
|
|
66
|
-
address:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
address:
|
|
70
|
-
port_value: endpoint[:port]
|
|
99
|
+
address: build_address(address),
|
|
100
|
+
additional_addresses: additional_addresses.map do |additional_address|
|
|
101
|
+
Envoy::Config::Endpoint::V3::Endpoint::AdditionalAddress.new(
|
|
102
|
+
address: build_address(additional_address)
|
|
71
103
|
)
|
|
72
|
-
|
|
73
|
-
hostname: endpoint[:hostname].to_s
|
|
104
|
+
end
|
|
74
105
|
),
|
|
75
|
-
health_status: health_status_value(
|
|
106
|
+
health_status: health_status_value(healthy)
|
|
76
107
|
)
|
|
77
108
|
end
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
109
|
+
|
|
110
|
+
# Build an Envoy address from a normalized IP or Unix address.
|
|
111
|
+
# @parameter address [Hash] An IP `:address` and `:port`, or a Unix `:path`.
|
|
112
|
+
# @returns [Envoy::Config::Core::V3::Address] The generated Envoy address.
|
|
113
|
+
# @raises [KeyError] If required IP address state is missing.
|
|
114
|
+
# @private
|
|
115
|
+
def self.build_address(address)
|
|
116
|
+
if path = address[:path]
|
|
117
|
+
Envoy::Config::Core::V3::Address.new(
|
|
118
|
+
pipe: Envoy::Config::Core::V3::Pipe.new(path: path)
|
|
119
|
+
)
|
|
88
120
|
else
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
else
|
|
97
|
-
raise ArgumentError, "Invalid endpoint: #{endpoint.inspect}"
|
|
98
|
-
end
|
|
121
|
+
Envoy::Config::Core::V3::Address.new(
|
|
122
|
+
socket_address: Envoy::Config::Core::V3::SocketAddress.new(
|
|
123
|
+
protocol: Envoy::Config::Core::V3::SocketAddress::Protocol::TCP,
|
|
124
|
+
address: address.fetch(:address),
|
|
125
|
+
port_value: address.fetch(:port)
|
|
126
|
+
)
|
|
127
|
+
)
|
|
99
128
|
end
|
|
100
129
|
end
|
|
101
|
-
|
|
130
|
+
|
|
131
|
+
private_class_method :build_address
|
|
132
|
+
|
|
133
|
+
# Convert seconds to a protobuf duration.
|
|
134
|
+
# @parameter seconds [Numeric] The duration in seconds.
|
|
135
|
+
# @returns [Google::Protobuf::Duration] The protobuf duration.
|
|
102
136
|
def self.duration(seconds)
|
|
103
137
|
whole_seconds = seconds.to_i
|
|
104
138
|
nanos = ((seconds.to_f - whole_seconds) * 1_000_000_000).to_i
|
|
105
|
-
|
|
139
|
+
|
|
106
140
|
Google::Protobuf::Duration.new(seconds: whole_seconds, nanos: nanos)
|
|
107
141
|
end
|
|
108
|
-
|
|
142
|
+
|
|
143
|
+
# Convert a load-balancer policy name to its Envoy enum value.
|
|
144
|
+
# @parameter load_balancer_policy [Symbol | String | Integer] The load-balancer policy.
|
|
145
|
+
# @returns [Integer] The Envoy load-balancer policy enum value.
|
|
109
146
|
def self.load_balancer_policy_value(load_balancer_policy)
|
|
110
147
|
case load_balancer_policy
|
|
111
148
|
when :round_robin, :ROUND_ROBIN, "round_robin", "ROUND_ROBIN"
|
|
@@ -118,7 +155,10 @@ module Async
|
|
|
118
155
|
load_balancer_policy
|
|
119
156
|
end
|
|
120
157
|
end
|
|
121
|
-
|
|
158
|
+
|
|
159
|
+
# Convert an endpoint health status to its Envoy enum value.
|
|
160
|
+
# @parameter healthy [Boolean | Symbol | String] The normalized health status.
|
|
161
|
+
# @returns [Integer] The Envoy health-status enum value.
|
|
122
162
|
def self.health_status_value(healthy)
|
|
123
163
|
case healthy
|
|
124
164
|
when :healthy, :HEALTHY, "healthy", "HEALTHY", true
|
|
@@ -135,4 +175,3 @@ module Async
|
|
|
135
175
|
end
|
|
136
176
|
end
|
|
137
177
|
end
|
|
138
|
-
|
|
@@ -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/readme.md
CHANGED
|
@@ -19,7 +19,7 @@ This is an early implementation focused on ADS with CDS and EDS. LDS/RDS, full r
|
|
|
19
19
|
The `xds/` directory contains a Docker Compose integration environment with a Go xDS control plane and Ruby gRPC backends.
|
|
20
20
|
|
|
21
21
|
``` bash
|
|
22
|
-
docker
|
|
22
|
+
docker compose -f xds/docker-compose.yaml up --build --exit-code-from tests
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Releases
|
|
@@ -28,7 +28,15 @@ Please see the [project releases](https://socketry.github.io/async-grpc-xds/rele
|
|
|
28
28
|
|
|
29
29
|
### v0.1.0
|
|
30
30
|
|
|
31
|
-
-
|
|
31
|
+
- Add complete documentation coverage for the public xDS API.
|
|
32
|
+
- Support grouped IP and Unix-domain-socket addresses in EDS endpoints.
|
|
33
|
+
- Support selecting HTTP/1 or HTTP/2 for generated clusters.
|
|
34
|
+
|
|
35
|
+
### v0.1.0
|
|
36
|
+
|
|
37
|
+
- Add complete documentation coverage for the public xDS API.
|
|
38
|
+
- Support grouped IP and Unix-domain-socket addresses in EDS endpoints.
|
|
39
|
+
- Support selecting HTTP/1 or HTTP/2 for generated clusters.
|
|
32
40
|
|
|
33
41
|
## See Also
|
|
34
42
|
|
data/releases.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.1.0
|
|
4
|
+
|
|
5
|
+
- Add complete documentation coverage for the public xDS API.
|
|
6
|
+
- Support grouped IP and Unix-domain-socket addresses in EDS endpoints.
|
|
7
|
+
- Support selecting HTTP/1 or HTTP/2 for generated clusters.
|
|
8
|
+
|
|
3
9
|
## v0.1.0
|
|
4
10
|
|
|
5
11
|
- Initial extraction from `async-grpc`.
|
data/xds/readme.md
CHANGED
|
@@ -11,38 +11,11 @@ The Docker Compose setup includes:
|
|
|
11
11
|
|
|
12
12
|
## Running Tests
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
From this directory:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
cd xds
|
|
18
|
-
|
|
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
|
|
18
|
+
docker compose up --build --exit-code-from tests
|
|
46
19
|
```
|
|
47
20
|
|
|
48
21
|
## Test Structure
|
|
@@ -33,8 +33,8 @@ describe Async::GRPC::XDS::ControlPlane do
|
|
|
33
33
|
control_plane.update_endpoints(
|
|
34
34
|
"myservice",
|
|
35
35
|
[
|
|
36
|
-
{address: "127.0.0.1", port: 50051},
|
|
37
|
-
{address: "127.0.0.2", port: 50052, healthy: false}
|
|
36
|
+
{addresses: [{address: "127.0.0.1", port: 50051}], healthy: true},
|
|
37
|
+
{addresses: [{address: "127.0.0.2", port: 50052}], healthy: false}
|
|
38
38
|
]
|
|
39
39
|
)
|
|
40
40
|
|
|
@@ -49,15 +49,15 @@ describe Async::GRPC::XDS::ControlPlane do
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
it "increments resource versions" do
|
|
52
|
-
control_plane.update_endpoints("myservice", [{address: "127.0.0.1", port: 50051}])
|
|
53
|
-
control_plane.update_endpoints("myservice", [{address: "127.0.0.2", port: 50052}])
|
|
52
|
+
control_plane.update_endpoints("myservice", [{addresses: [{address: "127.0.0.1", port: 50051}], healthy: true}])
|
|
53
|
+
control_plane.update_endpoints("myservice", [{addresses: [{address: "127.0.0.2", port: 50052}], healthy: true}])
|
|
54
54
|
|
|
55
55
|
expect(control_plane.version(Async::GRPC::XDS::ControlPlane::ENDPOINT_TYPE)).to be == "2"
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
it "serves resources over ADS" do
|
|
59
59
|
control_plane.update_cluster("myservice")
|
|
60
|
-
control_plane.update_endpoints("myservice", [{address: "127.0.0.1", port: 50051}])
|
|
60
|
+
control_plane.update_endpoints("myservice", [{addresses: [{address: "127.0.0.1", port: 50051}], healthy: true}])
|
|
61
61
|
|
|
62
62
|
port = available_port
|
|
63
63
|
endpoint = Async::HTTP::Endpoint.parse(
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
|
Binary file
|