service_mesh 0.4.4 → 0.6.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
- data/README.md +18 -2
- data/lib/service_mesh/rspec.rb +39 -17
- data/lib/service_mesh/types.rb +36 -2
- data/lib/service_mesh/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 643fe078e4376b1f4d19e3e45e0dbbe7e8b61e620e96591f67c7b0ed18865a50
|
|
4
|
+
data.tar.gz: 57c88b02dd056895234d1536b16f00973202cdec9b6600f7ab31621452e52d93
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 56f15b80a1db1ad69e0e95105e8ed221b873b4ae762ff85296e576ad7cb1625b22209d5bf13aad4e85b903a2144b08bd9f6bdfef0169b49860f6c3350ebcdc4d
|
|
7
|
+
data.tar.gz: 7be31a262f4c7cf55b5a2f7661c69baf7922e2261a5924a59537bbb3e0c0c3141eb0247977aca65941ed033a5eeb6dd66f5a900978e980d4a28d66cff6cc3aff
|
data/README.md
CHANGED
|
@@ -23,8 +23,12 @@ Requires Ruby 3.3 or newer. The gem has no runtime dependencies.
|
|
|
23
23
|
raises `KindMismatch`.
|
|
24
24
|
- `Message#payload` is always `Encoding::BINARY`.
|
|
25
25
|
- `Target#same_channel?` compares segments and kind and ignores metadata.
|
|
26
|
-
- The configuration keys the specification defines
|
|
27
|
-
`CONSUMER_GROUP_KEY`,
|
|
26
|
+
- The configuration keys the specification defines. `DEPLOYMENT_GROUP_KEY` is runtime
|
|
27
|
+
configuration. `CONSUMER_GROUP_KEY`, with the value `CONSUMER_GROUP_NONE`, is `Endpoint` and
|
|
28
|
+
`Subscriber` metadata.
|
|
29
|
+
- The reserved metadata prefix `RESERVED_PREFIX` (`Mesh-`).
|
|
30
|
+
- The metadata keys the specification defines: `HANDLER_ERROR_KEY`, `TIMEOUT_KEY`, `DEADLINE_KEY`,
|
|
31
|
+
`DELIVERY_ATTEMPT_KEY`, and `MESSAGE_ID_KEY`.
|
|
28
32
|
- The errors defined by the specification as `ServiceMesh::Error`: `ServiceMesh::KindMismatch`,
|
|
29
33
|
`ServiceMesh::InvalidTarget`, and `ServiceMesh::NoDeploymentGroup`.
|
|
30
34
|
- A conformance suite a transport runs against its own `Client` and
|
|
@@ -72,6 +76,18 @@ RSpec.describe MyTransport do
|
|
|
72
76
|
end
|
|
73
77
|
```
|
|
74
78
|
|
|
79
|
+
A transport that needs its own settings for the suite to pass supplies any of
|
|
80
|
+
four optional lets, each defaulting to an empty Hash:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
let(:endpoint_metadata) { {...} } # merged under the metadata of every Endpoint the suite builds
|
|
84
|
+
let(:subscriber_metadata) { {...} } # merged under the metadata of every Subscriber the suite builds
|
|
85
|
+
let(:request_options) { {...} } # the options of every request the suite makes
|
|
86
|
+
let(:publish_options) { {...} } # the options of every publish the suite makes
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Metadata an example sets itself wins over `endpoint_metadata` and `subscriber_metadata`.
|
|
90
|
+
|
|
75
91
|
The suite covers:
|
|
76
92
|
|
|
77
93
|
- kind checks
|
data/lib/service_mesh/rspec.rb
CHANGED
|
@@ -16,6 +16,12 @@ require_relative "../service_mesh"
|
|
|
16
16
|
# let(:client_config) { {...transport keys...} }
|
|
17
17
|
# let(:route_target) { ServiceMesh::Target.new(segments: %w[test echo], kind: :route) }
|
|
18
18
|
# let(:topic_target) { ServiceMesh::Target.new(segments: %w[test event], kind: :topic) }
|
|
19
|
+
#
|
|
20
|
+
# # Optional, each defaulting to an empty Hash.
|
|
21
|
+
# let(:endpoint_metadata) { {...} } # merged under each Endpoint's metadata
|
|
22
|
+
# let(:subscriber_metadata) { {...} } # merged under each Subscriber's metadata
|
|
23
|
+
# let(:request_options) { {...} } # options for every request
|
|
24
|
+
# let(:publish_options) { {...} } # options for every publish
|
|
19
25
|
# end
|
|
20
26
|
# end
|
|
21
27
|
#
|
|
@@ -25,13 +31,25 @@ require_relative "../service_mesh"
|
|
|
25
31
|
RSpec.shared_examples "a service mesh transport" do
|
|
26
32
|
let(:service_map) { ServiceMesh::ServiceMap.new }
|
|
27
33
|
let(:wait) { 3 }
|
|
34
|
+
let(:endpoint_metadata) { {} }
|
|
35
|
+
let(:subscriber_metadata) { {} }
|
|
36
|
+
let(:request_options) { {} }
|
|
37
|
+
let(:publish_options) { {} }
|
|
28
38
|
|
|
29
39
|
def endpoint(target, handler, metadata: {})
|
|
30
|
-
ServiceMesh::Endpoint.new(target: target, handler: handler, metadata: metadata)
|
|
40
|
+
ServiceMesh::Endpoint.new(target: target, handler: handler, metadata: endpoint_metadata.merge(metadata))
|
|
31
41
|
end
|
|
32
42
|
|
|
33
43
|
def subscriber(target, handler, metadata: {})
|
|
34
|
-
ServiceMesh::Subscriber.new(target: target, handler: handler, metadata: metadata)
|
|
44
|
+
ServiceMesh::Subscriber.new(target: target, handler: handler, metadata: subscriber_metadata.merge(metadata))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def request(msg, via: client)
|
|
48
|
+
via.request(msg, request_options)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def publish(msg, via: client)
|
|
52
|
+
via.publish(msg, publish_options)
|
|
35
53
|
end
|
|
36
54
|
|
|
37
55
|
def message(target, metadata: {}, payload: "")
|
|
@@ -72,11 +90,11 @@ RSpec.shared_examples "a service mesh transport" do
|
|
|
72
90
|
|
|
73
91
|
describe "kind checks" do
|
|
74
92
|
it "rejects a request to a topic" do
|
|
75
|
-
expect {
|
|
93
|
+
expect { request(message(topic_target)) }.to raise_error(ServiceMesh::KindMismatch)
|
|
76
94
|
end
|
|
77
95
|
|
|
78
96
|
it "rejects a publish to a route" do
|
|
79
|
-
expect {
|
|
97
|
+
expect { publish(message(route_target)) }.to raise_error(ServiceMesh::KindMismatch)
|
|
80
98
|
end
|
|
81
99
|
|
|
82
100
|
it "rejects an endpoint on a topic" do
|
|
@@ -107,7 +125,7 @@ RSpec.shared_examples "a service mesh transport" do
|
|
|
107
125
|
message(topic_target, metadata: {"Reply-Key" => "reply-value"}, payload: m.payload.upcase)
|
|
108
126
|
})])
|
|
109
127
|
|
|
110
|
-
reply =
|
|
128
|
+
reply = request(message(route_target, metadata: {"Request-Key" => "request-value"}, payload: "hello"))
|
|
111
129
|
|
|
112
130
|
expect(seen.target.same_channel?(route_target)).to be(true)
|
|
113
131
|
expect(seen.metadata["Request-Key"]).to eq("request-value")
|
|
@@ -119,7 +137,7 @@ RSpec.shared_examples "a service mesh transport" do
|
|
|
119
137
|
|
|
120
138
|
it "carries an empty payload with no metadata" do
|
|
121
139
|
serve(endpoints: [endpoint(route_target, ->(m) { m })])
|
|
122
|
-
reply =
|
|
140
|
+
reply = request(message(route_target))
|
|
123
141
|
expect([reply.payload, reply.metadata]).to eq(["", {}])
|
|
124
142
|
end
|
|
125
143
|
end
|
|
@@ -129,7 +147,7 @@ RSpec.shared_examples "a service mesh transport" do
|
|
|
129
147
|
got = Queue.new
|
|
130
148
|
serve(subscribers: [subscriber(topic_target, ->(m) { got << m })])
|
|
131
149
|
|
|
132
|
-
|
|
150
|
+
publish(message(topic_target, metadata: {"Event-Id" => "42"}, payload: "created"))
|
|
133
151
|
|
|
134
152
|
m = Timeout.timeout(wait) { got.pop }
|
|
135
153
|
expect(m.target.same_channel?(topic_target)).to be(true)
|
|
@@ -153,7 +171,7 @@ RSpec.shared_examples "a service mesh transport" do
|
|
|
153
171
|
serve(config_for(groups[i]), subscribers: [subscriber(topic_target, ->(_) { count << true }, metadata: metadata[i])])
|
|
154
172
|
end
|
|
155
173
|
|
|
156
|
-
|
|
174
|
+
publish(message(topic_target))
|
|
157
175
|
|
|
158
176
|
wait_until { count.size >= want }
|
|
159
177
|
sleep 0.1 # let an unwanted extra delivery show up
|
|
@@ -161,14 +179,18 @@ RSpec.shared_examples "a service mesh transport" do
|
|
|
161
179
|
end
|
|
162
180
|
end
|
|
163
181
|
|
|
164
|
-
it "
|
|
182
|
+
it "ignores a group set on the target, which carries none" do
|
|
165
183
|
count = Queue.new
|
|
166
184
|
%w[a b].each do |group|
|
|
167
185
|
t = ServiceMesh::Target.new(segments: topic_target.segments, kind: :topic, metadata: {ServiceMesh::CONSUMER_GROUP_KEY => group})
|
|
168
186
|
serve(config_for("same"), subscribers: [subscriber(t, ->(_) { count << true })])
|
|
169
187
|
end
|
|
170
|
-
|
|
171
|
-
|
|
188
|
+
|
|
189
|
+
publish(message(topic_target))
|
|
190
|
+
|
|
191
|
+
wait_until { count.size >= 1 }
|
|
192
|
+
sleep 0.1 # let an unwanted extra delivery show up
|
|
193
|
+
expect(count.size).to eq(1)
|
|
172
194
|
end
|
|
173
195
|
end
|
|
174
196
|
|
|
@@ -177,9 +199,9 @@ RSpec.shared_examples "a service mesh transport" do
|
|
|
177
199
|
serve(endpoints: [endpoint(route_target, ->(m) { m })])
|
|
178
200
|
own = new_client.call(client_config, service_map)
|
|
179
201
|
|
|
180
|
-
expect(
|
|
202
|
+
expect(request(message(route_target), via: own).payload).to eq("")
|
|
181
203
|
own.close
|
|
182
|
-
expect {
|
|
204
|
+
expect { request(message(route_target), via: own) }.to raise_error(StandardError)
|
|
183
205
|
end
|
|
184
206
|
end
|
|
185
207
|
|
|
@@ -193,9 +215,9 @@ RSpec.shared_examples "a service mesh transport" do
|
|
|
193
215
|
expect(rt.client).to equal(given)
|
|
194
216
|
expect(rt.service_map).to equal(service_map)
|
|
195
217
|
rt.start
|
|
196
|
-
expect(
|
|
218
|
+
expect(request(req, via: given).payload).to eq("")
|
|
197
219
|
rt.stop(1)
|
|
198
|
-
expect {
|
|
220
|
+
expect { request(req, via: given) }.to raise_error(StandardError)
|
|
199
221
|
end
|
|
200
222
|
end
|
|
201
223
|
|
|
@@ -230,7 +252,7 @@ RSpec.shared_examples "a service mesh transport" do
|
|
|
230
252
|
message(route_target, payload: "done")
|
|
231
253
|
})])
|
|
232
254
|
|
|
233
|
-
reply = Thread.new {
|
|
255
|
+
reply = Thread.new { request(message(route_target)) }
|
|
234
256
|
@conformance_threads << reply
|
|
235
257
|
Timeout.timeout(wait) { entered.pop }
|
|
236
258
|
|
|
@@ -252,7 +274,7 @@ RSpec.shared_examples "a service mesh transport" do
|
|
|
252
274
|
message(route_target)
|
|
253
275
|
})])
|
|
254
276
|
|
|
255
|
-
@conformance_threads << Thread.new {
|
|
277
|
+
@conformance_threads << Thread.new { request(message(route_target)) rescue nil } # rubocop:disable Style/RescueModifier
|
|
256
278
|
Timeout.timeout(wait) { entered.pop }
|
|
257
279
|
|
|
258
280
|
expect(rt.stop(0.1)).to be(false)
|
data/lib/service_mesh/types.rb
CHANGED
|
@@ -4,11 +4,43 @@ module ServiceMesh
|
|
|
4
4
|
KINDS = %i[route topic].freeze
|
|
5
5
|
|
|
6
6
|
# Configuration keys the specification defines. Everything else belongs to
|
|
7
|
-
# a transport.
|
|
7
|
+
# a transport. DEPLOYMENT_GROUP_KEY is runtime configuration, and nothing
|
|
8
|
+
# else carries it. CONSUMER_GROUP_KEY is Endpoint and Subscriber metadata;
|
|
9
|
+
# when it is unset or empty, the runtime's deployment group applies, and
|
|
10
|
+
# CONSUMER_GROUP_NONE means no group. A Target carries neither.
|
|
8
11
|
DEPLOYMENT_GROUP_KEY = "deployment_group"
|
|
9
12
|
CONSUMER_GROUP_KEY = "consumer_group"
|
|
10
13
|
CONSUMER_GROUP_NONE = "none"
|
|
11
14
|
|
|
15
|
+
# Metadata keys that start with RESERVED_PREFIX belong to transports and
|
|
16
|
+
# protocol layers, and application code does not set them. A key a
|
|
17
|
+
# transport defines for itself continues with the transport's name, such as
|
|
18
|
+
# "Mesh-Nats-". A transport may use any of the keys below, and one that does
|
|
19
|
+
# follows the meaning and format given here.
|
|
20
|
+
RESERVED_PREFIX = "Mesh-"
|
|
21
|
+
|
|
22
|
+
# Set by the serving transport on a reply when the Endpoint's handler
|
|
23
|
+
# failed. The value is the failure's text.
|
|
24
|
+
HANDLER_ERROR_KEY = "Mesh-Handler-Error"
|
|
25
|
+
|
|
26
|
+
# Set by the requesting transport on a request. The value is how long the
|
|
27
|
+
# caller waits for the reply, as a whole number of milliseconds.
|
|
28
|
+
TIMEOUT_KEY = "Mesh-Timeout"
|
|
29
|
+
|
|
30
|
+
# Set by the receiving transport on a message it hands to a handler. The
|
|
31
|
+
# value is when the handler's result stops mattering, in RFC 3339 with
|
|
32
|
+
# fractional seconds.
|
|
33
|
+
DEADLINE_KEY = "Mesh-Deadline"
|
|
34
|
+
|
|
35
|
+
# Set by the receiving transport on a message it hands to a handler. The
|
|
36
|
+
# value is which delivery of the message this is, starting at 1. It is
|
|
37
|
+
# absent when the transport does not track deliveries.
|
|
38
|
+
DELIVERY_ATTEMPT_KEY = "Mesh-Delivery-Attempt"
|
|
39
|
+
|
|
40
|
+
# Set by the publisher or caller. The value is an ID that stays the same
|
|
41
|
+
# when the same message is sent again, so handlers can recognize repeats.
|
|
42
|
+
MESSAGE_ID_KEY = "Mesh-Message-Id"
|
|
43
|
+
|
|
12
44
|
# Identifies a receiving channel on the mesh. A transport assembles the
|
|
13
45
|
# segments into its own address; that string never leaves the transport.
|
|
14
46
|
Target = Data.define(:segments, :kind, :metadata) do
|
|
@@ -31,7 +63,9 @@ module ServiceMesh
|
|
|
31
63
|
end
|
|
32
64
|
end
|
|
33
65
|
|
|
34
|
-
# What travels between services. Payload is always BINARY encoded.
|
|
66
|
+
# What travels between services. Payload is always BINARY encoded. Metadata
|
|
67
|
+
# keys that start with RESERVED_PREFIX belong to transports and protocol
|
|
68
|
+
# layers.
|
|
35
69
|
Message = Data.define(:target, :metadata, :payload) do
|
|
36
70
|
def initialize(target:, metadata: {}, payload: "")
|
|
37
71
|
super(target: target, metadata: metadata.to_h.freeze, payload: payload.to_s.b.freeze)
|
data/lib/service_mesh/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: service_mesh
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bryant Morrill
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-
|
|
12
|
+
date: 2026-10-08 00:00:00.000000000 Z
|
|
13
13
|
dependencies: []
|
|
14
14
|
description:
|
|
15
15
|
email:
|