nonnative 3.2.1 → 3.9.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 +30 -21
- data/.gitignore +11 -2
- data/AGENTS.md +8 -8
- data/Gemfile.lock +10 -10
- data/README.md +20 -8
- data/lib/nonnative/configuration.rb +27 -2
- data/lib/nonnative/configuration_process.rb +24 -1
- data/lib/nonnative/configuration_proxy.rb +10 -1
- data/lib/nonnative/configuration_readiness.rb +35 -0
- data/lib/nonnative/configuration_runner.rb +8 -2
- data/lib/nonnative/configuration_server.rb +19 -3
- data/lib/nonnative/cucumber.rb +20 -0
- data/lib/nonnative/fault_injection_proxy.rb +2 -2
- data/lib/nonnative/http_probe.rb +64 -0
- data/lib/nonnative/http_proxy_server.rb +9 -1
- data/lib/nonnative/http_server.rb +19 -6
- data/lib/nonnative/no_proxy.rb +1 -1
- data/lib/nonnative/pool.rb +6 -3
- data/lib/nonnative/port.rb +7 -0
- data/lib/nonnative/ports.rb +23 -2
- data/lib/nonnative/process.rb +8 -0
- data/lib/nonnative/proxy.rb +4 -1
- data/lib/nonnative/proxy_factory.rb +3 -1
- data/lib/nonnative/version.rb +1 -1
- data/lib/nonnative.rb +14 -11
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ef654d45571b49eaf151e67b60a8eaa5c7b6df2b1a2e877d774a30ec80cc3281
|
|
4
|
+
data.tar.gz: 3526ed68b510ee72ac7b2d92764109bbd729653b5e1ba06b8b7a0f1f60b44c07
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f28ca3884eef23d8db41bcef405205e5afca8369622627f415a845340579e54b17102cbdcabd3d44933ae7630a26eb3c6b3ba05c3e50da08092b84ec873a1629
|
|
7
|
+
data.tar.gz: 69166708a716e13658f5f25c86ccea83c201cccdeeec11296249fc318e7cabbec78816048ca266f9c890538b920d091968bff3275888a2c7bf7d9853343313f6
|
data/.circleci/config.yml
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
version: 2.1
|
|
2
2
|
|
|
3
|
+
commands:
|
|
4
|
+
sync-submodules:
|
|
5
|
+
steps:
|
|
6
|
+
- run: git submodule sync
|
|
7
|
+
- run: git submodule update --init
|
|
8
|
+
checkout-submodules:
|
|
9
|
+
steps:
|
|
10
|
+
- checkout:
|
|
11
|
+
method: blobless
|
|
12
|
+
- sync-submodules
|
|
13
|
+
|
|
14
|
+
executors:
|
|
15
|
+
ruby:
|
|
16
|
+
docker:
|
|
17
|
+
- image: alexfalkowski/ruby:3.17
|
|
18
|
+
release:
|
|
19
|
+
docker:
|
|
20
|
+
- image: alexfalkowski/release:8.18
|
|
21
|
+
alpine:
|
|
22
|
+
docker:
|
|
23
|
+
- image: alpine:latest
|
|
24
|
+
|
|
3
25
|
jobs:
|
|
4
26
|
build:
|
|
5
|
-
|
|
6
|
-
- image: alexfalkowski/ruby:3.14
|
|
27
|
+
executor: ruby
|
|
7
28
|
working_directory: ~/nonnative
|
|
8
29
|
steps:
|
|
9
|
-
- checkout
|
|
10
|
-
method: blobless
|
|
11
|
-
- run: git submodule sync
|
|
12
|
-
- run: git submodule update --init
|
|
30
|
+
- checkout-submodules
|
|
13
31
|
- run: make source-key
|
|
14
32
|
- restore_cache:
|
|
15
33
|
name: restore deps
|
|
@@ -34,34 +52,25 @@ jobs:
|
|
|
34
52
|
- run: make codecov-upload
|
|
35
53
|
resource_class: arm.large
|
|
36
54
|
sync:
|
|
37
|
-
|
|
38
|
-
- image: alexfalkowski/release:8.15
|
|
55
|
+
executor: release
|
|
39
56
|
working_directory: ~/nonnative
|
|
40
57
|
steps:
|
|
41
|
-
- checkout
|
|
42
|
-
method: blobless
|
|
43
|
-
- run: git submodule sync
|
|
44
|
-
- run: git submodule update --init
|
|
58
|
+
- checkout-submodules
|
|
45
59
|
- run: make sync push
|
|
46
60
|
resource_class: arm.large
|
|
47
61
|
version:
|
|
48
|
-
|
|
49
|
-
- image: alexfalkowski/release:8.15
|
|
62
|
+
executor: release
|
|
50
63
|
working_directory: ~/nonnative
|
|
51
64
|
steps:
|
|
52
|
-
- checkout
|
|
53
|
-
method: blobless
|
|
54
|
-
- run: git submodule sync
|
|
55
|
-
- run: git submodule update --init
|
|
65
|
+
- checkout-submodules
|
|
56
66
|
- run: version
|
|
57
67
|
- run: package
|
|
58
68
|
resource_class: arm.large
|
|
59
69
|
wait-all:
|
|
60
|
-
|
|
61
|
-
|
|
70
|
+
executor: alpine
|
|
71
|
+
resource_class: small
|
|
62
72
|
steps:
|
|
63
73
|
- run: echo "all applicable jobs finished"
|
|
64
|
-
resource_class: arm.large
|
|
65
74
|
|
|
66
75
|
workflows:
|
|
67
76
|
nonnative:
|
data/.gitignore
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
/.bundle/
|
|
2
|
+
.crush/
|
|
3
|
+
.ruby-lsp/
|
|
4
|
+
.source-key
|
|
2
5
|
/.yardoc
|
|
3
6
|
/_yardoc/
|
|
4
7
|
/doc/
|
|
5
8
|
/pkg/
|
|
9
|
+
ISSUES.md
|
|
10
|
+
TESTS.md
|
|
11
|
+
DOCS.md
|
|
12
|
+
FEATURES.md
|
|
13
|
+
PROJECTS.md
|
|
14
|
+
RELIABILITY.md
|
|
6
15
|
test/reports/*
|
|
16
|
+
!test/reports/.keep
|
|
7
17
|
/tmp/
|
|
8
|
-
vendor
|
|
9
|
-
ISSUES.md
|
|
18
|
+
vendor/
|
data/AGENTS.md
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# AGENTS.md
|
|
2
2
|
|
|
3
|
-
## Shared
|
|
3
|
+
## Shared guidance
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
`bin/AGENTS.md` for the canonical shared skill list and use the smallest
|
|
7
|
-
matching skill for the task.
|
|
5
|
+
Use `bin/AGENTS.md` for shared skills and cross-repository defaults.
|
|
8
6
|
|
|
9
7
|
`nonnative` is a Ruby gem for end-to-end testing systems implemented in other
|
|
10
8
|
languages. It starts processes, in-process servers, and proxy-only services,
|
|
@@ -18,18 +16,16 @@ of dependencies.
|
|
|
18
16
|
- Generated gRPC test stubs: `test/grpc/**/*`
|
|
19
17
|
- Test protos: `test/nonnative/v1/*.proto`
|
|
20
18
|
- Build wiring: root `Makefile` includes `bin/build/make/*.mak`
|
|
21
|
-
- Required submodule: `bin
|
|
19
|
+
- Required submodule: `bin/`; missing submodule setup breaks `make`
|
|
22
20
|
- Install deps: `make dep`
|
|
23
21
|
- Lint: `make lint`
|
|
22
|
+
- Security checks: `make sec`
|
|
24
23
|
- Features: `make features`
|
|
25
24
|
- Benchmarks only: `make benchmarks`
|
|
26
25
|
- Cleanup: `make clean-dep`, `make clean-reports`
|
|
27
26
|
|
|
28
27
|
## Intentional Design Choices
|
|
29
28
|
|
|
30
|
-
- Root `make` compatibility is GNU Make 4+/`gmake` oriented through the shared
|
|
31
|
-
`bin/build/make/*.mak` fragments. Do not flag GNU Make 3.81 parsing failures
|
|
32
|
-
as code issues unless the task is explicitly about supporting GNU Make 3.81.
|
|
33
29
|
- The configured HTTP proxy feature intentionally uses the external
|
|
34
30
|
`www.afalkowski.com` host through `features/support/http_proxy_server.rb`.
|
|
35
31
|
Do not flag this external dependency as a code issue unless the task is
|
|
@@ -54,6 +50,10 @@ of dependencies.
|
|
|
54
50
|
test proto changes. Do not flag the absence of an automatic generated-stub
|
|
55
51
|
freshness check as a test gap unless the task is explicitly about changing
|
|
56
52
|
test proto generation or generated-file validation.
|
|
53
|
+
- Generated gRPC test stubs may carry manual require-path adjustments after
|
|
54
|
+
generation. Do not treat `make -C test stale` as a required validation target
|
|
55
|
+
unless the task is explicitly about changing test proto generation; verify
|
|
56
|
+
the generated Ruby loads before accepting generator-only rewrites.
|
|
57
57
|
- Buf linting for the test-only proto module is intentionally not part of the
|
|
58
58
|
required local validation surface. Do not flag missing root-level validation
|
|
59
59
|
for `test/buf.yaml` as a test gap unless the task is explicitly about test
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
nonnative (3.
|
|
4
|
+
nonnative (3.9.0)
|
|
5
5
|
concurrent-ruby (>= 1, < 2)
|
|
6
6
|
config (>= 5, < 6)
|
|
7
7
|
cucumber (>= 7, < 12)
|
|
@@ -26,7 +26,7 @@ GEM
|
|
|
26
26
|
benchmark-trend (0.4.0)
|
|
27
27
|
bigdecimal (4.1.2)
|
|
28
28
|
builder (3.3.0)
|
|
29
|
-
concurrent-ruby (1.3.
|
|
29
|
+
concurrent-ruby (1.3.7)
|
|
30
30
|
config (5.6.1)
|
|
31
31
|
deep_merge (~> 1.2, >= 1.2.1)
|
|
32
32
|
ostruct
|
|
@@ -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.35.
|
|
67
|
+
google-protobuf (4.35.1-x86_64-darwin)
|
|
68
68
|
bigdecimal
|
|
69
69
|
rake (~> 13.3)
|
|
70
|
-
google-protobuf (4.35.
|
|
70
|
+
google-protobuf (4.35.1-x86_64-linux-gnu)
|
|
71
71
|
bigdecimal
|
|
72
72
|
rake (~> 13.3)
|
|
73
73
|
googleapis-common-protos-types (1.23.0)
|
|
74
74
|
google-protobuf (~> 4.26)
|
|
75
|
-
grpc (1.81.
|
|
75
|
+
grpc (1.81.1-x86_64-darwin)
|
|
76
76
|
google-protobuf (>= 3.25, < 5.0)
|
|
77
77
|
googleapis-common-protos-types (~> 1.0)
|
|
78
|
-
grpc (1.81.
|
|
78
|
+
grpc (1.81.1-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.
|
|
84
|
+
json (2.20.0)
|
|
85
85
|
language_server-protocol (3.17.0.5)
|
|
86
86
|
lint_roller (1.1.0)
|
|
87
87
|
logger (1.7.0)
|
|
@@ -114,7 +114,7 @@ GEM
|
|
|
114
114
|
rack (>= 3.0.0)
|
|
115
115
|
rainbow (3.1.1)
|
|
116
116
|
rake (13.4.2)
|
|
117
|
-
rbs (4.0.
|
|
117
|
+
rbs (4.0.3)
|
|
118
118
|
logger
|
|
119
119
|
prism (>= 1.6.0)
|
|
120
120
|
tsort
|
|
@@ -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.88.0)
|
|
150
150
|
json (~> 2.3)
|
|
151
151
|
language_server-protocol (~> 3.17.0.2)
|
|
152
152
|
lint_roller (~> 1.1.0)
|
|
@@ -169,7 +169,7 @@ GEM
|
|
|
169
169
|
docile (~> 1.1)
|
|
170
170
|
simplecov-html (~> 0.11)
|
|
171
171
|
simplecov_json_formatter (~> 0.1)
|
|
172
|
-
simplecov-cobertura (3.
|
|
172
|
+
simplecov-cobertura (3.2.0)
|
|
173
173
|
rexml
|
|
174
174
|
simplecov (~> 0.19)
|
|
175
175
|
simplecov-html (0.13.2)
|
data/README.md
CHANGED
|
@@ -62,17 +62,20 @@ Common runner fields:
|
|
|
62
62
|
|
|
63
63
|
Process/server fields:
|
|
64
64
|
- `ports`: client-facing ports. These are also used for readiness/shutdown port checks.
|
|
65
|
-
- `timeout`: max time (seconds) for readiness/shutdown port checks.
|
|
65
|
+
- `timeout`: max time (seconds) for readiness/shutdown port checks. Defaults to `1.0`.
|
|
66
66
|
- `wait`: small sleep (seconds) between lifecycle steps.
|
|
67
67
|
- `log`: per-runner log file used by process output redirection or server implementations.
|
|
68
68
|
|
|
69
|
+
Process-only fields:
|
|
70
|
+
- `readiness`: optional HTTP startup readiness check with explicit `port` and `path`.
|
|
71
|
+
|
|
69
72
|
Service fields:
|
|
70
73
|
- `port`: client-facing service port. Services do not get TCP readiness/shutdown checks from Nonnative.
|
|
71
74
|
|
|
72
|
-
Nonnative readiness and shutdown checks are TCP
|
|
75
|
+
Nonnative readiness and shutdown checks are TCP port checks by default. Configure process/server ports that are dedicated to the test run; if another process is already listening on the same endpoint, results are undefined. Processes can also opt into an HTTP readiness check that runs after TCP readiness succeeds.
|
|
73
76
|
|
|
74
77
|
> [!WARNING]
|
|
75
|
-
>
|
|
78
|
+
> TCP readiness and shutdown checks only prove that a TCP port opened or closed. HTTP readiness is process-only, checks for a 2xx response, and does not verify gRPC health, schema readiness, migrations, or other application-specific health.
|
|
76
79
|
|
|
77
80
|
Start and stop Nonnative around the test scope that should own the configured runners:
|
|
78
81
|
|
|
@@ -164,12 +167,13 @@ Nonnative.configure do |config|
|
|
|
164
167
|
|
|
165
168
|
config.process do |p|
|
|
166
169
|
p.name = 'start_1'
|
|
167
|
-
p.command = -> { ['
|
|
170
|
+
p.command = -> { ['bin/start-test-service', '12_321'] }
|
|
168
171
|
p.timeout = 5
|
|
169
172
|
p.wait = 0.1
|
|
170
173
|
p.ports = [12_321]
|
|
171
174
|
p.log = '12_321.log'
|
|
172
175
|
p.signal = 'INT' # Possible values are described in Signal.list.keys.
|
|
176
|
+
p.readiness = { port: 12_321, path: '/test/readyz' }
|
|
173
177
|
p.environment = { # Pass environment variables to process.
|
|
174
178
|
'TEST' => 'true'
|
|
175
179
|
}
|
|
@@ -177,7 +181,7 @@ Nonnative.configure do |config|
|
|
|
177
181
|
|
|
178
182
|
config.process do |p|
|
|
179
183
|
p.name = 'start_2'
|
|
180
|
-
p.command = -> { ['
|
|
184
|
+
p.command = -> { ['bin/start-test-service', '12_322'] }
|
|
181
185
|
p.timeout = 0.5
|
|
182
186
|
p.wait = 0.1
|
|
183
187
|
p.ports = [12_322]
|
|
@@ -197,7 +201,7 @@ processes:
|
|
|
197
201
|
-
|
|
198
202
|
name: start_1
|
|
199
203
|
command:
|
|
200
|
-
-
|
|
204
|
+
- bin/start-test-service
|
|
201
205
|
- "12_321"
|
|
202
206
|
timeout: 5
|
|
203
207
|
wait: 1
|
|
@@ -205,12 +209,15 @@ processes:
|
|
|
205
209
|
- 12321
|
|
206
210
|
log: 12_321.log
|
|
207
211
|
signal: INT # Possible values are described in Signal.list.keys.
|
|
212
|
+
readiness:
|
|
213
|
+
port: 12321
|
|
214
|
+
path: /test/readyz
|
|
208
215
|
environment: # Pass environment variables to process.
|
|
209
216
|
TEST: true
|
|
210
217
|
-
|
|
211
218
|
name: start_2
|
|
212
219
|
command:
|
|
213
|
-
-
|
|
220
|
+
- bin/start-test-service
|
|
214
221
|
- "12_322"
|
|
215
222
|
timeout: 5
|
|
216
223
|
wait: 1
|
|
@@ -474,6 +481,8 @@ end
|
|
|
474
481
|
|
|
475
482
|
Define your server:
|
|
476
483
|
|
|
484
|
+
Assume the gRPC service base class and response types below come from your generated gRPC stubs.
|
|
485
|
+
|
|
477
486
|
```ruby
|
|
478
487
|
module Nonnative
|
|
479
488
|
module Features
|
|
@@ -530,6 +539,9 @@ servers:
|
|
|
530
539
|
log: grpc_server_1.log
|
|
531
540
|
```
|
|
532
541
|
|
|
542
|
+
The `grpc` gem uses a global logger, so per-server gRPC log files are not independent. The first
|
|
543
|
+
initialized gRPC server sets the logger used by later gRPC servers in the same Ruby process.
|
|
544
|
+
|
|
533
545
|
Then load the file with:
|
|
534
546
|
|
|
535
547
|
```ruby
|
|
@@ -607,7 +619,7 @@ These proxies can simulate different situations. Available proxy kinds are:
|
|
|
607
619
|
- `fault_injection`
|
|
608
620
|
|
|
609
621
|
> [!WARNING]
|
|
610
|
-
> Unknown proxy kinds
|
|
622
|
+
> Unknown proxy kinds raise an error. If fault injection is not taking effect, check the `kind` spelling or register the custom kind before starting the system.
|
|
611
623
|
|
|
612
624
|
Custom proxy kinds can be registered through `Nonnative.proxies`:
|
|
613
625
|
|
|
@@ -22,6 +22,7 @@ module Nonnative
|
|
|
22
22
|
# p.ports = [8080, 9090]
|
|
23
23
|
# p.timeout = 10
|
|
24
24
|
# p.log = 'api.log'
|
|
25
|
+
# p.readiness = { port: 8080, path: '/example/readyz' }
|
|
25
26
|
# end
|
|
26
27
|
# end
|
|
27
28
|
#
|
|
@@ -42,13 +43,25 @@ module Nonnative
|
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
# @return [String, nil] logical system name (used for observability endpoints)
|
|
46
|
+
attr_accessor :name
|
|
47
|
+
|
|
45
48
|
# @return [String, nil] configuration version
|
|
49
|
+
attr_accessor :version
|
|
50
|
+
|
|
46
51
|
# @return [String, nil] base URL for observability queries (for example `"http://127.0.0.1:8080"`)
|
|
52
|
+
attr_accessor :url
|
|
53
|
+
|
|
47
54
|
# @return [String, nil] path to the Nonnative log file
|
|
55
|
+
attr_accessor :log
|
|
56
|
+
|
|
48
57
|
# @return [Array<Nonnative::ConfigurationProcess>] configured processes
|
|
58
|
+
attr_accessor :processes
|
|
59
|
+
|
|
49
60
|
# @return [Array<Nonnative::ConfigurationServer>] configured in-process servers
|
|
61
|
+
attr_accessor :servers
|
|
62
|
+
|
|
50
63
|
# @return [Array<Nonnative::ConfigurationService>] configured services (proxy-only)
|
|
51
|
-
attr_accessor :
|
|
64
|
+
attr_accessor :services
|
|
52
65
|
|
|
53
66
|
# Loads a configuration file and appends its runners to this instance.
|
|
54
67
|
#
|
|
@@ -130,6 +143,7 @@ module Nonnative
|
|
|
130
143
|
process_config.command = command(loaded_process)
|
|
131
144
|
process_config.signal = loaded_process.signal
|
|
132
145
|
process_config.environment = loaded_process.environment
|
|
146
|
+
process_config.readiness = loaded_process.readiness if loaded_process.readiness
|
|
133
147
|
runner_attributes(process_config, loaded_process)
|
|
134
148
|
end
|
|
135
149
|
end
|
|
@@ -151,6 +165,7 @@ module Nonnative
|
|
|
151
165
|
servers = cfg.servers || []
|
|
152
166
|
servers.each do |loaded_server|
|
|
153
167
|
reject_proxy(loaded_server, 'servers')
|
|
168
|
+
reject_readiness(loaded_server, 'servers')
|
|
154
169
|
|
|
155
170
|
server do |server_config|
|
|
156
171
|
server_config.klass = Object.const_get(server_class_name(loaded_server))
|
|
@@ -162,6 +177,8 @@ module Nonnative
|
|
|
162
177
|
def add_services(cfg)
|
|
163
178
|
services = cfg.services || []
|
|
164
179
|
services.each do |loaded_service|
|
|
180
|
+
reject_readiness(loaded_service, 'services')
|
|
181
|
+
|
|
165
182
|
service do |service_config|
|
|
166
183
|
service_config.name = loaded_service.name
|
|
167
184
|
service_config.host = loaded_service.host if loaded_service.host
|
|
@@ -179,7 +196,8 @@ module Nonnative
|
|
|
179
196
|
|
|
180
197
|
def runner_attributes(runner, loaded)
|
|
181
198
|
runner.name = loaded.name
|
|
182
|
-
|
|
199
|
+
timeout = loaded.timeout
|
|
200
|
+
runner.timeout = timeout unless timeout.nil?
|
|
183
201
|
runner.wait = loaded.wait if loaded.wait
|
|
184
202
|
runner.host = loaded.host if loaded.host
|
|
185
203
|
assign_ports(runner, loaded)
|
|
@@ -207,6 +225,13 @@ module Nonnative
|
|
|
207
225
|
raise ArgumentError, "Use 'services' for proxy configuration; #{kind} do not support 'proxy'"
|
|
208
226
|
end
|
|
209
227
|
|
|
228
|
+
def reject_readiness(loaded, kind)
|
|
229
|
+
values = loaded.to_h
|
|
230
|
+
return unless values.key?(:readiness) || values.key?('readiness')
|
|
231
|
+
|
|
232
|
+
raise ArgumentError, "Use 'processes' for readiness configuration; #{kind} do not support 'readiness'"
|
|
233
|
+
end
|
|
234
|
+
|
|
210
235
|
def assign_proxy(service, loaded_proxy)
|
|
211
236
|
return unless loaded_proxy
|
|
212
237
|
|
|
@@ -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 ports to open/close
|
|
22
|
+
# @return [Numeric] readiness timeout (seconds) used when waiting for ports to open/close (defaults to `1.0`)
|
|
23
23
|
attr_accessor :timeout
|
|
24
24
|
|
|
25
25
|
# @return [String] log file path to append process stdout/stderr to
|
|
@@ -27,5 +27,28 @@ module Nonnative
|
|
|
27
27
|
|
|
28
28
|
# @return [Hash, nil] environment variables to pass to the spawned process
|
|
29
29
|
attr_accessor :environment
|
|
30
|
+
|
|
31
|
+
# @return [Nonnative::ConfigurationReadiness, nil] optional HTTP readiness check
|
|
32
|
+
attr_reader :readiness
|
|
33
|
+
|
|
34
|
+
# Creates a process configuration with bounded lifecycle defaults.
|
|
35
|
+
#
|
|
36
|
+
# Defaults:
|
|
37
|
+
# - `timeout`: `1.0`
|
|
38
|
+
#
|
|
39
|
+
# @return [void]
|
|
40
|
+
def initialize
|
|
41
|
+
super
|
|
42
|
+
|
|
43
|
+
self.timeout = DEFAULT_TIMEOUT
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Sets optional HTTP readiness configuration.
|
|
47
|
+
#
|
|
48
|
+
# @param value [Hash, #to_h, nil] readiness attributes with required `port` and `path`
|
|
49
|
+
# @return [void]
|
|
50
|
+
def readiness=(value)
|
|
51
|
+
@readiness = value.nil? ? nil : Nonnative::ConfigurationReadiness.new(value)
|
|
52
|
+
end
|
|
30
53
|
end
|
|
31
54
|
end
|
|
@@ -15,12 +15,21 @@ module Nonnative
|
|
|
15
15
|
# @see Nonnative.proxies
|
|
16
16
|
class ConfigurationProxy
|
|
17
17
|
# @return [String] proxy kind name (for example `"none"` or `"fault_injection"`)
|
|
18
|
+
attr_accessor :kind
|
|
19
|
+
|
|
18
20
|
# @return [String] upstream host used by proxy implementations (defaults to `"127.0.0.1"`)
|
|
21
|
+
attr_accessor :host
|
|
22
|
+
|
|
19
23
|
# @return [Integer] upstream port used by proxy implementations (defaults to `0`)
|
|
24
|
+
attr_accessor :port
|
|
25
|
+
|
|
20
26
|
# @return [String, nil] path to proxy log file (implementation-dependent)
|
|
27
|
+
attr_accessor :log
|
|
28
|
+
|
|
21
29
|
# @return [Numeric] wait interval (seconds) after proxy state changes (defaults to `0.1`)
|
|
30
|
+
attr_accessor :wait
|
|
31
|
+
|
|
22
32
|
# @return [Hash] proxy implementation options (implementation-dependent)
|
|
23
|
-
attr_accessor :kind, :host, :port, :log, :wait
|
|
24
33
|
attr_reader :options
|
|
25
34
|
|
|
26
35
|
# Creates a proxy configuration with defaults.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# HTTP readiness configuration for a managed process.
|
|
5
|
+
#
|
|
6
|
+
# Readiness is optional. When present, both `port` and `path` are required so the startup
|
|
7
|
+
# check has an explicit application endpoint to poll after TCP readiness succeeds.
|
|
8
|
+
class ConfigurationReadiness
|
|
9
|
+
# @return [Integer] process HTTP readiness port
|
|
10
|
+
attr_accessor :port
|
|
11
|
+
|
|
12
|
+
# @return [String] HTTP readiness path
|
|
13
|
+
attr_accessor :path
|
|
14
|
+
|
|
15
|
+
# @param value [Hash, #to_h] readiness attributes
|
|
16
|
+
def initialize(value)
|
|
17
|
+
attributes = value.respond_to?(:to_h) ? value.to_h : value
|
|
18
|
+
self.port = attribute(attributes, :port)
|
|
19
|
+
self.path = attribute(attributes, :path)
|
|
20
|
+
|
|
21
|
+
validate!
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def attribute(attributes, name)
|
|
27
|
+
attributes[name] || attributes[name.to_s]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def validate!
|
|
31
|
+
raise ArgumentError, "Process readiness requires 'port'" if port.nil?
|
|
32
|
+
raise ArgumentError, "Process readiness requires 'path'" if path.nil?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -12,11 +12,17 @@ module Nonnative
|
|
|
12
12
|
# @see Nonnative::ConfigurationServer
|
|
13
13
|
# @see Nonnative::ConfigurationService
|
|
14
14
|
class ConfigurationRunner
|
|
15
|
+
# Default bounded readiness/shutdown timeout for process and server runners.
|
|
16
|
+
DEFAULT_TIMEOUT = 1.0
|
|
17
|
+
|
|
15
18
|
# @return [String, nil] runner name used for lookup (for example via `pool.process_by_name`)
|
|
19
|
+
attr_accessor :name
|
|
20
|
+
|
|
16
21
|
# @return [String] host to bind/connect to (defaults to `"127.0.0.1"`)
|
|
17
|
-
|
|
22
|
+
attr_accessor :host
|
|
23
|
+
|
|
18
24
|
# @return [Numeric] wait interval (seconds) used by runners between lifecycle steps
|
|
19
|
-
attr_accessor :
|
|
25
|
+
attr_accessor :wait
|
|
20
26
|
|
|
21
27
|
# @return [Array<Integer>] client-facing ports used for readiness/shutdown checks
|
|
22
28
|
attr_reader :ports
|
|
@@ -11,9 +11,25 @@ module Nonnative
|
|
|
11
11
|
# @see Nonnative::Configuration
|
|
12
12
|
# @see Nonnative::Server
|
|
13
13
|
class ConfigurationServer < ConfigurationRunner
|
|
14
|
-
# @return [Class] a class that implements `#initialize(service)
|
|
15
|
-
|
|
14
|
+
# @return [Class] a class that implements `#initialize(service)` and the hooks expected by {Nonnative::Server}
|
|
15
|
+
attr_accessor :klass
|
|
16
|
+
|
|
17
|
+
# @return [Numeric] readiness timeout (seconds) used when waiting for ports to open/close (defaults to `1.0`)
|
|
18
|
+
attr_accessor :timeout
|
|
19
|
+
|
|
16
20
|
# @return [String] log file path used by server implementations (for example Puma/gRPC log files)
|
|
17
|
-
attr_accessor :
|
|
21
|
+
attr_accessor :log
|
|
22
|
+
|
|
23
|
+
# Creates a server configuration with bounded lifecycle defaults.
|
|
24
|
+
#
|
|
25
|
+
# Defaults:
|
|
26
|
+
# - `timeout`: `1.0`
|
|
27
|
+
#
|
|
28
|
+
# @return [void]
|
|
29
|
+
def initialize
|
|
30
|
+
super
|
|
31
|
+
|
|
32
|
+
self.timeout = DEFAULT_TIMEOUT
|
|
33
|
+
end
|
|
18
34
|
end
|
|
19
35
|
end
|
data/lib/nonnative/cucumber.rb
CHANGED
|
@@ -8,6 +8,26 @@ module Nonnative
|
|
|
8
8
|
# Requiring `nonnative` outside a running Cucumber environment should not fail, but when Cucumber
|
|
9
9
|
# does finish booting its support-code registry this installer still needs to register the hooks
|
|
10
10
|
# and step definitions defined here.
|
|
11
|
+
#
|
|
12
|
+
# Supported hooks:
|
|
13
|
+
# - `@startup`: start before scenario, stop after scenario
|
|
14
|
+
# - `@manual`: stop after scenario; use `When I start the system` to start manually
|
|
15
|
+
# - `@clear`: clear memoized Nonnative state before scenario
|
|
16
|
+
# - `@reset`: reset proxies after scenario
|
|
17
|
+
#
|
|
18
|
+
# Installed step definitions:
|
|
19
|
+
# - `Given I set the proxy for service {string} to {string}`
|
|
20
|
+
# - `Then I should reset the proxy for service {string}`
|
|
21
|
+
# - `When I start the system`
|
|
22
|
+
# - `When I attempt to start the system`
|
|
23
|
+
# - `When I attempt to stop the system`
|
|
24
|
+
# - `Then I should see {string} as unhealthy`
|
|
25
|
+
# - `Then I should see {string} as healthy`
|
|
26
|
+
# - `Then the process {string} should consume less than {string} of memory`
|
|
27
|
+
# - `Then starting the system should raise an error`
|
|
28
|
+
# - `Then stopping the system should raise an error`
|
|
29
|
+
# - `Then I should see a log entry of {string} for process {string}`
|
|
30
|
+
# - `Then I should see a log entry of {string} in the file {string}`
|
|
11
31
|
module Cucumber
|
|
12
32
|
module LanguageHook
|
|
13
33
|
def rb_language=(value)
|
|
@@ -11,7 +11,7 @@ module Nonnative
|
|
|
11
11
|
#
|
|
12
12
|
# - {#close_all}: close connections immediately on accept
|
|
13
13
|
# - {#delay}: delay reads by a configured duration (default: 2 seconds)
|
|
14
|
-
# - {#invalid_data}:
|
|
14
|
+
# - {#invalid_data}: forward requests unchanged and mutate upstream responses before they reach clients
|
|
15
15
|
# - {#reset}: return to healthy pass-through behavior
|
|
16
16
|
#
|
|
17
17
|
# State changes terminate any active connections so new connections observe the new behavior.
|
|
@@ -107,7 +107,7 @@ module Nonnative
|
|
|
107
107
|
apply_state :delay
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
-
#
|
|
110
|
+
# Mutates upstream responses while forwarding client requests unchanged.
|
|
111
111
|
#
|
|
112
112
|
# @return [void]
|
|
113
113
|
def invalid_data
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Probes a managed process HTTP readiness endpoint.
|
|
5
|
+
class HTTPProbe < Nonnative::HTTPClient
|
|
6
|
+
NETWORK_ERRORS = [
|
|
7
|
+
Errno::ECONNREFUSED,
|
|
8
|
+
Errno::EHOSTUNREACH,
|
|
9
|
+
Errno::ECONNRESET,
|
|
10
|
+
SocketError,
|
|
11
|
+
RestClient::Exceptions::Timeout,
|
|
12
|
+
RestClient::ServerBrokeConnection
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
# @param process [Nonnative::ConfigurationProcess] process configuration with readiness attributes
|
|
16
|
+
def initialize(process)
|
|
17
|
+
@readiness = process.readiness
|
|
18
|
+
@base_url = "http://#{process.host}:#{readiness.port}"
|
|
19
|
+
@timeout = Nonnative::Timeout.new(process.timeout)
|
|
20
|
+
|
|
21
|
+
super(base_url)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns whether the configured HTTP endpoint returns a 2xx response before timeout.
|
|
25
|
+
#
|
|
26
|
+
# @return [Boolean]
|
|
27
|
+
def ready?
|
|
28
|
+
Nonnative.logger.info "checking if readiness '#{endpoint}' is ready"
|
|
29
|
+
|
|
30
|
+
timeout.perform do
|
|
31
|
+
response = get(readiness.path)
|
|
32
|
+
raise Nonnative::Error unless ready_response?(response)
|
|
33
|
+
|
|
34
|
+
true
|
|
35
|
+
rescue Nonnative::Error, *NETWORK_ERRORS
|
|
36
|
+
sleep_interval
|
|
37
|
+
retry
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns the HTTP readiness endpoint for lifecycle diagnostics.
|
|
42
|
+
#
|
|
43
|
+
# @return [String]
|
|
44
|
+
def endpoint
|
|
45
|
+
"#{base_url}#{path}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
attr_reader :base_url, :readiness, :timeout
|
|
51
|
+
|
|
52
|
+
def path
|
|
53
|
+
readiness.path.start_with?('/') ? readiness.path : "/#{readiness.path}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def ready_response?(response)
|
|
57
|
+
response.respond_to?(:code) && response.code.to_i.between?(200, 299)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def sleep_interval
|
|
61
|
+
sleep 0.01
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -23,6 +23,14 @@ module Nonnative
|
|
|
23
23
|
#
|
|
24
24
|
# Supported HTTP verbs: GET, POST, PUT, PATCH, DELETE.
|
|
25
25
|
class HTTPProxy < Sinatra::Application
|
|
26
|
+
NON_FORWARDABLE_HEADERS = %w[
|
|
27
|
+
Host
|
|
28
|
+
Accept-Encoding
|
|
29
|
+
Version
|
|
30
|
+
Proxy-Authenticate
|
|
31
|
+
Proxy-Authorization
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
26
34
|
# Extracts request headers from the Rack environment and normalizes them to standard HTTP names.
|
|
27
35
|
#
|
|
28
36
|
# Certain hop-by-hop or proxy-specific headers are removed.
|
|
@@ -36,7 +44,7 @@ module Nonnative
|
|
|
36
44
|
result[normalized_header_name(header)] = value
|
|
37
45
|
end
|
|
38
46
|
|
|
39
|
-
headers.except(
|
|
47
|
+
headers.except(*NON_FORWARDABLE_HEADERS)
|
|
40
48
|
end
|
|
41
49
|
|
|
42
50
|
# Builds the upstream URL for the given request.
|
|
@@ -9,14 +9,20 @@ module Nonnative
|
|
|
9
9
|
# The server is started and stopped by {Nonnative::Server} via {#perform_start} / {#perform_stop}.
|
|
10
10
|
#
|
|
11
11
|
# @example Running a Sinatra app
|
|
12
|
-
#
|
|
13
|
-
#
|
|
12
|
+
# class HelloHTTPServer < Nonnative::HTTPServer
|
|
13
|
+
# def initialize(service)
|
|
14
|
+
# app = Sinatra.new do
|
|
15
|
+
# get('/hello') { 'Hello World!' }
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# super(app, service)
|
|
19
|
+
# end
|
|
14
20
|
# end
|
|
15
21
|
#
|
|
16
22
|
# Nonnative.configure do |config|
|
|
17
23
|
# config.server do |s|
|
|
18
24
|
# s.name = 'http'
|
|
19
|
-
# s.klass =
|
|
25
|
+
# s.klass = HelloHTTPServer
|
|
20
26
|
# s.timeout = 2
|
|
21
27
|
# s.host = '127.0.0.1'
|
|
22
28
|
# s.ports = [4567]
|
|
@@ -24,7 +30,7 @@ module Nonnative
|
|
|
24
30
|
# end
|
|
25
31
|
# end
|
|
26
32
|
#
|
|
27
|
-
#
|
|
33
|
+
# YAML configuration uses the same concrete subclass name in its `class` field.
|
|
28
34
|
#
|
|
29
35
|
# @see Nonnative::Server
|
|
30
36
|
class HTTPServer < Nonnative::Server
|
|
@@ -33,7 +39,8 @@ module Nonnative
|
|
|
33
39
|
# @param app [#call] a Rack-compatible application (e.g. Sinatra/Rack app)
|
|
34
40
|
# @param service [Nonnative::ConfigurationServer] server configuration
|
|
35
41
|
def initialize(app, service)
|
|
36
|
-
|
|
42
|
+
# Keep the log IO so the server lifecycle can release Puma's file handle on stop.
|
|
43
|
+
@log = File.open(service.log, 'a')
|
|
37
44
|
options = {
|
|
38
45
|
log_writer: Puma::LogWriter.new(log, log),
|
|
39
46
|
force_shutdown_after: service.timeout
|
|
@@ -60,10 +67,16 @@ module Nonnative
|
|
|
60
67
|
# @return [void]
|
|
61
68
|
def perform_stop
|
|
62
69
|
server.graceful_shutdown
|
|
70
|
+
ensure
|
|
71
|
+
close_log
|
|
63
72
|
end
|
|
64
73
|
|
|
65
74
|
private
|
|
66
75
|
|
|
67
|
-
attr_reader :server
|
|
76
|
+
attr_reader :server, :log
|
|
77
|
+
|
|
78
|
+
def close_log
|
|
79
|
+
log.close unless log.closed?
|
|
80
|
+
end
|
|
68
81
|
end
|
|
69
82
|
end
|
data/lib/nonnative/no_proxy.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Nonnative
|
|
4
4
|
# No-op proxy implementation.
|
|
5
5
|
#
|
|
6
|
-
# This is the default proxy when `service.proxy.kind` is `"none"
|
|
6
|
+
# This is the default proxy when `service.proxy.kind` is `"none"`.
|
|
7
7
|
# It does not bind/listen or alter traffic; it simply exposes the underlying runner's configured
|
|
8
8
|
# `host` and primary `port`.
|
|
9
9
|
#
|
data/lib/nonnative/pool.rb
CHANGED
|
@@ -30,6 +30,7 @@ module Nonnative
|
|
|
30
30
|
# @yieldparam name [String, nil] runner name
|
|
31
31
|
# @yieldparam values [Object] runner-specific return value from `start` (e.g. `[pid, running]` for processes)
|
|
32
32
|
# @yieldparam result [Boolean] result of the port readiness check (`true` if ready in time)
|
|
33
|
+
# @yieldparam port [Nonnative::Ports] checked port group
|
|
33
34
|
# @return [Array<String>] lifecycle and readiness-check errors collected while starting
|
|
34
35
|
def start(&)
|
|
35
36
|
errors = []
|
|
@@ -47,6 +48,7 @@ module Nonnative
|
|
|
47
48
|
# @yieldparam name [String, nil] runner name
|
|
48
49
|
# @yieldparam id [Object] runner-specific identifier returned by `stop` (e.g. pid or object_id)
|
|
49
50
|
# @yieldparam result [Boolean] result of the port shutdown check (`true` if closed in time)
|
|
51
|
+
# @yieldparam port [Nonnative::Ports] checked port group
|
|
50
52
|
# @return [Array<String>] lifecycle and shutdown-check errors collected while stopping
|
|
51
53
|
def stop(&)
|
|
52
54
|
errors = []
|
|
@@ -65,6 +67,7 @@ module Nonnative
|
|
|
65
67
|
# @yieldparam name [String, nil] runner name
|
|
66
68
|
# @yieldparam id [Object] runner-specific identifier returned by `stop`
|
|
67
69
|
# @yieldparam result [Boolean] result of the port shutdown check (`true` if closed in time)
|
|
70
|
+
# @yieldparam port [Nonnative::Ports] checked port group
|
|
68
71
|
# @return [Array<String>] lifecycle and shutdown-check errors collected while rolling back
|
|
69
72
|
def rollback(&)
|
|
70
73
|
errors = []
|
|
@@ -183,7 +186,7 @@ module Nonnative
|
|
|
183
186
|
|
|
184
187
|
runners.each do |runner, port|
|
|
185
188
|
values = runner.send(lifecycle_method)
|
|
186
|
-
checks << [runner, values, Thread.new { check_port(port, port_method) }]
|
|
189
|
+
checks << [runner, values, port, Thread.new { check_port(port, port_method) }]
|
|
187
190
|
rescue StandardError => e
|
|
188
191
|
errors << lifecycle_error(action, runner, e)
|
|
189
192
|
end
|
|
@@ -198,12 +201,12 @@ module Nonnative
|
|
|
198
201
|
end
|
|
199
202
|
|
|
200
203
|
def yield_results(checks, action, &)
|
|
201
|
-
checks.each_with_object([]) do |(type, values, thread), errors|
|
|
204
|
+
checks.each_with_object([]) do |(type, values, port, thread), errors|
|
|
202
205
|
result = thread.value
|
|
203
206
|
if result[:error]
|
|
204
207
|
errors << port_error(action, type, result[:error])
|
|
205
208
|
elsif block_given?
|
|
206
|
-
yield type.name, values, result[:result]
|
|
209
|
+
yield type.name, values, result[:result], port
|
|
207
210
|
end
|
|
208
211
|
end
|
|
209
212
|
end
|
data/lib/nonnative/port.rb
CHANGED
data/lib/nonnative/ports.rb
CHANGED
|
@@ -10,14 +10,16 @@ module Nonnative
|
|
|
10
10
|
class Ports
|
|
11
11
|
# @param runner [#host, #ports, #timeout] runner configuration providing connection details
|
|
12
12
|
def initialize(runner)
|
|
13
|
+
@runner = runner
|
|
13
14
|
@ports = runner.ports.map { |port| Nonnative::Port.new(runner, port) }
|
|
15
|
+
@readiness = Nonnative::HTTPProbe.new(runner) if runner.respond_to?(:readiness) && runner.readiness
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
# Returns whether all configured ports become connectable before their timeouts elapse.
|
|
17
19
|
#
|
|
18
20
|
# @return [Boolean]
|
|
19
21
|
def open?
|
|
20
|
-
ports.all?(&:open?)
|
|
22
|
+
ports.all?(&:open?) && (readiness.nil? || readiness.ready?)
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
# Returns whether all configured ports become non-connectable before their timeouts elapse.
|
|
@@ -27,8 +29,27 @@ module Nonnative
|
|
|
27
29
|
ports.all?(&:closed?)
|
|
28
30
|
end
|
|
29
31
|
|
|
32
|
+
# Returns the checked endpoints for lifecycle diagnostics.
|
|
33
|
+
#
|
|
34
|
+
# @return [String]
|
|
35
|
+
def endpoints
|
|
36
|
+
ports.map(&:endpoint).join(', ')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns endpoint and log context for lifecycle errors.
|
|
40
|
+
#
|
|
41
|
+
# @return [String]
|
|
42
|
+
def description
|
|
43
|
+
details = []
|
|
44
|
+
details << "readiness: #{readiness.endpoint}" if readiness
|
|
45
|
+
log = runner.log if runner.respond_to?(:log)
|
|
46
|
+
details << "log: #{log}" if log
|
|
47
|
+
|
|
48
|
+
details.empty? ? endpoints : "#{endpoints} (#{details.join('; ')})"
|
|
49
|
+
end
|
|
50
|
+
|
|
30
51
|
private
|
|
31
52
|
|
|
32
|
-
attr_reader :ports
|
|
53
|
+
attr_reader :ports, :readiness, :runner
|
|
33
54
|
end
|
|
34
55
|
end
|
data/lib/nonnative/process.rb
CHANGED
|
@@ -49,6 +49,7 @@ module Nonnative
|
|
|
49
49
|
if process_exists?
|
|
50
50
|
process_kill
|
|
51
51
|
stopped = wait_stop != false
|
|
52
|
+
force_stop unless stopped
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
[pid, stopped]
|
|
@@ -82,6 +83,13 @@ module Nonnative
|
|
|
82
83
|
::Process.kill(signal, pid)
|
|
83
84
|
end
|
|
84
85
|
|
|
86
|
+
def force_stop
|
|
87
|
+
::Process.kill('KILL', pid)
|
|
88
|
+
wait_stop
|
|
89
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
|
90
|
+
true
|
|
91
|
+
end
|
|
92
|
+
|
|
85
93
|
def process_spawn
|
|
86
94
|
environment = service.environment.to_h
|
|
87
95
|
environment = environment.transform_keys(&:to_s).transform_values(&:to_s)
|
data/lib/nonnative/proxy.rb
CHANGED
|
@@ -6,11 +6,14 @@ module Nonnative
|
|
|
6
6
|
# A proxy is responsible for interposing behavior between a client and a target service.
|
|
7
7
|
# Runtime services create a proxy instance via {Nonnative::ProxyFactory} based on `service.proxy.kind`.
|
|
8
8
|
#
|
|
9
|
+
# Service configuration `host` and `port` are the client-facing endpoint. Concrete proxy methods are
|
|
10
|
+
# implementation-specific; for example {Nonnative::FaultInjectionProxy#host} and
|
|
11
|
+
# {Nonnative::FaultInjectionProxy#port} return the upstream target behind the proxy.
|
|
12
|
+
#
|
|
9
13
|
# Concrete proxies typically implement these public methods:
|
|
10
14
|
# - `start`: begin proxying (bind/listen, start threads, etc)
|
|
11
15
|
# - `stop`: stop proxying and release resources
|
|
12
16
|
# - `reset`: return proxy behavior to a healthy/default state
|
|
13
|
-
# - `host` / `port`: endpoint clients should connect to when the proxy is enabled
|
|
14
17
|
#
|
|
15
18
|
# @see Nonnative::ProxyFactory
|
|
16
19
|
# @see Nonnative::NoProxy
|
|
@@ -6,7 +6,9 @@ module Nonnative
|
|
|
6
6
|
# A runtime service constructs a proxy via this factory. The proxy implementation is selected by
|
|
7
7
|
# `service.proxy.kind` and resolved using {Nonnative.proxy}.
|
|
8
8
|
#
|
|
9
|
-
# If the kind is
|
|
9
|
+
# If the kind is `"none"`, {Nonnative.proxy} returns {Nonnative::NoProxy}.
|
|
10
|
+
# Unknown non-`"none"` kinds raise an error so proxy configuration typos do not silently disable
|
|
11
|
+
# fault injection.
|
|
10
12
|
#
|
|
11
13
|
# @see Nonnative.proxy
|
|
12
14
|
# @see Nonnative.proxies
|
data/lib/nonnative/version.rb
CHANGED
data/lib/nonnative.rb
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# It can:
|
|
7
7
|
#
|
|
8
8
|
# - start external processes and in-process servers
|
|
9
|
-
# - wait for readiness via port checks
|
|
9
|
+
# - wait for readiness via port checks and optional process HTTP readiness
|
|
10
10
|
# - optionally run fault-injection proxies in front of services
|
|
11
11
|
#
|
|
12
12
|
# The public entry points are exposed as module-level methods on {Nonnative}.
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
# p.ports = [8080, 9090]
|
|
28
28
|
# p.timeout = 10
|
|
29
29
|
# p.log = 'api.log'
|
|
30
|
+
# p.readiness = { port: 8080, path: '/example/readyz' }
|
|
30
31
|
# end
|
|
31
32
|
# end
|
|
32
33
|
#
|
|
@@ -36,10 +37,10 @@
|
|
|
36
37
|
# # run tests...
|
|
37
38
|
# Nonnative.stop
|
|
38
39
|
#
|
|
39
|
-
# ==
|
|
40
|
+
# == Cucumber integration
|
|
40
41
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
42
|
+
# Requiring `nonnative` loads the lazy Cucumber integration. It is safe outside a booted Cucumber
|
|
43
|
+
# runtime; hooks and step definitions are installed once Cucumber's Ruby DSL is ready.
|
|
43
44
|
#
|
|
44
45
|
require 'socket'
|
|
45
46
|
require 'timeout'
|
|
@@ -72,6 +73,7 @@ require 'nonnative/ports'
|
|
|
72
73
|
require 'nonnative/configuration_file'
|
|
73
74
|
require 'nonnative/configuration'
|
|
74
75
|
require 'nonnative/configuration_runner'
|
|
76
|
+
require 'nonnative/configuration_readiness'
|
|
75
77
|
require 'nonnative/configuration_process'
|
|
76
78
|
require 'nonnative/configuration_server'
|
|
77
79
|
require 'nonnative/configuration_service'
|
|
@@ -82,6 +84,7 @@ require 'nonnative/server'
|
|
|
82
84
|
require 'nonnative/service'
|
|
83
85
|
require 'nonnative/pool'
|
|
84
86
|
require 'nonnative/http_client'
|
|
87
|
+
require 'nonnative/http_probe'
|
|
85
88
|
require 'nonnative/http_server'
|
|
86
89
|
require 'nonnative/http_proxy_server'
|
|
87
90
|
require 'nonnative/grpc_server'
|
|
@@ -204,7 +207,7 @@ module Nonnative
|
|
|
204
207
|
# @param kind [String] proxy kind name (for example `"fault_injection"`)
|
|
205
208
|
# @return [Class] a subclass of {Nonnative::Proxy}
|
|
206
209
|
def proxy(kind)
|
|
207
|
-
|
|
210
|
+
kind.nil? || kind == 'none' ? NoProxy : proxies.fetch(kind) { raise ArgumentError, "Unsupported proxy kind '#{kind}'" }
|
|
208
211
|
end
|
|
209
212
|
|
|
210
213
|
# Starts all configured services, servers, and processes, and waits for readiness.
|
|
@@ -216,9 +219,9 @@ module Nonnative
|
|
|
216
219
|
def start
|
|
217
220
|
@pool ||= Nonnative::Pool.new(configuration)
|
|
218
221
|
errors = []
|
|
219
|
-
errors.concat(@pool.start do |name, values, result|
|
|
222
|
+
errors.concat(@pool.start do |name, values, result, ports|
|
|
220
223
|
id, started = values
|
|
221
|
-
errors << "Started #{name} with id #{id}, though did not respond in time" if !started || !result
|
|
224
|
+
errors << "Started #{name} with id #{id}, though did not respond in time for #{ports.description}" if !started || !result
|
|
222
225
|
end)
|
|
223
226
|
nil
|
|
224
227
|
rescue StandardError => e
|
|
@@ -239,9 +242,9 @@ module Nonnative
|
|
|
239
242
|
errors = []
|
|
240
243
|
return if @pool.nil?
|
|
241
244
|
|
|
242
|
-
errors.concat(@pool.stop do |name, values, result|
|
|
245
|
+
errors.concat(@pool.stop do |name, values, result, ports|
|
|
243
246
|
id, stopped = Array(values).then { |v| [v.first, v.fetch(1, true)] }
|
|
244
|
-
errors << "Stopped #{name} with id #{id}, though did not respond in time" unless result
|
|
247
|
+
errors << "Stopped #{name} with id #{id}, though did not respond in time for #{ports.description}" unless result
|
|
245
248
|
errors << "Stopped #{name} with id #{id}, though the process did not exit in time" unless stopped
|
|
246
249
|
end)
|
|
247
250
|
nil
|
|
@@ -305,9 +308,9 @@ module Nonnative
|
|
|
305
308
|
errors = []
|
|
306
309
|
return errors if @pool.nil?
|
|
307
310
|
|
|
308
|
-
errors.concat(@pool.rollback do |name, values, result|
|
|
311
|
+
errors.concat(@pool.rollback do |name, values, result, ports|
|
|
309
312
|
id, stopped = Array(values).then { |v| [v.first, v.fetch(1, true)] }
|
|
310
|
-
errors << "Rollback failed for #{name} with id #{id}, because it did not stop in time" unless result
|
|
313
|
+
errors << "Rollback failed for #{name} with id #{id}, because it did not stop in time for #{ports.description}" unless result
|
|
311
314
|
errors << "Rollback failed for #{name} with id #{id}, because the process did not exit in time" unless stopped
|
|
312
315
|
end)
|
|
313
316
|
rescue StandardError => e
|
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: 3.
|
|
4
|
+
version: 3.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alejandro Falkowski
|
|
@@ -291,6 +291,7 @@ files:
|
|
|
291
291
|
- lib/nonnative/configuration_file.rb
|
|
292
292
|
- lib/nonnative/configuration_process.rb
|
|
293
293
|
- lib/nonnative/configuration_proxy.rb
|
|
294
|
+
- lib/nonnative/configuration_readiness.rb
|
|
294
295
|
- lib/nonnative/configuration_runner.rb
|
|
295
296
|
- lib/nonnative/configuration_server.rb
|
|
296
297
|
- lib/nonnative/configuration_service.rb
|
|
@@ -302,6 +303,7 @@ files:
|
|
|
302
303
|
- lib/nonnative/grpc_server.rb
|
|
303
304
|
- lib/nonnative/header.rb
|
|
304
305
|
- lib/nonnative/http_client.rb
|
|
306
|
+
- lib/nonnative/http_probe.rb
|
|
305
307
|
- lib/nonnative/http_proxy_server.rb
|
|
306
308
|
- lib/nonnative/http_server.rb
|
|
307
309
|
- lib/nonnative/invalid_data_socket_pair.rb
|