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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fba6859fd43ebb2de75d228d4511125d1c7b36f08321fdda99eabf573fea900a
4
- data.tar.gz: 2bca6eb3ec749a434703f0296f6a12c14e6d75f5518930618355e3abd8b37d83
3
+ metadata.gz: 668ae495babdb4aa2afd890a356bf9f7d80f4e1649213af63e5e0804432983ba
4
+ data.tar.gz: 3634be8ae4e830a673c5706de0c7ed2f08462de1edcee0c7ffc401f9b1d3b2ab
5
5
  SHA512:
6
- metadata.gz: 78f056024e2e5c0cfbf1e1ca67acf9edd9b6ca357659911cbd733eeb2fde2ff0d73c169724a2caebd1ea1860bfaa09ce07784d950a5e9908c9c1a07c2021e81d
7
- data.tar.gz: 4e9a3490cfb4e8918994301ef300a2780929dffd31dec5278bf690f976e37681e9476f58899fc668f9799d3d9398fbc740654d42599a7ee8b01c265a47a70e7b
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
- def self.cluster(name, service_name: name, load_balancer_policy: :round_robin, connect_timeout: 5)
35
- Envoy::Config::Cluster::V3::Cluster.new(
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
- http2_protocol_options: Envoy::Config::Core::V3::Http2ProtocolOptions.new
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
- endpoint = normalize_endpoint(endpoint)
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: Envoy::Config::Core::V3::Address.new(
67
- socket_address: Envoy::Config::Core::V3::SocketAddress.new(
68
- protocol: Envoy::Config::Core::V3::SocketAddress::Protocol::TCP,
69
- address: endpoint[: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(endpoint.fetch(:healthy, true))
106
+ health_status: health_status_value(healthy)
76
107
  )
77
108
  end
78
-
79
- def self.normalize_endpoint(endpoint)
80
- case endpoint
81
- when Hash
82
- {
83
- address: endpoint.fetch(:address){endpoint.fetch("address")},
84
- port: endpoint.fetch(:port){endpoint.fetch("port")}.to_i,
85
- hostname: endpoint[:hostname] || endpoint["hostname"],
86
- healthy: endpoint.key?(:healthy) ? endpoint[:healthy] : endpoint.fetch("healthy", true)
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
- if endpoint.respond_to?(:address) && endpoint.respond_to?(:port)
90
- {
91
- address: endpoint.address,
92
- port: endpoint.port.to_i,
93
- hostname: endpoint.respond_to?(:hostname) ? endpoint.hostname : nil,
94
- healthy: endpoint.respond_to?(:healthy?) ? endpoint.healthy? : true
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
-
@@ -9,6 +9,7 @@ module Async
9
9
  # Caches discovered xDS resources
10
10
  # Thread-safe cache for clusters and endpoints
11
11
  class ResourceCache
12
+ # Initialize an empty resource cache.
12
13
  def initialize
13
14
  @clusters = {}
14
15
  @endpoints = {}
@@ -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
@@ -3,10 +3,13 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2026, by Samuel Williams.
5
5
 
6
+ # @namespace
6
7
  module Async
8
+ # @namespace
7
9
  module GRPC
10
+ # @namespace
8
11
  module XDS
9
- VERSION = "0.0.1"
12
+ VERSION = "0.1.0"
10
13
  end
11
14
  end
12
15
  end
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-compose -f xds/docker-compose.yaml up --exit-code-from tests
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
- - Initial extraction from `async-grpc`.
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
- ### Start the environment
14
+ From this directory:
15
15
 
16
16
  ```bash
17
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
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
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-grpc-xds
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
metadata.gz.sig CHANGED
Binary file