nonnative 3.1.0 → 3.2.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/Gemfile.lock +1 -1
- data/README.md +32 -190
- data/lib/nonnative/configuration.rb +13 -6
- data/lib/nonnative/configuration_proxy.rb +4 -4
- data/lib/nonnative/configuration_runner.rb +2 -37
- data/lib/nonnative/configuration_service.rb +28 -0
- data/lib/nonnative/cucumber.rb +0 -20
- data/lib/nonnative/fault_injection_proxy.rb +5 -5
- data/lib/nonnative/grpc_server.rb +3 -5
- data/lib/nonnative/http_server.rb +3 -5
- data/lib/nonnative/no_proxy.rb +1 -1
- data/lib/nonnative/pool.rb +1 -3
- data/lib/nonnative/process.rb +2 -6
- data/lib/nonnative/proxy.rb +2 -3
- data/lib/nonnative/proxy_factory.rb +5 -6
- data/lib/nonnative/runner.rb +0 -8
- data/lib/nonnative/server.rb +2 -5
- data/lib/nonnative/service.rb +12 -0
- data/lib/nonnative/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3015cc7b1f784a45dbb77184fd73e67e6cf33ff9d7bb97d09ebd01f1359d85b9
|
|
4
|
+
data.tar.gz: 9bf8f2fc9bb72f8de89e76f44912d62e8a7cd19ccb533c7930600fbc70411b28
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 73147dafad8a271673d070f033a1a3f92270994b0593bb7f391d0ae126ed0e34005cfd22af9e01052846bd757b866fba9db4d36a3468219be54208e8121a5f80
|
|
7
|
+
data.tar.gz: ad58bc0015ccdf3f91f9ffbb5ca12de3bd9650d540ef33ec982df353abd6892c496328815f60991e340721971e1bfbf86a663292337a755280d1157c2ff9d5be
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Nonnative is a Ruby-first harness for end-to-end testing of systems implemented
|
|
|
10
10
|
It helps you:
|
|
11
11
|
- start **OS processes** (e.g. your Go/Java/Rust service binary),
|
|
12
12
|
- start **in-process Ruby servers** (e.g. small HTTP/TCP/gRPC fakes for dependencies),
|
|
13
|
-
- optionally start **proxies** in front of
|
|
13
|
+
- optionally start **service proxies** for fault-injection in front of externally managed dependencies,
|
|
14
14
|
- wait for readiness/shutdown using **TCP port checks**.
|
|
15
15
|
|
|
16
16
|
Once started, you can test however you like (TCP, HTTP, gRPC, etc).
|
|
@@ -54,25 +54,20 @@ High-level configuration fields:
|
|
|
54
54
|
- `log`: path for the Nonnative logger output.
|
|
55
55
|
- `processes`: child processes to `spawn`.
|
|
56
56
|
- `servers`: in-process Ruby servers started in threads.
|
|
57
|
-
- `services`: external dependencies (
|
|
57
|
+
- `services`: external dependencies (no process/thread started by Nonnative).
|
|
58
58
|
|
|
59
59
|
Common runner fields:
|
|
60
60
|
- `name`: runner name used for lookup.
|
|
61
61
|
- `host`: client-facing host. Defaults to `127.0.0.1`.
|
|
62
62
|
|
|
63
63
|
Process/server fields:
|
|
64
|
-
- `ports`: client-facing ports. These are also used for readiness/shutdown port checks.
|
|
64
|
+
- `ports`: client-facing ports. These are also used for readiness/shutdown port checks.
|
|
65
65
|
- `timeout`: max time (seconds) for readiness/shutdown port checks.
|
|
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
69
|
Service fields:
|
|
70
|
-
- `port`: client-facing
|
|
71
|
-
|
|
72
|
-
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`.
|
|
73
|
-
|
|
74
|
-
> [!IMPORTANT]
|
|
75
|
-
> When a proxy is enabled, tests and clients connect to the runner `host` and client-facing endpoint (`ports` first entry for processes/servers, `port` for services); the nested `proxy.host`/`proxy.port` is the upstream target behind the proxy.
|
|
70
|
+
- `port`: client-facing service port. Services do not get TCP readiness/shutdown checks from Nonnative.
|
|
76
71
|
|
|
77
72
|
Nonnative readiness and shutdown checks are TCP-only. Configure process/server ports that are dedicated to the test run; if another process is already listening on the same endpoint, results are undefined.
|
|
78
73
|
|
|
@@ -252,7 +247,7 @@ module Nonnative
|
|
|
252
247
|
def initialize(service)
|
|
253
248
|
super
|
|
254
249
|
|
|
255
|
-
@socket_server = ::TCPServer.new(
|
|
250
|
+
@socket_server = ::TCPServer.new(service.host, service.port)
|
|
256
251
|
end
|
|
257
252
|
|
|
258
253
|
def perform_start
|
|
@@ -409,9 +404,9 @@ Nonnative.configure do |config|
|
|
|
409
404
|
end
|
|
410
405
|
```
|
|
411
406
|
|
|
412
|
-
##### 🔀 Proxy
|
|
407
|
+
##### 🔀 HTTP Forward Proxy
|
|
413
408
|
|
|
414
|
-
The system allows you to define an HTTP proxy for external systems, e.g. `api.github.com`.
|
|
409
|
+
The system allows you to define an in-process HTTP forward proxy server for external systems, e.g. `api.github.com`. This is a server implementation, not a fault-injection service proxy.
|
|
415
410
|
|
|
416
411
|
Define your server:
|
|
417
412
|
|
|
@@ -547,9 +542,9 @@ end
|
|
|
547
542
|
|
|
548
543
|
### 🧩 Services
|
|
549
544
|
|
|
550
|
-
A service is an external dependency to your system that you **do not** want Nonnative to start (no OS process, no Ruby thread).
|
|
545
|
+
A service is an external dependency to your system that you **do not** want Nonnative to start (no OS process, no Ruby thread).
|
|
551
546
|
|
|
552
|
-
Services do not get process lifecycle management or TCP readiness/shutdown checks from Nonnative. They
|
|
547
|
+
Services do not get process lifecycle management or TCP readiness/shutdown checks from Nonnative. They provide a named endpoint for a dependency that another tool already manages.
|
|
553
548
|
|
|
554
549
|
Set it up programmatically:
|
|
555
550
|
|
|
@@ -620,153 +615,38 @@ Custom proxy kinds can be registered through `Nonnative.proxies`:
|
|
|
620
615
|
Nonnative.proxies['custom'] = CustomProxy
|
|
621
616
|
```
|
|
622
617
|
|
|
623
|
-
For `fault_injection`, keep the
|
|
624
|
-
|
|
625
|
-
##### ⚙️ Process Proxies
|
|
626
|
-
|
|
627
|
-
Add this to an existing process configuration:
|
|
628
|
-
|
|
629
|
-
```ruby
|
|
630
|
-
require 'nonnative'
|
|
631
|
-
|
|
632
|
-
Nonnative.configure do |config|
|
|
633
|
-
config.version = '1.0'
|
|
634
|
-
config.name = 'test'
|
|
635
|
-
config.url = 'http://localhost:4567'
|
|
636
|
-
config.log = 'nonnative.log'
|
|
637
|
-
|
|
638
|
-
config.process do |p|
|
|
639
|
-
p.host = '127.0.0.1'
|
|
640
|
-
p.ports = [20_000]
|
|
641
|
-
|
|
642
|
-
p.proxy = {
|
|
643
|
-
kind: 'fault_injection',
|
|
644
|
-
host: '127.0.0.1',
|
|
645
|
-
port: 12_321,
|
|
646
|
-
log: 'proxy_server.log',
|
|
647
|
-
wait: 1,
|
|
648
|
-
options: {
|
|
649
|
-
delay: 5
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
end
|
|
653
|
-
end
|
|
654
|
-
```
|
|
655
|
-
|
|
656
|
-
YAML fragment:
|
|
618
|
+
Only services support proxies. For `fault_injection`, keep the service `host`/`port` as the client-facing proxy endpoint and use nested `proxy.host`/`proxy.port` for the upstream target behind the proxy.
|
|
657
619
|
|
|
658
|
-
|
|
659
|
-
version: "1.0"
|
|
660
|
-
name: test
|
|
661
|
-
url: http://localhost:4567
|
|
662
|
-
log: nonnative.log
|
|
663
|
-
processes:
|
|
664
|
-
-
|
|
665
|
-
host: 127.0.0.1
|
|
666
|
-
ports:
|
|
667
|
-
- 20000
|
|
668
|
-
proxy:
|
|
669
|
-
kind: fault_injection
|
|
670
|
-
host: 127.0.0.1
|
|
671
|
-
port: 12321
|
|
672
|
-
log: proxy_server.log
|
|
673
|
-
wait: 1
|
|
674
|
-
options:
|
|
675
|
-
delay: 5
|
|
676
|
-
```
|
|
620
|
+
##### 🧩 Service Proxies
|
|
677
621
|
|
|
678
|
-
|
|
622
|
+
###### Programmatic Configuration
|
|
679
623
|
|
|
680
|
-
Add
|
|
624
|
+
Add a proxy to a service configuration:
|
|
681
625
|
|
|
682
626
|
```ruby
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
kind: 'fault_injection',
|
|
697
|
-
host: '127.0.0.1',
|
|
698
|
-
port: 12_321,
|
|
699
|
-
log: 'proxy_server.log',
|
|
700
|
-
wait: 1,
|
|
701
|
-
options: {
|
|
702
|
-
delay: 5
|
|
703
|
-
}
|
|
627
|
+
config.service do |s|
|
|
628
|
+
s.name = 'redis'
|
|
629
|
+
s.host = '127.0.0.1'
|
|
630
|
+
s.port = 16_379
|
|
631
|
+
|
|
632
|
+
s.proxy = {
|
|
633
|
+
kind: 'fault_injection',
|
|
634
|
+
host: '127.0.0.1',
|
|
635
|
+
port: 6379,
|
|
636
|
+
log: 'proxy_server.log',
|
|
637
|
+
wait: 1,
|
|
638
|
+
options: {
|
|
639
|
+
delay: 5
|
|
704
640
|
}
|
|
705
|
-
|
|
641
|
+
}
|
|
706
642
|
end
|
|
707
643
|
```
|
|
708
644
|
|
|
709
|
-
YAML
|
|
710
|
-
|
|
711
|
-
```yaml
|
|
712
|
-
version: "1.0"
|
|
713
|
-
name: test
|
|
714
|
-
url: http://localhost:4567
|
|
715
|
-
log: nonnative.log
|
|
716
|
-
servers:
|
|
717
|
-
-
|
|
718
|
-
host: 127.0.0.1
|
|
719
|
-
ports:
|
|
720
|
-
- 20000
|
|
721
|
-
proxy:
|
|
722
|
-
kind: fault_injection
|
|
723
|
-
host: 127.0.0.1
|
|
724
|
-
port: 12321
|
|
725
|
-
log: proxy_server.log
|
|
726
|
-
wait: 1
|
|
727
|
-
options:
|
|
728
|
-
delay: 5
|
|
729
|
-
```
|
|
730
|
-
|
|
731
|
-
##### 🧩 Service Proxies
|
|
732
|
-
|
|
733
|
-
Add this to an existing service configuration:
|
|
734
|
-
|
|
735
|
-
```ruby
|
|
736
|
-
require 'nonnative'
|
|
645
|
+
###### YAML Configuration
|
|
737
646
|
|
|
738
|
-
|
|
739
|
-
config.version = '1.0'
|
|
740
|
-
config.name = 'test'
|
|
741
|
-
config.url = 'http://localhost:4567'
|
|
742
|
-
config.log = 'nonnative.log'
|
|
743
|
-
|
|
744
|
-
config.service do |s|
|
|
745
|
-
s.name = 'redis'
|
|
746
|
-
s.host = '127.0.0.1'
|
|
747
|
-
s.port = 16_379
|
|
748
|
-
|
|
749
|
-
s.proxy = {
|
|
750
|
-
kind: 'fault_injection',
|
|
751
|
-
host: '127.0.0.1',
|
|
752
|
-
port: 6379,
|
|
753
|
-
log: 'proxy_server.log',
|
|
754
|
-
wait: 1,
|
|
755
|
-
options: {
|
|
756
|
-
delay: 5
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
end
|
|
760
|
-
end
|
|
761
|
-
```
|
|
762
|
-
|
|
763
|
-
YAML fragment:
|
|
647
|
+
Add a proxy to a service YAML entry:
|
|
764
648
|
|
|
765
649
|
```yaml
|
|
766
|
-
version: "1.0"
|
|
767
|
-
name: test
|
|
768
|
-
url: http://localhost:4567
|
|
769
|
-
log: nonnative.log
|
|
770
650
|
services:
|
|
771
651
|
-
|
|
772
652
|
name: redis
|
|
@@ -786,53 +666,15 @@ services:
|
|
|
786
666
|
|
|
787
667
|
The `fault_injection` proxy allows you to simulate failures by injecting them. We currently support the following:
|
|
788
668
|
|
|
789
|
-
Clients connect to the
|
|
669
|
+
Clients connect to the service `host`/`port`, while the proxy forwards traffic to nested `proxy.host`/`proxy.port`.
|
|
790
670
|
|
|
791
671
|
- `close_all` - Closes the socket as soon as it connects.
|
|
792
672
|
- `delay` - Delays traffic on the connection. Defaults to 2 seconds and can be configured through options.
|
|
793
673
|
- `invalid_data` - Forwards client requests unchanged, then corrupts upstream responses before they reach the client.
|
|
794
674
|
|
|
795
|
-
###### ⚙️ Fault Injection Processes
|
|
796
|
-
|
|
797
|
-
Set it up programmatically:
|
|
798
|
-
|
|
799
|
-
```ruby
|
|
800
|
-
name = 'name of process in configuration'
|
|
801
|
-
server = Nonnative.pool.process_by_name(name)
|
|
802
|
-
|
|
803
|
-
server.proxy.close_all # To use close_all.
|
|
804
|
-
server.proxy.reset # To reset it back to a good state.
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
With cucumber:
|
|
808
|
-
|
|
809
|
-
```cucumber
|
|
810
|
-
Given I set the proxy for process 'process_1' to 'close_all'
|
|
811
|
-
Then I should reset the proxy for process 'process_1'
|
|
812
|
-
```
|
|
813
|
-
|
|
814
|
-
###### 🖥️ Fault Injection Servers
|
|
815
|
-
|
|
816
|
-
Set it up programmatically:
|
|
817
|
-
|
|
818
|
-
```ruby
|
|
819
|
-
name = 'name of server in configuration'
|
|
820
|
-
server = Nonnative.pool.server_by_name(name)
|
|
821
|
-
|
|
822
|
-
server.proxy.close_all # To use close_all.
|
|
823
|
-
server.proxy.reset # To reset it back to a good state.
|
|
824
|
-
```
|
|
825
|
-
|
|
826
|
-
With cucumber:
|
|
827
|
-
|
|
828
|
-
```cucumber
|
|
829
|
-
Given I set the proxy for server 'server_1' to 'close_all'
|
|
830
|
-
Then I should reset the proxy for server 'server_1'
|
|
831
|
-
```
|
|
832
|
-
|
|
833
675
|
###### 🧩 Fault Injection Services
|
|
834
676
|
|
|
835
|
-
Set
|
|
677
|
+
Set the proxy state programmatically:
|
|
836
678
|
|
|
837
679
|
```ruby
|
|
838
680
|
name = 'name of service in configuration'
|
|
@@ -842,7 +684,7 @@ service.proxy.close_all # To use close_all.
|
|
|
842
684
|
service.proxy.reset # To reset it back to a good state.
|
|
843
685
|
```
|
|
844
686
|
|
|
845
|
-
|
|
687
|
+
Use the Cucumber proxy steps:
|
|
846
688
|
|
|
847
689
|
```cucumber
|
|
848
690
|
Given I set the proxy for service 'service_1' to 'close_all'
|
|
@@ -124,13 +124,13 @@ module Nonnative
|
|
|
124
124
|
def add_processes(cfg)
|
|
125
125
|
processes = cfg.processes || []
|
|
126
126
|
processes.each do |loaded_process|
|
|
127
|
+
reject_proxy(loaded_process, 'processes')
|
|
128
|
+
|
|
127
129
|
process do |process_config|
|
|
128
130
|
process_config.command = command(loaded_process)
|
|
129
131
|
process_config.signal = loaded_process.signal
|
|
130
132
|
process_config.environment = loaded_process.environment
|
|
131
133
|
runner_attributes(process_config, loaded_process)
|
|
132
|
-
|
|
133
|
-
assign_proxy(process_config, loaded_process.proxy)
|
|
134
134
|
end
|
|
135
135
|
end
|
|
136
136
|
end
|
|
@@ -150,11 +150,11 @@ module Nonnative
|
|
|
150
150
|
def add_servers(cfg)
|
|
151
151
|
servers = cfg.servers || []
|
|
152
152
|
servers.each do |loaded_server|
|
|
153
|
+
reject_proxy(loaded_server, 'servers')
|
|
154
|
+
|
|
153
155
|
server do |server_config|
|
|
154
156
|
server_config.klass = Object.const_get(server_class_name(loaded_server))
|
|
155
157
|
runner_attributes(server_config, loaded_server)
|
|
156
|
-
|
|
157
|
-
assign_proxy(server_config, loaded_server.proxy)
|
|
158
158
|
end
|
|
159
159
|
end
|
|
160
160
|
end
|
|
@@ -200,7 +200,14 @@ module Nonnative
|
|
|
200
200
|
service.port = loaded.port if loaded.port
|
|
201
201
|
end
|
|
202
202
|
|
|
203
|
-
def
|
|
203
|
+
def reject_proxy(loaded, kind)
|
|
204
|
+
values = loaded.to_h
|
|
205
|
+
return unless values.key?(:proxy) || values.key?('proxy')
|
|
206
|
+
|
|
207
|
+
raise ArgumentError, "Use 'services' for proxy configuration; #{kind} do not support 'proxy'"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def assign_proxy(service, loaded_proxy)
|
|
204
211
|
return unless loaded_proxy
|
|
205
212
|
|
|
206
213
|
proxy_attributes = {
|
|
@@ -213,7 +220,7 @@ module Nonnative
|
|
|
213
220
|
proxy_attributes[:host] = loaded_proxy.host if loaded_proxy.host
|
|
214
221
|
proxy_attributes[:wait] = loaded_proxy.wait if loaded_proxy.wait
|
|
215
222
|
|
|
216
|
-
|
|
223
|
+
service.proxy = proxy_attributes
|
|
217
224
|
end
|
|
218
225
|
end
|
|
219
226
|
end
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Nonnative
|
|
4
|
-
# Proxy configuration attached to a
|
|
4
|
+
# Proxy configuration attached to a service configuration.
|
|
5
5
|
#
|
|
6
6
|
# A proxy allows you to interpose behavior between a client and a real service. For example,
|
|
7
7
|
# the built-in `"fault_injection"` proxy can close connections, introduce delays, or corrupt data
|
|
8
8
|
# for resilience testing.
|
|
9
9
|
#
|
|
10
|
-
# This object is created automatically for each
|
|
11
|
-
# When `kind` is set to `"none"`, no proxy is started and the
|
|
10
|
+
# This object is created automatically for each service via {Nonnative::ConfigurationService}.
|
|
11
|
+
# When `kind` is set to `"none"`, no proxy is started and the service will use its configured
|
|
12
12
|
# `host`/`port` directly.
|
|
13
13
|
#
|
|
14
|
-
# @see Nonnative::
|
|
14
|
+
# @see Nonnative::ConfigurationService#proxy
|
|
15
15
|
# @see Nonnative.proxies
|
|
16
16
|
class ConfigurationProxy
|
|
17
17
|
# @return [String] proxy kind name (for example `"none"` or `"fault_injection"`)
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
module Nonnative
|
|
4
4
|
# Base configuration for a runnable unit managed by Nonnative.
|
|
5
5
|
#
|
|
6
|
-
# This class holds connection and timing attributes common to processes, servers and services
|
|
7
|
-
# as well as a nested {Nonnative::ConfigurationProxy} describing how/if a proxy should be started.
|
|
6
|
+
# This class holds connection and timing attributes common to processes, servers and services.
|
|
8
7
|
#
|
|
9
8
|
# Instances of this type are typically created via {Nonnative::Configuration#process},
|
|
10
9
|
# {Nonnative::Configuration#server}, or {Nonnative::Configuration#service}.
|
|
@@ -22,29 +21,18 @@ module Nonnative
|
|
|
22
21
|
# @return [Array<Integer>] client-facing ports used for readiness/shutdown checks
|
|
23
22
|
attr_reader :ports
|
|
24
23
|
|
|
25
|
-
# Proxy configuration for this runner.
|
|
26
|
-
#
|
|
27
|
-
# Note that this returns a configuration object even if no proxy is enabled; by default
|
|
28
|
-
# the proxy kind is `"none"`.
|
|
29
|
-
#
|
|
30
|
-
# @return [Nonnative::ConfigurationProxy]
|
|
31
|
-
attr_reader :proxy
|
|
32
|
-
|
|
33
24
|
# Creates a runner configuration with defaults.
|
|
34
25
|
#
|
|
35
26
|
# Defaults:
|
|
36
27
|
# - `host`: `"127.0.0.1"`
|
|
37
28
|
# - `ports`: `[0]`
|
|
38
29
|
# - `wait`: `0.1`
|
|
39
|
-
# - `proxy`: a new {Nonnative::ConfigurationProxy} with its own defaults
|
|
40
30
|
#
|
|
41
31
|
# @return [void]
|
|
42
32
|
def initialize
|
|
43
33
|
self.host = '127.0.0.1'
|
|
44
34
|
@ports = [0]
|
|
45
35
|
self.wait = 0.1
|
|
46
|
-
|
|
47
|
-
@proxy = Nonnative::ConfigurationProxy.new
|
|
48
36
|
end
|
|
49
37
|
|
|
50
38
|
# Sets the client-facing ports for this runner.
|
|
@@ -57,34 +45,11 @@ module Nonnative
|
|
|
57
45
|
|
|
58
46
|
# Returns the primary client-facing port.
|
|
59
47
|
#
|
|
60
|
-
# This preserves a single endpoint for
|
|
61
|
-
# configuration contract uses {#ports}.
|
|
48
|
+
# This preserves a single endpoint for client helpers while the public configuration contract uses {#ports}.
|
|
62
49
|
#
|
|
63
50
|
# @return [Integer]
|
|
64
51
|
def port
|
|
65
52
|
ports.first
|
|
66
53
|
end
|
|
67
|
-
|
|
68
|
-
# Sets proxy configuration using a hash-like value.
|
|
69
|
-
#
|
|
70
|
-
# This is primarily used when loading YAML configuration files, where proxy attributes are
|
|
71
|
-
# represented as scalar values.
|
|
72
|
-
#
|
|
73
|
-
# @param value [Hash] proxy attributes
|
|
74
|
-
# @option value [String] :kind proxy kind name (for example `"fault_injection"`)
|
|
75
|
-
# @option value [String] :host upstream host behind the proxy (optional)
|
|
76
|
-
# @option value [Integer] :port upstream port behind the proxy
|
|
77
|
-
# @option value [String] :log proxy log file path
|
|
78
|
-
# @option value [Numeric] :wait wait interval (seconds) after state changes (optional)
|
|
79
|
-
# @option value [Hash] :options proxy implementation specific options
|
|
80
|
-
# @return [void]
|
|
81
|
-
def proxy=(value)
|
|
82
|
-
proxy.kind = value[:kind]
|
|
83
|
-
proxy.host = value[:host] if value[:host]
|
|
84
|
-
proxy.port = value[:port]
|
|
85
|
-
proxy.log = value[:log]
|
|
86
|
-
proxy.wait = value[:wait] if value[:wait]
|
|
87
|
-
proxy.options = value[:options]
|
|
88
|
-
end
|
|
89
54
|
end
|
|
90
55
|
end
|
|
@@ -14,6 +14,11 @@ module Nonnative
|
|
|
14
14
|
# @return [Integer] client-facing port used by the service proxy
|
|
15
15
|
attr_accessor :port
|
|
16
16
|
|
|
17
|
+
# Proxy configuration for this service.
|
|
18
|
+
#
|
|
19
|
+
# @return [Nonnative::ConfigurationProxy]
|
|
20
|
+
attr_reader :proxy
|
|
21
|
+
|
|
17
22
|
# Creates a service configuration with defaults.
|
|
18
23
|
#
|
|
19
24
|
# @return [void]
|
|
@@ -21,6 +26,29 @@ module Nonnative
|
|
|
21
26
|
super
|
|
22
27
|
|
|
23
28
|
self.port = 0
|
|
29
|
+
@proxy = Nonnative::ConfigurationProxy.new
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Sets proxy configuration using a hash-like value.
|
|
33
|
+
#
|
|
34
|
+
# This is primarily used when loading YAML configuration files, where proxy attributes are
|
|
35
|
+
# represented as scalar values.
|
|
36
|
+
#
|
|
37
|
+
# @param value [Hash] proxy attributes
|
|
38
|
+
# @option value [String] :kind proxy kind name (for example `"fault_injection"`)
|
|
39
|
+
# @option value [String] :host upstream host behind the proxy (optional)
|
|
40
|
+
# @option value [Integer] :port upstream port behind the proxy
|
|
41
|
+
# @option value [String] :log proxy log file path
|
|
42
|
+
# @option value [Numeric] :wait wait interval (seconds) after state changes (optional)
|
|
43
|
+
# @option value [Hash] :options proxy implementation specific options
|
|
44
|
+
# @return [void]
|
|
45
|
+
def proxy=(value)
|
|
46
|
+
proxy.kind = value[:kind]
|
|
47
|
+
proxy.host = value[:host] if value[:host]
|
|
48
|
+
proxy.port = value[:port]
|
|
49
|
+
proxy.log = value[:log]
|
|
50
|
+
proxy.wait = value[:wait] if value[:wait]
|
|
51
|
+
proxy.options = value[:options]
|
|
24
52
|
end
|
|
25
53
|
|
|
26
54
|
# Services expose a single proxy listener, so plural runner ports are not supported.
|
data/lib/nonnative/cucumber.rb
CHANGED
|
@@ -45,16 +45,6 @@ module Nonnative
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def install_proxy_mutation_steps
|
|
48
|
-
Given('I set the proxy for process {string} to {string}') do |name, operation|
|
|
49
|
-
process = Nonnative.pool.process_by_name(name)
|
|
50
|
-
Nonnative::Cucumber::Registration.apply_proxy_operation(process.proxy, operation)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
Given('I set the proxy for server {string} to {string}') do |name, operation|
|
|
54
|
-
server = Nonnative.pool.server_by_name(name)
|
|
55
|
-
Nonnative::Cucumber::Registration.apply_proxy_operation(server.proxy, operation)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
48
|
Given('I set the proxy for service {string} to {string}') do |name, operation|
|
|
59
49
|
service = Nonnative.pool.service_by_name(name)
|
|
60
50
|
Nonnative::Cucumber::Registration.apply_proxy_operation(service.proxy, operation)
|
|
@@ -62,16 +52,6 @@ module Nonnative
|
|
|
62
52
|
end
|
|
63
53
|
|
|
64
54
|
def install_proxy_reset_steps
|
|
65
|
-
Then('I should reset the proxy for process {string}') do |name|
|
|
66
|
-
process = Nonnative.pool.process_by_name(name)
|
|
67
|
-
process.proxy.reset
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
Then('I should reset the proxy for server {string}') do |name|
|
|
71
|
-
server = Nonnative.pool.server_by_name(name)
|
|
72
|
-
server.proxy.reset
|
|
73
|
-
end
|
|
74
|
-
|
|
75
55
|
Then('I should reset the proxy for service {string}') do |name|
|
|
76
56
|
service = Nonnative.pool.service_by_name(name)
|
|
77
57
|
service.proxy.reset
|
|
@@ -18,12 +18,12 @@ module Nonnative
|
|
|
18
18
|
#
|
|
19
19
|
# ## Wiring
|
|
20
20
|
#
|
|
21
|
-
# When enabled, your test/client should connect to the
|
|
21
|
+
# When enabled, your test/client should connect to the service `host` and `port` (the proxy
|
|
22
22
|
# endpoint), and the proxy will forward traffic to the upstream target exposed by {#host}:{#port}.
|
|
23
23
|
#
|
|
24
24
|
# ## Configuration
|
|
25
25
|
#
|
|
26
|
-
# The proxy is configured via the
|
|
26
|
+
# The proxy is configured via the service's `proxy` hash:
|
|
27
27
|
#
|
|
28
28
|
# - `kind`: `"fault_injection"`
|
|
29
29
|
# - `host` / `port`: upstream target behind the proxy (exposed via {#host}/{#port})
|
|
@@ -50,7 +50,7 @@ module Nonnative
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
# @param service [Nonnative::
|
|
53
|
+
# @param service [Nonnative::ConfigurationService] service configuration with proxy settings
|
|
54
54
|
def initialize(service)
|
|
55
55
|
@connections = Concurrent::Hash.new
|
|
56
56
|
@logger = Logger.new(service.proxy.log)
|
|
@@ -62,8 +62,8 @@ 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
|
|
66
|
-
# Clients connect to that
|
|
65
|
+
# This binds a TCP server on the service `host` and `port`.
|
|
66
|
+
# Clients connect to that service endpoint, while upstream traffic is forwarded to {#host}:{#port}.
|
|
67
67
|
#
|
|
68
68
|
# @return [void]
|
|
69
69
|
def start
|
|
@@ -4,8 +4,7 @@ module Nonnative
|
|
|
4
4
|
# gRPC server runner implemented using {GRPC::RpcServer}.
|
|
5
5
|
#
|
|
6
6
|
# This is a convenience server implementation for running a gRPC service in-process under
|
|
7
|
-
# Nonnative's server lifecycle. It binds to the configured
|
|
8
|
-
# by {Nonnative::Server} via {#perform_start} / {#perform_stop}.
|
|
7
|
+
# Nonnative's server lifecycle. It binds to the configured server `host` and first `ports` entry.
|
|
9
8
|
#
|
|
10
9
|
# Important note about logging: the `grpc` gem uses a global logger. This implementation sets
|
|
11
10
|
# `GRPC.logger` to write to the configured `service.log`, and whichever gRPC server is initialized
|
|
@@ -33,12 +32,11 @@ module Nonnative
|
|
|
33
32
|
|
|
34
33
|
# Binds the gRPC server and begins serving requests.
|
|
35
34
|
#
|
|
36
|
-
# The server binds to the
|
|
37
|
-
# runner host and first configured port as the client-facing endpoint used by readiness checks.
|
|
35
|
+
# The server binds to the configured server host and first configured port.
|
|
38
36
|
#
|
|
39
37
|
# @return [void]
|
|
40
38
|
def perform_start
|
|
41
|
-
server.add_http2_port("#{
|
|
39
|
+
server.add_http2_port("#{service.host}:#{service.port}", :this_port_is_insecure)
|
|
42
40
|
server.run
|
|
43
41
|
end
|
|
44
42
|
|
|
@@ -4,8 +4,7 @@ module Nonnative
|
|
|
4
4
|
# Puma-based HTTP server runner.
|
|
5
5
|
#
|
|
6
6
|
# This is a convenience server implementation for running a Rack/Sinatra application in-process
|
|
7
|
-
# under Nonnative's server lifecycle. It binds to the configured
|
|
8
|
-
# consistently with proxy configuration) and uses Puma for HTTP serving.
|
|
7
|
+
# under Nonnative's server lifecycle. It binds to the configured server `host` and first `ports` entry.
|
|
9
8
|
#
|
|
10
9
|
# The server is started and stopped by {Nonnative::Server} via {#perform_start} / {#perform_stop}.
|
|
11
10
|
#
|
|
@@ -48,12 +47,11 @@ module Nonnative
|
|
|
48
47
|
|
|
49
48
|
# Binds the Puma server and begins serving.
|
|
50
49
|
#
|
|
51
|
-
# The listener binds to the
|
|
52
|
-
# runner host and first configured port as the client-facing endpoint used by readiness checks.
|
|
50
|
+
# The listener binds to the configured server host and first configured port.
|
|
53
51
|
#
|
|
54
52
|
# @return [void]
|
|
55
53
|
def perform_start
|
|
56
|
-
server.add_tcp_listener
|
|
54
|
+
server.add_tcp_listener service.host, service.port
|
|
57
55
|
server.run false
|
|
58
56
|
end
|
|
59
57
|
|
data/lib/nonnative/no_proxy.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Nonnative
|
|
|
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
|
#
|
|
10
|
-
#
|
|
10
|
+
# Services can always call `start`, `stop`, and `reset` safely on this proxy.
|
|
11
11
|
#
|
|
12
12
|
# @see Nonnative.proxy
|
|
13
13
|
# @see Nonnative::Proxy
|
data/lib/nonnative/pool.rb
CHANGED
|
@@ -104,15 +104,13 @@ module Nonnative
|
|
|
104
104
|
services[runner_index(configuration.services, name)]
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
# Resets proxies
|
|
107
|
+
# Resets service proxies in this pool.
|
|
108
108
|
#
|
|
109
109
|
# This is used by the Cucumber `@reset` hook and is safe to call any time after the pool is created.
|
|
110
110
|
#
|
|
111
111
|
# @return [void]
|
|
112
112
|
def reset
|
|
113
113
|
services.each { |s| s.proxy.reset }
|
|
114
|
-
servers.each { |s| s.first.proxy.reset }
|
|
115
|
-
processes.each { |p| p.first.proxy.reset }
|
|
116
114
|
end
|
|
117
115
|
|
|
118
116
|
private
|
data/lib/nonnative/process.rb
CHANGED
|
@@ -4,7 +4,6 @@ module Nonnative
|
|
|
4
4
|
# Runtime runner that manages an OS-level child process.
|
|
5
5
|
#
|
|
6
6
|
# A process runner:
|
|
7
|
-
# - starts the configured proxy (if any),
|
|
8
7
|
# - spawns a child process using the configured command and environment,
|
|
9
8
|
# - waits briefly (via the runner `wait`), and
|
|
10
9
|
# - participates in readiness/shutdown via TCP port checks orchestrated by {Nonnative::Pool}.
|
|
@@ -21,7 +20,7 @@ module Nonnative
|
|
|
21
20
|
@timeout = Nonnative::Timeout.new(service.timeout)
|
|
22
21
|
end
|
|
23
22
|
|
|
24
|
-
#
|
|
23
|
+
# Spawns the configured process if it is not already running.
|
|
25
24
|
#
|
|
26
25
|
# @return [Array<(Integer, Boolean)>]
|
|
27
26
|
# a tuple of:
|
|
@@ -29,7 +28,6 @@ module Nonnative
|
|
|
29
28
|
# - whether the process appears to still be running (non-blocking wait result)
|
|
30
29
|
def start
|
|
31
30
|
unless process_exists?
|
|
32
|
-
proxy.start
|
|
33
31
|
@pid = process_spawn
|
|
34
32
|
wait_start
|
|
35
33
|
end
|
|
@@ -37,7 +35,7 @@ module Nonnative
|
|
|
37
35
|
[pid, ::Process.waitpid2(pid, ::Process::WNOHANG).nil?]
|
|
38
36
|
end
|
|
39
37
|
|
|
40
|
-
# Stops the process
|
|
38
|
+
# Stops the process if it is running.
|
|
41
39
|
#
|
|
42
40
|
# The process is signalled using the configured signal (defaults to `INT` when not set).
|
|
43
41
|
#
|
|
@@ -54,8 +52,6 @@ module Nonnative
|
|
|
54
52
|
end
|
|
55
53
|
|
|
56
54
|
[pid, stopped]
|
|
57
|
-
ensure
|
|
58
|
-
proxy.stop
|
|
59
55
|
end
|
|
60
56
|
|
|
61
57
|
# Returns a memoized memory reader for the spawned process.
|
data/lib/nonnative/proxy.rb
CHANGED
|
@@ -4,8 +4,7 @@ module Nonnative
|
|
|
4
4
|
# Base class for proxy implementations.
|
|
5
5
|
#
|
|
6
6
|
# A proxy is responsible for interposing behavior between a client and a target service.
|
|
7
|
-
#
|
|
8
|
-
# instance via {Nonnative::ProxyFactory} based on `service.proxy.kind`.
|
|
7
|
+
# Runtime services create a proxy instance via {Nonnative::ProxyFactory} based on `service.proxy.kind`.
|
|
9
8
|
#
|
|
10
9
|
# Concrete proxies typically implement these public methods:
|
|
11
10
|
# - `start`: begin proxying (bind/listen, start threads, etc)
|
|
@@ -17,7 +16,7 @@ module Nonnative
|
|
|
17
16
|
# @see Nonnative::NoProxy
|
|
18
17
|
# @see Nonnative::FaultInjectionProxy
|
|
19
18
|
class Proxy
|
|
20
|
-
# @param service [Nonnative::
|
|
19
|
+
# @param service [Nonnative::ConfigurationService] service configuration with an attached proxy configuration
|
|
21
20
|
def initialize(service)
|
|
22
21
|
@service = service
|
|
23
22
|
end
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Nonnative
|
|
4
|
-
# Factory for creating proxy instances for
|
|
4
|
+
# Factory for creating proxy instances for services.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# using {Nonnative.proxy}.
|
|
6
|
+
# A runtime service constructs a proxy via this factory. The proxy implementation is selected by
|
|
7
|
+
# `service.proxy.kind` and resolved using {Nonnative.proxy}.
|
|
9
8
|
#
|
|
10
9
|
# If the kind is unknown (or `"none"`), {Nonnative.proxy} returns {Nonnative::NoProxy}.
|
|
11
10
|
#
|
|
@@ -15,9 +14,9 @@ module Nonnative
|
|
|
15
14
|
# @see Nonnative::NoProxy
|
|
16
15
|
class ProxyFactory
|
|
17
16
|
class << self
|
|
18
|
-
# Creates a proxy instance for the given
|
|
17
|
+
# Creates a proxy instance for the given service configuration.
|
|
19
18
|
#
|
|
20
|
-
# @param service [Nonnative::
|
|
19
|
+
# @param service [Nonnative::ConfigurationService] service configuration with an attached proxy configuration
|
|
21
20
|
# @return [Nonnative::Proxy] proxy instance (may be a {Nonnative::NoProxy})
|
|
22
21
|
def create(service)
|
|
23
22
|
proxy = Nonnative.proxy(service.proxy.kind)
|
data/lib/nonnative/runner.rb
CHANGED
|
@@ -10,21 +10,13 @@ module Nonnative
|
|
|
10
10
|
# - {Nonnative::Server} for in-process Ruby servers (threads)
|
|
11
11
|
# - {Nonnative::Service} for proxy-only external dependencies
|
|
12
12
|
#
|
|
13
|
-
# Each runner has an associated proxy instance created via {Nonnative::ProxyFactory}.
|
|
14
|
-
#
|
|
15
13
|
# @see Nonnative::Process
|
|
16
14
|
# @see Nonnative::Server
|
|
17
15
|
# @see Nonnative::Service
|
|
18
16
|
class Runner
|
|
19
|
-
# Returns the proxy instance for this runner.
|
|
20
|
-
#
|
|
21
|
-
# @return [Nonnative::Proxy]
|
|
22
|
-
attr_reader :proxy
|
|
23
|
-
|
|
24
17
|
# @param service [Nonnative::ConfigurationRunner] runner configuration
|
|
25
18
|
def initialize(service)
|
|
26
19
|
@service = service
|
|
27
|
-
@proxy = Nonnative::ProxyFactory.create(service)
|
|
28
20
|
end
|
|
29
21
|
|
|
30
22
|
# Returns the configured runner name.
|
data/lib/nonnative/server.rb
CHANGED
|
@@ -4,7 +4,6 @@ module Nonnative
|
|
|
4
4
|
# Runtime runner that manages an in-process Ruby server.
|
|
5
5
|
#
|
|
6
6
|
# A server runner:
|
|
7
|
-
# - starts the configured proxy (if any),
|
|
8
7
|
# - starts a Ruby thread that runs {#perform_start},
|
|
9
8
|
# - waits briefly (via the runner `wait`), and
|
|
10
9
|
# - participates in readiness/shutdown via TCP port checks orchestrated by {Nonnative::Pool}.
|
|
@@ -25,7 +24,7 @@ module Nonnative
|
|
|
25
24
|
@timeout = Nonnative::Timeout.new(service.timeout)
|
|
26
25
|
end
|
|
27
26
|
|
|
28
|
-
# Starts the
|
|
27
|
+
# Starts the server thread if it is not already started.
|
|
29
28
|
#
|
|
30
29
|
# @return [Array<(Integer, TrueClass)>]
|
|
31
30
|
# a tuple of:
|
|
@@ -33,7 +32,6 @@ module Nonnative
|
|
|
33
32
|
# - `true` (thread creation itself is considered started; readiness is checked separately)
|
|
34
33
|
def start
|
|
35
34
|
unless thread
|
|
36
|
-
proxy.start
|
|
37
35
|
@thread = Thread.new { perform_start }
|
|
38
36
|
|
|
39
37
|
wait_start
|
|
@@ -46,14 +44,13 @@ module Nonnative
|
|
|
46
44
|
|
|
47
45
|
# Stops the server if it is running.
|
|
48
46
|
#
|
|
49
|
-
# Calls {#perform_stop}, terminates the server thread,
|
|
47
|
+
# Calls {#perform_stop}, terminates the server thread, and waits briefly.
|
|
50
48
|
#
|
|
51
49
|
# @return [Integer] the server identifier (`object_id`)
|
|
52
50
|
def stop
|
|
53
51
|
if thread
|
|
54
52
|
perform_stop
|
|
55
53
|
thread.terminate
|
|
56
|
-
proxy.stop
|
|
57
54
|
|
|
58
55
|
@thread = nil
|
|
59
56
|
wait_stop
|
data/lib/nonnative/service.rb
CHANGED
|
@@ -12,6 +12,18 @@ module Nonnative
|
|
|
12
12
|
# @see Nonnative::ConfigurationService
|
|
13
13
|
# @see Nonnative::Proxy
|
|
14
14
|
class Service < Runner
|
|
15
|
+
# Returns the proxy instance for this service.
|
|
16
|
+
#
|
|
17
|
+
# @return [Nonnative::Proxy]
|
|
18
|
+
attr_reader :proxy
|
|
19
|
+
|
|
20
|
+
# @param service [Nonnative::ConfigurationService] service configuration
|
|
21
|
+
def initialize(service)
|
|
22
|
+
super
|
|
23
|
+
|
|
24
|
+
@proxy = Nonnative::ProxyFactory.create(service)
|
|
25
|
+
end
|
|
26
|
+
|
|
15
27
|
# Starts the configured proxy (if any).
|
|
16
28
|
#
|
|
17
29
|
# @return [void]
|
data/lib/nonnative/version.rb
CHANGED