nonnative 2.23.0 → 3.0.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/.circleci/config.yml +4 -4
- data/Gemfile.lock +11 -11
- data/README.md +138 -57
- data/lib/nonnative/configuration.rb +38 -31
- data/lib/nonnative/configuration_process.rb +1 -1
- data/lib/nonnative/configuration_runner.rb +25 -4
- data/lib/nonnative/configuration_server.rb +1 -1
- data/lib/nonnative/fault_injection_proxy.rb +3 -3
- data/lib/nonnative/grpc_server.rb +1 -1
- data/lib/nonnative/http_proxy_server.rb +5 -4
- data/lib/nonnative/http_server.rb +3 -3
- data/lib/nonnative/no_proxy.rb +2 -2
- data/lib/nonnative/pool.rb +13 -11
- data/lib/nonnative/port.rb +12 -10
- data/lib/nonnative/ports.rb +34 -0
- data/lib/nonnative/socket_pair.rb +7 -7
- data/lib/nonnative/version.rb +1 -1
- data/lib/nonnative.rb +3 -2
- data/nonnative.gemspec +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 786062cd10bbaba4298dd41abc683bc7a5fd769ea7c41a05e39a9f41bc2c5d96
|
|
4
|
+
data.tar.gz: 1d0e8303858861f849aacbcb54ba2bac1f5a8336fdb74c44d09cb48881658c9f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 729e1141493b2b21c5c9014216a73aade7f7ec1ab03686ab581cbcb1b1c28d2d469a4fd5e416bcd391c2f8056725433a0befe40c283357166b0743f3f4f2d834
|
|
7
|
+
data.tar.gz: e17dfe6bd69bbf4d0faf76d0b37502a32abf1ca0943d24f261c4c32301be86fff18e302f65dc9af32ac499ca73d5fe4a08ede17422d2de715a15b4c986ea64c7
|
data/.circleci/config.yml
CHANGED
|
@@ -3,7 +3,7 @@ version: 2.1
|
|
|
3
3
|
jobs:
|
|
4
4
|
build:
|
|
5
5
|
docker:
|
|
6
|
-
- image: alexfalkowski/ruby:3.
|
|
6
|
+
- image: alexfalkowski/ruby:3.14
|
|
7
7
|
working_directory: ~/nonnative
|
|
8
8
|
steps:
|
|
9
9
|
- checkout:
|
|
@@ -35,7 +35,7 @@ jobs:
|
|
|
35
35
|
resource_class: arm.large
|
|
36
36
|
sync:
|
|
37
37
|
docker:
|
|
38
|
-
- image: alexfalkowski/release:8.
|
|
38
|
+
- image: alexfalkowski/release:8.14
|
|
39
39
|
working_directory: ~/nonnative
|
|
40
40
|
steps:
|
|
41
41
|
- checkout:
|
|
@@ -46,7 +46,7 @@ jobs:
|
|
|
46
46
|
resource_class: arm.large
|
|
47
47
|
version:
|
|
48
48
|
docker:
|
|
49
|
-
- image: alexfalkowski/release:8.
|
|
49
|
+
- image: alexfalkowski/release:8.14
|
|
50
50
|
working_directory: ~/nonnative
|
|
51
51
|
steps:
|
|
52
52
|
- checkout:
|
|
@@ -58,7 +58,7 @@ jobs:
|
|
|
58
58
|
resource_class: arm.large
|
|
59
59
|
wait-all:
|
|
60
60
|
docker:
|
|
61
|
-
- image: alexfalkowski/ruby:3.
|
|
61
|
+
- image: alexfalkowski/ruby:3.14
|
|
62
62
|
steps:
|
|
63
63
|
- run: echo "all applicable jobs finished"
|
|
64
64
|
resource_class: arm.large
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
nonnative (
|
|
4
|
+
nonnative (3.0.0)
|
|
5
5
|
concurrent-ruby (>= 1, < 2)
|
|
6
6
|
config (>= 5, < 6)
|
|
7
7
|
cucumber (>= 7, < 12)
|
|
@@ -30,7 +30,7 @@ GEM
|
|
|
30
30
|
config (5.6.1)
|
|
31
31
|
deep_merge (~> 1.2, >= 1.2.1)
|
|
32
32
|
ostruct
|
|
33
|
-
cucumber (11.
|
|
33
|
+
cucumber (11.1.0)
|
|
34
34
|
base64 (~> 0.2)
|
|
35
35
|
builder (~> 3.2)
|
|
36
36
|
cucumber-ci-environment (> 9, < 12)
|
|
@@ -64,24 +64,24 @@ GEM
|
|
|
64
64
|
get_process_mem (1.0.0)
|
|
65
65
|
bigdecimal (>= 2.0)
|
|
66
66
|
ffi (~> 1.0)
|
|
67
|
-
google-protobuf (4.
|
|
67
|
+
google-protobuf (4.35.0-x86_64-darwin)
|
|
68
68
|
bigdecimal
|
|
69
69
|
rake (~> 13.3)
|
|
70
|
-
google-protobuf (4.
|
|
70
|
+
google-protobuf (4.35.0-x86_64-linux-gnu)
|
|
71
71
|
bigdecimal
|
|
72
72
|
rake (~> 13.3)
|
|
73
|
-
googleapis-common-protos-types (1.
|
|
73
|
+
googleapis-common-protos-types (1.23.0)
|
|
74
74
|
google-protobuf (~> 4.26)
|
|
75
|
-
grpc (1.
|
|
75
|
+
grpc (1.81.0-x86_64-darwin)
|
|
76
76
|
google-protobuf (>= 3.25, < 5.0)
|
|
77
77
|
googleapis-common-protos-types (~> 1.0)
|
|
78
|
-
grpc (1.
|
|
78
|
+
grpc (1.81.0-x86_64-linux-gnu)
|
|
79
79
|
google-protobuf (>= 3.25, < 5.0)
|
|
80
80
|
googleapis-common-protos-types (~> 1.0)
|
|
81
81
|
http-accept (1.7.0)
|
|
82
82
|
http-cookie (1.1.6)
|
|
83
83
|
domain_name (~> 0.5)
|
|
84
|
-
json (2.19.
|
|
84
|
+
json (2.19.8)
|
|
85
85
|
language_server-protocol (3.17.0.5)
|
|
86
86
|
lint_roller (1.1.0)
|
|
87
87
|
logger (1.7.0)
|
|
@@ -101,7 +101,7 @@ GEM
|
|
|
101
101
|
ast (~> 2.4.1)
|
|
102
102
|
racc
|
|
103
103
|
prism (1.9.0)
|
|
104
|
-
puma (7.2.
|
|
104
|
+
puma (7.2.1)
|
|
105
105
|
nio4r (~> 2.0)
|
|
106
106
|
racc (1.8.1)
|
|
107
107
|
rack (3.2.6)
|
|
@@ -124,7 +124,7 @@ GEM
|
|
|
124
124
|
http-cookie (>= 1.0.2, < 2.0)
|
|
125
125
|
mime-types (>= 1.16, < 4.0)
|
|
126
126
|
netrc (~> 0.8)
|
|
127
|
-
retriable (3.
|
|
127
|
+
retriable (3.8.0)
|
|
128
128
|
rexml (3.4.4)
|
|
129
129
|
rspec (3.13.2)
|
|
130
130
|
rspec-core (~> 3.13.0)
|
|
@@ -146,7 +146,7 @@ GEM
|
|
|
146
146
|
rspec-support (3.13.7)
|
|
147
147
|
rspec-wait (1.0.2)
|
|
148
148
|
rspec (>= 3.4)
|
|
149
|
-
rubocop (1.
|
|
149
|
+
rubocop (1.87.0)
|
|
150
150
|
json (~> 2.3)
|
|
151
151
|
language_server-protocol (~> 3.17.0.2)
|
|
152
152
|
lint_roller (~> 1.1.0)
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://badge.fury.io/rb/nonnative)
|
|
4
4
|
[](https://masterminds.github.io/stability/active.html)
|
|
5
5
|
|
|
6
|
-
# Nonnative
|
|
6
|
+
# 🧪 Nonnative
|
|
7
7
|
|
|
8
8
|
Nonnative is a Ruby-first harness for end-to-end testing of systems implemented in other languages.
|
|
9
9
|
|
|
@@ -15,7 +15,10 @@ It helps you:
|
|
|
15
15
|
|
|
16
16
|
Once started, you can test however you like (TCP, HTTP, gRPC, etc).
|
|
17
17
|
|
|
18
|
-
## Installation
|
|
18
|
+
## 📦 Installation
|
|
19
|
+
|
|
20
|
+
> [!IMPORTANT]
|
|
21
|
+
> Nonnative currently supports Ruby `>= 4.0.0` and `< 5.0.0`.
|
|
19
22
|
|
|
20
23
|
Add this line to your application's Gemfile:
|
|
21
24
|
|
|
@@ -35,12 +38,15 @@ Or install it yourself as:
|
|
|
35
38
|
gem install nonnative
|
|
36
39
|
```
|
|
37
40
|
|
|
38
|
-
## Usage
|
|
41
|
+
## 🚀 Usage
|
|
39
42
|
|
|
40
43
|
Nonnative is configured via `Nonnative.configure` (programmatic) or `config.load_file(...)` (YAML).
|
|
41
44
|
YAML configuration is loaded as data only: ERB is not evaluated and arbitrary Ruby objects are not
|
|
42
45
|
deserialized.
|
|
43
46
|
|
|
47
|
+
> [!CAUTION]
|
|
48
|
+
> Treat YAML configuration as plain data. ERB is not evaluated and arbitrary Ruby object tags are rejected.
|
|
49
|
+
|
|
44
50
|
High-level configuration fields:
|
|
45
51
|
- `version`: configuration version (example: `"1.0"`).
|
|
46
52
|
- `name`: logical system name (used by `Nonnative.observability` for `/<name>/healthz`, etc).
|
|
@@ -52,7 +58,7 @@ High-level configuration fields:
|
|
|
52
58
|
|
|
53
59
|
Common runner fields:
|
|
54
60
|
- `name`: runner name used for lookup.
|
|
55
|
-
- `host`/`
|
|
61
|
+
- `host`/`ports`: client-facing address. `host` defaults to `127.0.0.1`. For processes and servers, this address is also used for readiness/shutdown port checks. When a `fault_injection` proxy is enabled, clients should hit the first configured port.
|
|
56
62
|
|
|
57
63
|
Process/server fields:
|
|
58
64
|
- `timeout`: max time (seconds) for readiness/shutdown port checks.
|
|
@@ -61,9 +67,55 @@ Process/server fields:
|
|
|
61
67
|
|
|
62
68
|
For `fault_injection`, the nested `proxy.host`/`proxy.port` describe the upstream target behind the proxy. Nested `proxy.host` also defaults to `127.0.0.1`. In-process server implementations typically bind there via `proxy.host` / `proxy.port`.
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
> [!IMPORTANT]
|
|
71
|
+
> When a proxy is enabled, tests and clients connect to the runner `host` and first configured `ports` entry; the nested `proxy.host`/`proxy.port` is the upstream target behind the proxy.
|
|
72
|
+
|
|
73
|
+
Nonnative readiness and shutdown checks are TCP-only. Configure ports that are dedicated to the test run; if another process is already listening on the same `host`/`ports` endpoint, results are undefined.
|
|
74
|
+
|
|
75
|
+
> [!WARNING]
|
|
76
|
+
> Readiness and shutdown checks only prove that a TCP port opened or closed. They do not verify HTTP status, gRPC health, schema readiness, migrations, or application-specific health.
|
|
77
|
+
|
|
78
|
+
Start and stop Nonnative around the test scope that should own the configured runners:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
require 'nonnative'
|
|
82
|
+
|
|
83
|
+
Nonnative.configure do |config|
|
|
84
|
+
config.load_file('configuration.yml')
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
Nonnative.start
|
|
88
|
+
# run tests...
|
|
89
|
+
Nonnative.stop
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`Nonnative.start` starts services first, then servers and processes. `Nonnative.stop` stops processes and servers first, then services. If startup fails, Nonnative rolls back runners that already started and raises `Nonnative::StartError`; shutdown failures raise `Nonnative::StopError`.
|
|
93
|
+
|
|
94
|
+
> [!NOTE]
|
|
95
|
+
> `Nonnative.clear` clears memoized configuration, logger, observability client, and pool. Use it before reconfiguring Nonnative in the same Ruby process.
|
|
65
96
|
|
|
66
|
-
###
|
|
97
|
+
### 📈 Observability
|
|
98
|
+
|
|
99
|
+
`Nonnative.observability` is an HTTP client for common service endpoints under the configured `name` and `url`:
|
|
100
|
+
|
|
101
|
+
- `health(...)`: calls `/<name>/healthz`.
|
|
102
|
+
- `liveness(...)`: calls `/<name>/livez`.
|
|
103
|
+
- `readiness(...)`: calls `/<name>/readyz`.
|
|
104
|
+
- `metrics(...)`: calls `/<name>/metrics`.
|
|
105
|
+
|
|
106
|
+
Each method accepts RestClient options such as `headers`, `open_timeout`, and `read_timeout`.
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
response = Nonnative.observability.health(
|
|
110
|
+
headers: { content_type: :json, accept: :json },
|
|
111
|
+
open_timeout: 2,
|
|
112
|
+
read_timeout: 2
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
expect(response.code).to eq(200)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 🔁 Lifecycle strategies (Cucumber integration)
|
|
67
119
|
|
|
68
120
|
Nonnative ships Cucumber hooks (when loaded) that support these tags/strategies:
|
|
69
121
|
- `@startup`: start before scenario; stop after scenario
|
|
@@ -84,7 +136,7 @@ The repo’s own Cucumber suite also uses taxonomy tags to classify coverage:
|
|
|
84
136
|
|
|
85
137
|
Requiring `nonnative` is enough; the Cucumber hooks and step definitions are installed lazily once Cucumber’s Ruby DSL is ready.
|
|
86
138
|
|
|
87
|
-
If you want
|
|
139
|
+
If you want "start once per test run", require:
|
|
88
140
|
|
|
89
141
|
```ruby
|
|
90
142
|
require 'nonnative/startup'
|
|
@@ -92,11 +144,13 @@ require 'nonnative/startup'
|
|
|
92
144
|
|
|
93
145
|
This calls `Nonnative.start` immediately and registers an `at_exit` stop.
|
|
94
146
|
|
|
95
|
-
### Processes
|
|
147
|
+
### ⚙️ Processes
|
|
96
148
|
|
|
97
149
|
A process is some sort of command that you would run locally.
|
|
98
|
-
|
|
99
|
-
|
|
150
|
+
Programmatic `p.command` values must be callables that return a shell string or an argv array. YAML `command` values can be scalars or lists and are wrapped internally. String commands preserve legacy shell semantics, while argv arrays avoid shell interpretation and are preferred for new configuration.
|
|
151
|
+
|
|
152
|
+
> [!TIP]
|
|
153
|
+
> Prefer argv arrays for new process commands. Use shell strings only when you intentionally need shell parsing, expansion, or redirection.
|
|
100
154
|
|
|
101
155
|
Set it up programmatically:
|
|
102
156
|
|
|
@@ -114,7 +168,7 @@ Nonnative.configure do |config|
|
|
|
114
168
|
p.command = -> { ['features/support/bin/start', '12_321'] }
|
|
115
169
|
p.timeout = 5
|
|
116
170
|
p.wait = 0.1
|
|
117
|
-
p.
|
|
171
|
+
p.ports = [12_321]
|
|
118
172
|
p.log = '12_321.log'
|
|
119
173
|
p.signal = 'INT' # Possible values are described in Signal.list.keys.
|
|
120
174
|
p.environment = { # Pass environment variables to process.
|
|
@@ -127,7 +181,7 @@ Nonnative.configure do |config|
|
|
|
127
181
|
p.command = -> { ['features/support/bin/start', '12_322'] }
|
|
128
182
|
p.timeout = 0.5
|
|
129
183
|
p.wait = 0.1
|
|
130
|
-
p.
|
|
184
|
+
p.ports = [12_322]
|
|
131
185
|
p.log = '12_322.log'
|
|
132
186
|
end
|
|
133
187
|
end
|
|
@@ -148,7 +202,8 @@ processes:
|
|
|
148
202
|
- "12_321"
|
|
149
203
|
timeout: 5
|
|
150
204
|
wait: 1
|
|
151
|
-
|
|
205
|
+
ports:
|
|
206
|
+
- 12321
|
|
152
207
|
log: 12_321.log
|
|
153
208
|
signal: INT # Possible values are described in Signal.list.keys.
|
|
154
209
|
environment: # Pass environment variables to process.
|
|
@@ -160,7 +215,8 @@ processes:
|
|
|
160
215
|
- "12_322"
|
|
161
216
|
timeout: 5
|
|
162
217
|
wait: 1
|
|
163
|
-
|
|
218
|
+
ports:
|
|
219
|
+
- 12322
|
|
164
220
|
log: 12_322.log
|
|
165
221
|
```
|
|
166
222
|
|
|
@@ -180,9 +236,9 @@ With cucumber you can also verify how much memory is used by the process:
|
|
|
180
236
|
Then the process 'start_1' should consume less than '25mb' of memory
|
|
181
237
|
```
|
|
182
238
|
|
|
183
|
-
### Servers
|
|
239
|
+
### 🖥️ Servers
|
|
184
240
|
|
|
185
|
-
A server is a
|
|
241
|
+
A server is an in-process Ruby fake or helper server that Nonnative starts in a thread. Use servers for dependencies that are easiest to model inside the test process, such as small TCP, HTTP, or gRPC fakes.
|
|
186
242
|
|
|
187
243
|
Define your server:
|
|
188
244
|
|
|
@@ -231,7 +287,7 @@ Nonnative.configure do |config|
|
|
|
231
287
|
s.name = 'server_1'
|
|
232
288
|
s.klass = Nonnative::TCPServer
|
|
233
289
|
s.timeout = 1
|
|
234
|
-
s.
|
|
290
|
+
s.ports = [12_323]
|
|
235
291
|
s.log = 'server_1.log'
|
|
236
292
|
end
|
|
237
293
|
|
|
@@ -239,7 +295,7 @@ Nonnative.configure do |config|
|
|
|
239
295
|
s.name = 'server_2'
|
|
240
296
|
s.klass = Nonnative::TCPServer
|
|
241
297
|
s.timeout = 1
|
|
242
|
-
s.
|
|
298
|
+
s.ports = [12_324]
|
|
243
299
|
s.log = 'server_2.log'
|
|
244
300
|
end
|
|
245
301
|
end
|
|
@@ -257,13 +313,15 @@ servers:
|
|
|
257
313
|
name: server_1
|
|
258
314
|
class: Nonnative::TCPServer
|
|
259
315
|
timeout: 1
|
|
260
|
-
|
|
316
|
+
ports:
|
|
317
|
+
- 12323
|
|
261
318
|
log: server_1.log
|
|
262
319
|
-
|
|
263
320
|
name: server_2
|
|
264
321
|
class: Nonnative::TCPServer
|
|
265
322
|
timeout: 1
|
|
266
|
-
|
|
323
|
+
ports:
|
|
324
|
+
- 12324
|
|
267
325
|
log: server_2.log
|
|
268
326
|
```
|
|
269
327
|
|
|
@@ -277,7 +335,7 @@ Nonnative.configure do |config|
|
|
|
277
335
|
end
|
|
278
336
|
```
|
|
279
337
|
|
|
280
|
-
#### HTTP
|
|
338
|
+
#### 🌐 HTTP
|
|
281
339
|
|
|
282
340
|
Define your server:
|
|
283
341
|
|
|
@@ -314,7 +372,7 @@ Nonnative.configure do |config|
|
|
|
314
372
|
s.name = 'http_server_1'
|
|
315
373
|
s.klass = Nonnative::Features::HTTPServer
|
|
316
374
|
s.timeout = 1
|
|
317
|
-
s.
|
|
375
|
+
s.ports = [4567]
|
|
318
376
|
s.log = 'http_server_1.log'
|
|
319
377
|
end
|
|
320
378
|
end
|
|
@@ -332,7 +390,8 @@ servers:
|
|
|
332
390
|
name: http_server_1
|
|
333
391
|
class: Nonnative::Features::HTTPServer
|
|
334
392
|
timeout: 1
|
|
335
|
-
|
|
393
|
+
ports:
|
|
394
|
+
- 4567
|
|
336
395
|
log: http_server_1.log
|
|
337
396
|
```
|
|
338
397
|
|
|
@@ -346,9 +405,9 @@ Nonnative.configure do |config|
|
|
|
346
405
|
end
|
|
347
406
|
```
|
|
348
407
|
|
|
349
|
-
##### Proxy
|
|
408
|
+
##### 🔀 Proxy
|
|
350
409
|
|
|
351
|
-
The system allows you to define
|
|
410
|
+
The system allows you to define an HTTP proxy for external systems, e.g. `api.github.com`.
|
|
352
411
|
|
|
353
412
|
Define your server:
|
|
354
413
|
|
|
@@ -379,7 +438,7 @@ Nonnative.configure do |config|
|
|
|
379
438
|
s.name = 'http_server_proxy'
|
|
380
439
|
s.klass = Nonnative::Features::HTTPProxyServer
|
|
381
440
|
s.timeout = 1
|
|
382
|
-
s.
|
|
441
|
+
s.ports = [4567]
|
|
383
442
|
s.log = 'http_server_proxy.log'
|
|
384
443
|
end
|
|
385
444
|
end
|
|
@@ -397,7 +456,8 @@ servers:
|
|
|
397
456
|
name: http_server_proxy
|
|
398
457
|
class: Nonnative::Features::HTTPProxyServer
|
|
399
458
|
timeout: 1
|
|
400
|
-
|
|
459
|
+
ports:
|
|
460
|
+
- 4567
|
|
401
461
|
log: http_server_proxy.log
|
|
402
462
|
```
|
|
403
463
|
|
|
@@ -411,7 +471,7 @@ Nonnative.configure do |config|
|
|
|
411
471
|
end
|
|
412
472
|
```
|
|
413
473
|
|
|
414
|
-
#### gRPC
|
|
474
|
+
#### 📡 gRPC
|
|
415
475
|
|
|
416
476
|
Define your server:
|
|
417
477
|
|
|
@@ -448,7 +508,7 @@ Nonnative.configure do |config|
|
|
|
448
508
|
s.name = 'grpc_server_1'
|
|
449
509
|
s.klass = Nonnative::Features::GRPCServer
|
|
450
510
|
s.timeout = 1
|
|
451
|
-
s.
|
|
511
|
+
s.ports = [9002]
|
|
452
512
|
s.log = 'grpc_server_1.log'
|
|
453
513
|
end
|
|
454
514
|
end
|
|
@@ -466,7 +526,8 @@ servers:
|
|
|
466
526
|
name: grpc_server_1
|
|
467
527
|
class: Nonnative::Features::GRPCServer
|
|
468
528
|
timeout: 1
|
|
469
|
-
|
|
529
|
+
ports:
|
|
530
|
+
- 9002
|
|
470
531
|
log: grpc_server_1.log
|
|
471
532
|
```
|
|
472
533
|
|
|
@@ -480,10 +541,12 @@ Nonnative.configure do |config|
|
|
|
480
541
|
end
|
|
481
542
|
```
|
|
482
543
|
|
|
483
|
-
### Services
|
|
544
|
+
### 🧩 Services
|
|
484
545
|
|
|
485
546
|
A service is an external dependency to your system that you **do not** want Nonnative to start (no OS process, no Ruby thread). Services are primarily useful when paired with proxies, because they let you inject failures into dependencies that are managed elsewhere (e.g. a DB running in Docker).
|
|
486
547
|
|
|
548
|
+
Services do not get process lifecycle management or TCP readiness/shutdown checks from Nonnative. They only provide a named runner and optional proxy lifecycle for a dependency that another tool already manages.
|
|
549
|
+
|
|
487
550
|
Set it up programmatically:
|
|
488
551
|
|
|
489
552
|
```ruby
|
|
@@ -498,13 +561,13 @@ Nonnative.configure do |config|
|
|
|
498
561
|
config.service do |s|
|
|
499
562
|
s.name = 'postgres'
|
|
500
563
|
s.host = '127.0.0.1'
|
|
501
|
-
s.
|
|
564
|
+
s.ports = [5432]
|
|
502
565
|
end
|
|
503
566
|
|
|
504
567
|
config.service do |s|
|
|
505
568
|
s.name = 'redis'
|
|
506
569
|
s.host = '127.0.0.1'
|
|
507
|
-
s.
|
|
570
|
+
s.ports = [6379]
|
|
508
571
|
end
|
|
509
572
|
end
|
|
510
573
|
```
|
|
@@ -520,11 +583,13 @@ services:
|
|
|
520
583
|
-
|
|
521
584
|
name: postgres
|
|
522
585
|
host: 127.0.0.1
|
|
523
|
-
|
|
586
|
+
ports:
|
|
587
|
+
- 5432
|
|
524
588
|
-
|
|
525
589
|
name: redis
|
|
526
590
|
host: 127.0.0.1
|
|
527
|
-
|
|
591
|
+
ports:
|
|
592
|
+
- 6379
|
|
528
593
|
```
|
|
529
594
|
|
|
530
595
|
Then load the file with:
|
|
@@ -537,16 +602,25 @@ Nonnative.configure do |config|
|
|
|
537
602
|
end
|
|
538
603
|
```
|
|
539
604
|
|
|
540
|
-
#### Proxies
|
|
605
|
+
#### 🕸️ Proxies
|
|
541
606
|
|
|
542
|
-
|
|
607
|
+
These proxies can simulate different situations. Available proxy kinds are:
|
|
543
608
|
|
|
544
609
|
- `none` (this is the default)
|
|
545
610
|
- `fault_injection`
|
|
546
611
|
|
|
547
|
-
|
|
612
|
+
> [!WARNING]
|
|
613
|
+
> Unknown proxy kinds fall back to `none`. If fault injection is not taking effect, check the `kind` spelling or register the custom kind before loading the configuration.
|
|
614
|
+
|
|
615
|
+
Custom proxy kinds can be registered through `Nonnative.proxies`:
|
|
616
|
+
|
|
617
|
+
```ruby
|
|
618
|
+
Nonnative.proxies['custom'] = CustomProxy
|
|
619
|
+
```
|
|
548
620
|
|
|
549
|
-
|
|
621
|
+
For `fault_injection`, keep the runner `host` and first `ports` entry as the client-facing endpoint and use nested `proxy.host`/`proxy.port` for the upstream target behind the proxy.
|
|
622
|
+
|
|
623
|
+
##### ⚙️ Process Proxies
|
|
550
624
|
|
|
551
625
|
Add this to an existing process configuration:
|
|
552
626
|
|
|
@@ -561,7 +635,7 @@ Nonnative.configure do |config|
|
|
|
561
635
|
|
|
562
636
|
config.process do |p|
|
|
563
637
|
p.host = '127.0.0.1'
|
|
564
|
-
p.
|
|
638
|
+
p.ports = [20_000]
|
|
565
639
|
|
|
566
640
|
p.proxy = {
|
|
567
641
|
kind: 'fault_injection',
|
|
@@ -587,7 +661,8 @@ log: nonnative.log
|
|
|
587
661
|
processes:
|
|
588
662
|
-
|
|
589
663
|
host: 127.0.0.1
|
|
590
|
-
|
|
664
|
+
ports:
|
|
665
|
+
- 20000
|
|
591
666
|
proxy:
|
|
592
667
|
kind: fault_injection
|
|
593
668
|
host: 127.0.0.1
|
|
@@ -598,7 +673,7 @@ processes:
|
|
|
598
673
|
delay: 5
|
|
599
674
|
```
|
|
600
675
|
|
|
601
|
-
##### Proxies
|
|
676
|
+
##### 🖥️ Server Proxies
|
|
602
677
|
|
|
603
678
|
Add this to an existing server configuration:
|
|
604
679
|
|
|
@@ -613,7 +688,7 @@ Nonnative.configure do |config|
|
|
|
613
688
|
|
|
614
689
|
config.server do |s|
|
|
615
690
|
s.host = '127.0.0.1'
|
|
616
|
-
s.
|
|
691
|
+
s.ports = [20_000]
|
|
617
692
|
|
|
618
693
|
s.proxy = {
|
|
619
694
|
kind: 'fault_injection',
|
|
@@ -639,7 +714,8 @@ log: nonnative.log
|
|
|
639
714
|
servers:
|
|
640
715
|
-
|
|
641
716
|
host: 127.0.0.1
|
|
642
|
-
|
|
717
|
+
ports:
|
|
718
|
+
- 20000
|
|
643
719
|
proxy:
|
|
644
720
|
kind: fault_injection
|
|
645
721
|
host: 127.0.0.1
|
|
@@ -650,7 +726,7 @@ servers:
|
|
|
650
726
|
delay: 5
|
|
651
727
|
```
|
|
652
728
|
|
|
653
|
-
##### Proxies
|
|
729
|
+
##### 🧩 Service Proxies
|
|
654
730
|
|
|
655
731
|
Add this to an existing service configuration:
|
|
656
732
|
|
|
@@ -666,7 +742,7 @@ Nonnative.configure do |config|
|
|
|
666
742
|
config.service do |s|
|
|
667
743
|
s.name = 'redis'
|
|
668
744
|
s.host = '127.0.0.1'
|
|
669
|
-
s.
|
|
745
|
+
s.ports = [16_379]
|
|
670
746
|
|
|
671
747
|
s.proxy = {
|
|
672
748
|
kind: 'fault_injection',
|
|
@@ -693,7 +769,8 @@ services:
|
|
|
693
769
|
-
|
|
694
770
|
name: redis
|
|
695
771
|
host: 127.0.0.1
|
|
696
|
-
|
|
772
|
+
ports:
|
|
773
|
+
- 16379
|
|
697
774
|
proxy:
|
|
698
775
|
kind: fault_injection
|
|
699
776
|
host: 127.0.0.1
|
|
@@ -704,17 +781,17 @@ services:
|
|
|
704
781
|
delay: 5
|
|
705
782
|
```
|
|
706
783
|
|
|
707
|
-
##### Fault Injection
|
|
784
|
+
##### 🧪 Fault Injection
|
|
708
785
|
|
|
709
786
|
The `fault_injection` proxy allows you to simulate failures by injecting them. We currently support the following:
|
|
710
787
|
|
|
711
|
-
Clients connect to the runner `host
|
|
788
|
+
Clients connect to the runner `host` and first configured `ports` entry, while the proxy forwards traffic to nested `proxy.host`/`proxy.port`.
|
|
712
789
|
|
|
713
790
|
- `close_all` - Closes the socket as soon as it connects.
|
|
714
|
-
- `delay` -
|
|
715
|
-
- `invalid_data` -
|
|
791
|
+
- `delay` - Delays traffic on the connection. Defaults to 2 seconds and can be configured through options.
|
|
792
|
+
- `invalid_data` - Forwards client requests unchanged, then corrupts upstream responses before they reach the client.
|
|
716
793
|
|
|
717
|
-
###### Fault Injection Processes
|
|
794
|
+
###### ⚙️ Fault Injection Processes
|
|
718
795
|
|
|
719
796
|
Set it up programmatically:
|
|
720
797
|
|
|
@@ -733,7 +810,7 @@ Given I set the proxy for process 'process_1' to 'close_all'
|
|
|
733
810
|
Then I should reset the proxy for process 'process_1'
|
|
734
811
|
```
|
|
735
812
|
|
|
736
|
-
###### Fault Injection Servers
|
|
813
|
+
###### 🖥️ Fault Injection Servers
|
|
737
814
|
|
|
738
815
|
Set it up programmatically:
|
|
739
816
|
|
|
@@ -752,7 +829,7 @@ Given I set the proxy for server 'server_1' to 'close_all'
|
|
|
752
829
|
Then I should reset the proxy for server 'server_1'
|
|
753
830
|
```
|
|
754
831
|
|
|
755
|
-
###### Fault Injection Services
|
|
832
|
+
###### 🧩 Fault Injection Services
|
|
756
833
|
|
|
757
834
|
Set it up programmatically:
|
|
758
835
|
|
|
@@ -771,7 +848,7 @@ Given I set the proxy for service 'service_1' to 'close_all'
|
|
|
771
848
|
Then I should reset the proxy for service 'service_1'
|
|
772
849
|
```
|
|
773
850
|
|
|
774
|
-
### Go
|
|
851
|
+
### 🐹 Go
|
|
775
852
|
|
|
776
853
|
As we love using Go as a language for services we have added support to start binaries with defined parameters.
|
|
777
854
|
|
|
@@ -782,7 +859,7 @@ Nonnative.configure do |config|
|
|
|
782
859
|
config.process do |p|
|
|
783
860
|
p.name = 'go'
|
|
784
861
|
p.command = -> { Nonnative.go_argv(%w[cover], 'reports', 'your_binary', 'sub_command', '-i file:.config/server.yml') }
|
|
785
|
-
p.
|
|
862
|
+
p.ports = [12_345]
|
|
786
863
|
end
|
|
787
864
|
end
|
|
788
865
|
```
|
|
@@ -791,6 +868,9 @@ Use `Nonnative.go_argv(...)` when a process should execute without shell interpr
|
|
|
791
868
|
|
|
792
869
|
YAML `go:` configuration is for Go test binaries compiled with `go test -c`. It builds argv entries in this order: executable, optional `-test.*` profiling/trace/coverage flags, command, then parameters. Parameter strings are parsed into argv words with shell-style quoting, but the argv entries are executed without shell interpretation.
|
|
793
870
|
|
|
871
|
+
> [!IMPORTANT]
|
|
872
|
+
> If `tools` is omitted or empty, Nonnative enables all Go tools: `prof`, `trace`, and `cover`. Provide a subset, such as `tools: [cover]`, to limit the generated `-test.*` flags.
|
|
873
|
+
|
|
794
874
|
To get this to work you will need to create a `main_test.go` file with these contents:
|
|
795
875
|
|
|
796
876
|
```go
|
|
@@ -830,6 +910,7 @@ processes:
|
|
|
830
910
|
parameters:
|
|
831
911
|
- "-i file:.config/server.yml"
|
|
832
912
|
timeout: 5
|
|
833
|
-
|
|
913
|
+
ports:
|
|
914
|
+
- 8000
|
|
834
915
|
log: go.log
|
|
835
916
|
```
|
|
@@ -19,7 +19,7 @@ module Nonnative
|
|
|
19
19
|
# p.name = 'api'
|
|
20
20
|
# p.command = -> { './bin/api' }
|
|
21
21
|
# p.host = '127.0.0.1'
|
|
22
|
-
# p.
|
|
22
|
+
# p.ports = [8080, 9090]
|
|
23
23
|
# p.timeout = 10
|
|
24
24
|
# p.log = 'api.log'
|
|
25
25
|
# end
|
|
@@ -123,14 +123,14 @@ module Nonnative
|
|
|
123
123
|
|
|
124
124
|
def add_processes(cfg)
|
|
125
125
|
processes = cfg.processes || []
|
|
126
|
-
processes.each do |
|
|
127
|
-
process do |
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
runner_attributes(
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
processes.each do |loaded_process|
|
|
127
|
+
process do |process_config|
|
|
128
|
+
process_config.command = command(loaded_process)
|
|
129
|
+
process_config.signal = loaded_process.signal
|
|
130
|
+
process_config.environment = loaded_process.environment
|
|
131
|
+
runner_attributes(process_config, loaded_process)
|
|
132
|
+
|
|
133
|
+
assign_proxy(process_config, loaded_process.proxy)
|
|
134
134
|
end
|
|
135
135
|
end
|
|
136
136
|
end
|
|
@@ -149,25 +149,25 @@ module Nonnative
|
|
|
149
149
|
|
|
150
150
|
def add_servers(cfg)
|
|
151
151
|
servers = cfg.servers || []
|
|
152
|
-
servers.each do |
|
|
153
|
-
server do |
|
|
154
|
-
|
|
155
|
-
runner_attributes(
|
|
152
|
+
servers.each do |loaded_server|
|
|
153
|
+
server do |server_config|
|
|
154
|
+
server_config.klass = Object.const_get(server_class_name(loaded_server))
|
|
155
|
+
runner_attributes(server_config, loaded_server)
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
assign_proxy(server_config, loaded_server.proxy)
|
|
158
158
|
end
|
|
159
159
|
end
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
def add_services(cfg)
|
|
163
163
|
services = cfg.services || []
|
|
164
|
-
services.each do |
|
|
165
|
-
service do |
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
164
|
+
services.each do |loaded_service|
|
|
165
|
+
service do |service_config|
|
|
166
|
+
service_config.name = loaded_service.name
|
|
167
|
+
service_config.host = loaded_service.host if loaded_service.host
|
|
168
|
+
assign_ports(service_config, loaded_service)
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
assign_proxy(service_config, loaded_service.proxy)
|
|
171
171
|
end
|
|
172
172
|
end
|
|
173
173
|
end
|
|
@@ -182,24 +182,31 @@ module Nonnative
|
|
|
182
182
|
runner.timeout = loaded.timeout
|
|
183
183
|
runner.wait = loaded.wait if loaded.wait
|
|
184
184
|
runner.host = loaded.host if loaded.host
|
|
185
|
-
runner
|
|
185
|
+
assign_ports(runner, loaded)
|
|
186
186
|
runner.log = loaded.log if loaded.respond_to?(:log)
|
|
187
187
|
end
|
|
188
188
|
|
|
189
|
-
def
|
|
190
|
-
|
|
189
|
+
def assign_ports(runner, loaded)
|
|
190
|
+
values = loaded.to_h
|
|
191
|
+
raise ArgumentError, "Use 'ports' instead of 'port' for runner '#{loaded.name}'" if values.key?(:port) || values.key?('port')
|
|
191
192
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
runner.ports = loaded.ports if loaded.ports
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def assign_proxy(runner, loaded_proxy)
|
|
197
|
+
return unless loaded_proxy
|
|
198
|
+
|
|
199
|
+
proxy_attributes = {
|
|
200
|
+
kind: loaded_proxy.kind,
|
|
201
|
+
port: loaded_proxy.port,
|
|
202
|
+
log: loaded_proxy.log,
|
|
203
|
+
options: loaded_proxy.options
|
|
197
204
|
}
|
|
198
205
|
|
|
199
|
-
|
|
200
|
-
|
|
206
|
+
proxy_attributes[:host] = loaded_proxy.host if loaded_proxy.host
|
|
207
|
+
proxy_attributes[:wait] = loaded_proxy.wait if loaded_proxy.wait
|
|
201
208
|
|
|
202
|
-
runner.proxy =
|
|
209
|
+
runner.proxy = proxy_attributes
|
|
203
210
|
end
|
|
204
211
|
end
|
|
205
212
|
end
|
|
@@ -19,7 +19,7 @@ module Nonnative
|
|
|
19
19
|
# @return [String, nil] signal name to use for stopping (defaults to `"INT"` when not set)
|
|
20
20
|
attr_accessor :signal
|
|
21
21
|
|
|
22
|
-
# @return [Numeric] readiness timeout (seconds) used when waiting for
|
|
22
|
+
# @return [Numeric] readiness timeout (seconds) used when waiting for ports to open/close
|
|
23
23
|
attr_accessor :timeout
|
|
24
24
|
|
|
25
25
|
# @return [String] log file path to append process stdout/stderr to
|
|
@@ -15,9 +15,12 @@ module Nonnative
|
|
|
15
15
|
class ConfigurationRunner
|
|
16
16
|
# @return [String, nil] runner name used for lookup (for example via `pool.process_by_name`)
|
|
17
17
|
# @return [String] host to bind/connect to (defaults to `"127.0.0.1"`)
|
|
18
|
-
# @return [Integer]
|
|
18
|
+
# @return [Array<Integer>] ports to bind/connect to
|
|
19
19
|
# @return [Numeric] wait interval (seconds) used by runners between lifecycle steps
|
|
20
|
-
attr_accessor :name, :host, :
|
|
20
|
+
attr_accessor :name, :host, :wait
|
|
21
|
+
|
|
22
|
+
# @return [Array<Integer>] client-facing ports used for readiness/shutdown checks
|
|
23
|
+
attr_reader :ports
|
|
21
24
|
|
|
22
25
|
# Proxy configuration for this runner.
|
|
23
26
|
#
|
|
@@ -31,19 +34,37 @@ module Nonnative
|
|
|
31
34
|
#
|
|
32
35
|
# Defaults:
|
|
33
36
|
# - `host`: `"127.0.0.1"`
|
|
34
|
-
# - `
|
|
37
|
+
# - `ports`: `[0]`
|
|
35
38
|
# - `wait`: `0.1`
|
|
36
39
|
# - `proxy`: a new {Nonnative::ConfigurationProxy} with its own defaults
|
|
37
40
|
#
|
|
38
41
|
# @return [void]
|
|
39
42
|
def initialize
|
|
40
43
|
self.host = '127.0.0.1'
|
|
41
|
-
self.
|
|
44
|
+
self.ports = [0]
|
|
42
45
|
self.wait = 0.1
|
|
43
46
|
|
|
44
47
|
@proxy = Nonnative::ConfigurationProxy.new
|
|
45
48
|
end
|
|
46
49
|
|
|
50
|
+
# Sets the client-facing ports for this runner.
|
|
51
|
+
#
|
|
52
|
+
# @param value [Array<Integer>] ports to check for readiness/shutdown
|
|
53
|
+
# @return [void]
|
|
54
|
+
def ports=(value)
|
|
55
|
+
@ports = Array(value)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the primary client-facing port.
|
|
59
|
+
#
|
|
60
|
+
# This preserves a single endpoint for proxy binding and client helpers while the public
|
|
61
|
+
# configuration contract uses {#ports}.
|
|
62
|
+
#
|
|
63
|
+
# @return [Integer]
|
|
64
|
+
def port
|
|
65
|
+
ports.first
|
|
66
|
+
end
|
|
67
|
+
|
|
47
68
|
# Sets proxy configuration using a hash-like value.
|
|
48
69
|
#
|
|
49
70
|
# This is primarily used when loading YAML configuration files, where proxy attributes are
|
|
@@ -12,7 +12,7 @@ module Nonnative
|
|
|
12
12
|
# @see Nonnative::Server
|
|
13
13
|
class ConfigurationServer < ConfigurationRunner
|
|
14
14
|
# @return [Class] a class that implements `#initialize(service)`, and lifecycle hooks expected by {Nonnative::Server}
|
|
15
|
-
# @return [Numeric] readiness timeout (seconds) used when waiting for
|
|
15
|
+
# @return [Numeric] readiness timeout (seconds) used when waiting for ports to open/close
|
|
16
16
|
# @return [String] log file path used by server implementations (for example Puma/gRPC log files)
|
|
17
17
|
attr_accessor :klass, :timeout, :log
|
|
18
18
|
end
|
|
@@ -18,8 +18,8 @@ module Nonnative
|
|
|
18
18
|
#
|
|
19
19
|
# ## Wiring
|
|
20
20
|
#
|
|
21
|
-
# When enabled, your test/client should connect to the runner `host`
|
|
22
|
-
# and the proxy will forward traffic to the upstream target exposed by {#host}:{#port}.
|
|
21
|
+
# When enabled, your test/client should connect to the runner `host` and first configured port (the
|
|
22
|
+
# proxy endpoint), and the proxy will forward traffic to the upstream target exposed by {#host}:{#port}.
|
|
23
23
|
#
|
|
24
24
|
# ## Configuration
|
|
25
25
|
#
|
|
@@ -62,7 +62,7 @@ module Nonnative
|
|
|
62
62
|
|
|
63
63
|
# Starts the proxy accept loop in a background thread.
|
|
64
64
|
#
|
|
65
|
-
# This binds a TCP server on the underlying runner’s `service.host`
|
|
65
|
+
# This binds a TCP server on the underlying runner’s `service.host` and first configured port.
|
|
66
66
|
# Clients connect to that runner endpoint, while upstream traffic is forwarded to {#host}:{#port}.
|
|
67
67
|
#
|
|
68
68
|
# @return [void]
|
|
@@ -34,7 +34,7 @@ module Nonnative
|
|
|
34
34
|
# Binds the gRPC server and begins serving requests.
|
|
35
35
|
#
|
|
36
36
|
# The server binds to the upstream proxy host/port so the fault-injection proxy can expose the
|
|
37
|
-
# runner host
|
|
37
|
+
# runner host and first configured port as the client-facing endpoint used by readiness checks.
|
|
38
38
|
#
|
|
39
39
|
# @return [void]
|
|
40
40
|
def perform_start
|
|
@@ -50,9 +50,10 @@ module Nonnative
|
|
|
50
50
|
|
|
51
51
|
# Executes the upstream request and returns the response.
|
|
52
52
|
#
|
|
53
|
-
# @param
|
|
54
|
-
# @param
|
|
55
|
-
# @param
|
|
53
|
+
# @param method [Symbol] HTTP verb name (e.g. `:get`)
|
|
54
|
+
# @param url [String] upstream URL
|
|
55
|
+
# @param headers [Hash] request headers
|
|
56
|
+
# @param payload [String, nil] request payload
|
|
56
57
|
# @return [RestClient::Response] response for error statuses, otherwise RestClient return value
|
|
57
58
|
def api_response(method:, url:, headers:, payload: nil)
|
|
58
59
|
options = { method:, url:, headers: }
|
|
@@ -109,7 +110,7 @@ module Nonnative
|
|
|
109
110
|
# s.klass = Nonnative::Features::HTTPProxyServer
|
|
110
111
|
# s.timeout = 2
|
|
111
112
|
# s.host = '127.0.0.1'
|
|
112
|
-
# s.
|
|
113
|
+
# s.ports = [4567]
|
|
113
114
|
# s.log = 'proxy.log'
|
|
114
115
|
# end
|
|
115
116
|
# end
|
|
@@ -20,7 +20,7 @@ module Nonnative
|
|
|
20
20
|
# s.klass = ->(service) { Nonnative::HTTPServer.new(app, service) }
|
|
21
21
|
# s.timeout = 2
|
|
22
22
|
# s.host = '127.0.0.1'
|
|
23
|
-
# s.
|
|
23
|
+
# s.ports = [4567]
|
|
24
24
|
# s.log = 'http.log'
|
|
25
25
|
# end
|
|
26
26
|
# end
|
|
@@ -49,7 +49,7 @@ module Nonnative
|
|
|
49
49
|
# Binds the Puma server and begins serving.
|
|
50
50
|
#
|
|
51
51
|
# The listener binds to the upstream proxy host/port so the fault-injection proxy can expose the
|
|
52
|
-
# runner host
|
|
52
|
+
# runner host and first configured port as the client-facing endpoint used by readiness checks.
|
|
53
53
|
#
|
|
54
54
|
# @return [void]
|
|
55
55
|
def perform_start
|
|
@@ -66,6 +66,6 @@ module Nonnative
|
|
|
66
66
|
|
|
67
67
|
private
|
|
68
68
|
|
|
69
|
-
attr_reader :
|
|
69
|
+
attr_reader :server
|
|
70
70
|
end
|
|
71
71
|
end
|
data/lib/nonnative/no_proxy.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Nonnative
|
|
|
5
5
|
#
|
|
6
6
|
# This is the default proxy when `service.proxy.kind` is `"none"` (or an unknown kind is provided).
|
|
7
7
|
# It does not bind/listen or alter traffic; it simply exposes the underlying runner's configured
|
|
8
|
-
# `host` and `port`.
|
|
8
|
+
# `host` and primary `port`.
|
|
9
9
|
#
|
|
10
10
|
# Runners can always call `start`, `stop`, and `reset` safely on this proxy.
|
|
11
11
|
#
|
|
@@ -50,7 +50,7 @@ module Nonnative
|
|
|
50
50
|
|
|
51
51
|
# Returns the port clients should connect to.
|
|
52
52
|
#
|
|
53
|
-
# For {NoProxy}, this is the underlying runner configuration port.
|
|
53
|
+
# For {NoProxy}, this is the first underlying runner configuration port.
|
|
54
54
|
#
|
|
55
55
|
# @return [Integer]
|
|
56
56
|
def port
|
data/lib/nonnative/pool.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Nonnative
|
|
|
9
9
|
# - On start: services first, then servers/processes (in parallel port-check threads)
|
|
10
10
|
# - On stop: processes/servers first, then services
|
|
11
11
|
#
|
|
12
|
-
# Readiness and shutdown are determined via TCP port checks ({Nonnative::
|
|
12
|
+
# Readiness and shutdown are determined via TCP port checks ({Nonnative::Ports#open?} / {Nonnative::Ports#closed?}).
|
|
13
13
|
#
|
|
14
14
|
# @see Nonnative.start
|
|
15
15
|
# @see Nonnative.stop
|
|
@@ -35,7 +35,7 @@ module Nonnative
|
|
|
35
35
|
errors = []
|
|
36
36
|
|
|
37
37
|
errors.concat(service_lifecycle(services, :start, :start))
|
|
38
|
-
[servers, processes].each { |
|
|
38
|
+
[servers, processes].each { |runners| errors.concat(run_lifecycle_checks(runners, :start, :open?, :start, &)) }
|
|
39
39
|
|
|
40
40
|
errors
|
|
41
41
|
end
|
|
@@ -51,7 +51,7 @@ module Nonnative
|
|
|
51
51
|
def stop(&)
|
|
52
52
|
errors = []
|
|
53
53
|
|
|
54
|
-
[processes, servers].each { |
|
|
54
|
+
[processes, servers].each { |runners| errors.concat(run_lifecycle_checks(runners, :stop, :closed?, :stop, &)) }
|
|
55
55
|
errors.concat(service_lifecycle(services, :stop, :stop))
|
|
56
56
|
|
|
57
57
|
errors
|
|
@@ -69,7 +69,9 @@ module Nonnative
|
|
|
69
69
|
def rollback(&)
|
|
70
70
|
errors = []
|
|
71
71
|
|
|
72
|
-
[existing_processes, existing_servers].each
|
|
72
|
+
[existing_processes, existing_servers].each do |runners|
|
|
73
|
+
errors.concat(run_lifecycle_checks(runners, :stop, :closed?, :stop, &))
|
|
74
|
+
end
|
|
73
75
|
errors.concat(service_lifecycle(existing_services, :stop, :stop))
|
|
74
76
|
|
|
75
77
|
errors
|
|
@@ -129,7 +131,7 @@ module Nonnative
|
|
|
129
131
|
|
|
130
132
|
@processes = []
|
|
131
133
|
configuration.processes.each do |p|
|
|
132
|
-
@processes << [Nonnative::Process.new(p), Nonnative::
|
|
134
|
+
@processes << [Nonnative::Process.new(p), Nonnative::Ports.new(p)]
|
|
133
135
|
end
|
|
134
136
|
|
|
135
137
|
@processes
|
|
@@ -140,7 +142,7 @@ module Nonnative
|
|
|
140
142
|
|
|
141
143
|
@servers = []
|
|
142
144
|
configuration.servers.each do |s|
|
|
143
|
-
@servers << [s.klass.new(s), Nonnative::
|
|
145
|
+
@servers << [s.klass.new(s), Nonnative::Ports.new(s)]
|
|
144
146
|
end
|
|
145
147
|
|
|
146
148
|
@servers
|
|
@@ -177,15 +179,15 @@ module Nonnative
|
|
|
177
179
|
end
|
|
178
180
|
end
|
|
179
181
|
|
|
180
|
-
def
|
|
182
|
+
def run_lifecycle_checks(runners, lifecycle_method, port_method, action, &)
|
|
181
183
|
checks = []
|
|
182
184
|
errors = []
|
|
183
185
|
|
|
184
|
-
|
|
185
|
-
values =
|
|
186
|
-
checks << [
|
|
186
|
+
runners.each do |runner, port|
|
|
187
|
+
values = runner.send(lifecycle_method)
|
|
188
|
+
checks << [runner, values, Thread.new { check_port(port, port_method) }]
|
|
187
189
|
rescue StandardError => e
|
|
188
|
-
errors << lifecycle_error(action,
|
|
190
|
+
errors << lifecycle_error(action, runner, e)
|
|
189
191
|
end
|
|
190
192
|
|
|
191
193
|
errors.concat(yield_results(checks, action, &))
|
data/lib/nonnative/port.rb
CHANGED
|
@@ -3,21 +3,23 @@
|
|
|
3
3
|
module Nonnative
|
|
4
4
|
# Performs TCP port readiness/shutdown checks for a configured runner.
|
|
5
5
|
#
|
|
6
|
-
# Nonnative uses this to decide whether a process/server is ready after start, and whether it
|
|
7
|
-
# shut down after stop. The checks repeatedly attempt to open a TCP connection to `process.host`
|
|
8
|
-
# and `
|
|
6
|
+
# Nonnative uses this to decide whether a process/server port is ready after start, and whether it
|
|
7
|
+
# has shut down after stop. The checks repeatedly attempt to open a TCP connection to `process.host`
|
|
8
|
+
# and the configured `port` until either:
|
|
9
9
|
#
|
|
10
10
|
# - the expected condition is met, or
|
|
11
11
|
# - the configured timeout elapses (in which case the method returns `false`)
|
|
12
12
|
#
|
|
13
13
|
# The `process` argument is a runner configuration object (e.g. {Nonnative::ConfigurationProcess}
|
|
14
|
-
# or {Nonnative::ConfigurationServer}) that responds to `host
|
|
14
|
+
# or {Nonnative::ConfigurationServer}) that responds to `host` and `timeout`.
|
|
15
15
|
#
|
|
16
16
|
# @see Nonnative::Pool for how these checks are orchestrated during start/stop
|
|
17
17
|
class Port
|
|
18
|
-
# @param process [#host, #
|
|
19
|
-
|
|
18
|
+
# @param process [#host, #timeout] runner configuration providing connection details
|
|
19
|
+
# @param port [Integer] port to check
|
|
20
|
+
def initialize(process, port = process.port)
|
|
20
21
|
@process = process
|
|
22
|
+
@port = port
|
|
21
23
|
@timeout = Nonnative::Timeout.new(process.timeout)
|
|
22
24
|
end
|
|
23
25
|
|
|
@@ -28,7 +30,7 @@ module Nonnative
|
|
|
28
30
|
#
|
|
29
31
|
# @return [Boolean] `true` if the port opened in time; otherwise `false`
|
|
30
32
|
def open?
|
|
31
|
-
Nonnative.logger.info "checking if port '#{
|
|
33
|
+
Nonnative.logger.info "checking if port '#{port}' is open on host '#{process.host}'"
|
|
32
34
|
|
|
33
35
|
timeout.perform do
|
|
34
36
|
open_socket
|
|
@@ -46,7 +48,7 @@ module Nonnative
|
|
|
46
48
|
#
|
|
47
49
|
# @return [Boolean] `true` if the port closed in time; otherwise `false`
|
|
48
50
|
def closed?
|
|
49
|
-
Nonnative.logger.info "checking if port '#{
|
|
51
|
+
Nonnative.logger.info "checking if port '#{port}' is closed on host '#{process.host}'"
|
|
50
52
|
|
|
51
53
|
timeout.perform do
|
|
52
54
|
open_socket
|
|
@@ -61,10 +63,10 @@ module Nonnative
|
|
|
61
63
|
|
|
62
64
|
private
|
|
63
65
|
|
|
64
|
-
attr_reader :process, :timeout
|
|
66
|
+
attr_reader :process, :port, :timeout
|
|
65
67
|
|
|
66
68
|
def open_socket
|
|
67
|
-
TCPSocket.new(process.host,
|
|
69
|
+
TCPSocket.new(process.host, port).close
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
def sleep_interval
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Performs aggregate TCP readiness/shutdown checks for all configured runner ports.
|
|
5
|
+
#
|
|
6
|
+
# A runner is considered ready only when every configured port is open, and stopped only when every
|
|
7
|
+
# configured port is closed.
|
|
8
|
+
#
|
|
9
|
+
# @see Nonnative::Port
|
|
10
|
+
class Ports
|
|
11
|
+
# @param runner [#host, #ports, #timeout] runner configuration providing connection details
|
|
12
|
+
def initialize(runner)
|
|
13
|
+
@ports = runner.ports.map { |port| Nonnative::Port.new(runner, port) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns whether all configured ports become connectable before their timeouts elapse.
|
|
17
|
+
#
|
|
18
|
+
# @return [Boolean]
|
|
19
|
+
def open?
|
|
20
|
+
ports.all?(&:open?)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Returns whether all configured ports become non-connectable before their timeouts elapse.
|
|
24
|
+
#
|
|
25
|
+
# @return [Boolean]
|
|
26
|
+
def closed?
|
|
27
|
+
ports.all?(&:closed?)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :ports
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -65,18 +65,18 @@ module Nonnative
|
|
|
65
65
|
::TCPSocket.new(proxy.host, proxy.port)
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
# Pipes data from `
|
|
68
|
+
# Pipes data from `source_socket` to `destination_socket` when the source is readable.
|
|
69
69
|
#
|
|
70
70
|
# @param ready [Array<Array<IO>>] the result from `select`
|
|
71
|
-
# @param
|
|
72
|
-
# @param
|
|
71
|
+
# @param source_socket [IO] readable side
|
|
72
|
+
# @param destination_socket [IO] writable side
|
|
73
73
|
# @return [Boolean] whether the piping loop should terminate
|
|
74
|
-
def pipe?(ready,
|
|
75
|
-
if ready[0].include?(
|
|
76
|
-
data = read(
|
|
74
|
+
def pipe?(ready, source_socket, destination_socket)
|
|
75
|
+
if ready[0].include?(source_socket)
|
|
76
|
+
data = read(source_socket)
|
|
77
77
|
return true if data.empty?
|
|
78
78
|
|
|
79
|
-
write
|
|
79
|
+
write destination_socket, data
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
false
|
data/lib/nonnative/version.rb
CHANGED
data/lib/nonnative.rb
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
# p.name = 'api'
|
|
25
25
|
# p.command = -> { './bin/api' }
|
|
26
26
|
# p.host = '127.0.0.1'
|
|
27
|
-
# p.
|
|
27
|
+
# p.ports = [8080, 9090]
|
|
28
28
|
# p.timeout = 10
|
|
29
29
|
# p.log = 'api.log'
|
|
30
30
|
# end
|
|
@@ -68,6 +68,7 @@ require 'nonnative/stop_error'
|
|
|
68
68
|
require 'nonnative/not_found_error'
|
|
69
69
|
require 'nonnative/timeout'
|
|
70
70
|
require 'nonnative/port'
|
|
71
|
+
require 'nonnative/ports'
|
|
71
72
|
require 'nonnative/configuration_file'
|
|
72
73
|
require 'nonnative/configuration'
|
|
73
74
|
require 'nonnative/configuration_runner'
|
|
@@ -208,7 +209,7 @@ module Nonnative
|
|
|
208
209
|
|
|
209
210
|
# Starts all configured services, servers, and processes, and waits for readiness.
|
|
210
211
|
#
|
|
211
|
-
# Readiness is determined by attempting to connect to each runner's configured host/
|
|
212
|
+
# Readiness is determined by attempting to connect to each runner's configured host/ports.
|
|
212
213
|
#
|
|
213
214
|
# @return [void]
|
|
214
215
|
# @raise [Nonnative::StartError] if one or more runners fail to start or become ready in time
|
data/nonnative.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.authors = ['Alejandro Falkowski']
|
|
12
12
|
spec.email = ['alexrfalkowski@gmail.com']
|
|
13
13
|
|
|
14
|
-
spec.summary = 'Allows you to keep using the power of
|
|
14
|
+
spec.summary = 'Allows you to keep using the power of Ruby to test other systems'
|
|
15
15
|
spec.description = spec.summary
|
|
16
16
|
spec.homepage = 'https://github.com/alexfalkowski/nonnative'
|
|
17
17
|
spec.license = 'MIT'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nonnative
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alejandro Falkowski
|
|
@@ -263,7 +263,7 @@ dependencies:
|
|
|
263
263
|
- - "<"
|
|
264
264
|
- !ruby/object:Gem::Version
|
|
265
265
|
version: '5'
|
|
266
|
-
description: Allows you to keep using the power of
|
|
266
|
+
description: Allows you to keep using the power of Ruby to test other systems
|
|
267
267
|
email:
|
|
268
268
|
- alexrfalkowski@gmail.com
|
|
269
269
|
executables: []
|
|
@@ -310,6 +310,7 @@ files:
|
|
|
310
310
|
- lib/nonnative/observability.rb
|
|
311
311
|
- lib/nonnative/pool.rb
|
|
312
312
|
- lib/nonnative/port.rb
|
|
313
|
+
- lib/nonnative/ports.rb
|
|
313
314
|
- lib/nonnative/process.rb
|
|
314
315
|
- lib/nonnative/proxy.rb
|
|
315
316
|
- lib/nonnative/proxy_factory.rb
|
|
@@ -348,5 +349,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
348
349
|
requirements: []
|
|
349
350
|
rubygems_version: 4.0.11
|
|
350
351
|
specification_version: 4
|
|
351
|
-
summary: Allows you to keep using the power of
|
|
352
|
+
summary: Allows you to keep using the power of Ruby to test other systems
|
|
352
353
|
test_files: []
|