nonnative 1.27.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 +7 -0
- data/.circleci/config.yml +38 -0
- data/.config/cucumber.yml +1 -0
- data/.editorconfig +24 -0
- data/.gitignore +11 -0
- data/.rubocop.yml +87 -0
- data/.ruby-version +1 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +202 -0
- data/LICENSE +24 -0
- data/Makefile +27 -0
- data/README.md +366 -0
- data/Rakefile +4 -0
- data/bin/console +15 -0
- data/bin/setup +9 -0
- data/lib/nonnative.rb +89 -0
- data/lib/nonnative/before.rb +11 -0
- data/lib/nonnative/close_all_socket_pair.rb +9 -0
- data/lib/nonnative/command.rb +54 -0
- data/lib/nonnative/configuration.rb +80 -0
- data/lib/nonnative/configuration_process.rb +7 -0
- data/lib/nonnative/configuration_proxy.rb +13 -0
- data/lib/nonnative/configuration_server.rb +19 -0
- data/lib/nonnative/delay_socket_pair.rb +12 -0
- data/lib/nonnative/error.rb +6 -0
- data/lib/nonnative/fault_injection_proxy.rb +78 -0
- data/lib/nonnative/grpc_server.rb +44 -0
- data/lib/nonnative/http_client.rb +53 -0
- data/lib/nonnative/http_server.rb +40 -0
- data/lib/nonnative/invalid_data_socket_pair.rb +11 -0
- data/lib/nonnative/manual.rb +3 -0
- data/lib/nonnative/no_proxy.rb +17 -0
- data/lib/nonnative/observability.rb +13 -0
- data/lib/nonnative/pool.rb +60 -0
- data/lib/nonnative/port.rb +44 -0
- data/lib/nonnative/proxy.rb +13 -0
- data/lib/nonnative/proxy_factory.rb +18 -0
- data/lib/nonnative/server.rb +41 -0
- data/lib/nonnative/service.rb +26 -0
- data/lib/nonnative/socket_pair.rb +53 -0
- data/lib/nonnative/socket_pair_factory.rb +22 -0
- data/lib/nonnative/start_error.rb +6 -0
- data/lib/nonnative/startup.rb +7 -0
- data/lib/nonnative/stop_error.rb +6 -0
- data/lib/nonnative/timeout.rb +21 -0
- data/lib/nonnative/version.rb +5 -0
- data/nonnative.gemspec +42 -0
- data/sonar-project.properties +4 -0
- metadata +340 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'nonnative'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/nonnative.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'timeout'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
require 'grpc'
|
8
|
+
require 'sinatra'
|
9
|
+
require 'rest-client'
|
10
|
+
require 'puma'
|
11
|
+
require 'puma/server'
|
12
|
+
require 'concurrent'
|
13
|
+
|
14
|
+
require 'nonnative/version'
|
15
|
+
require 'nonnative/error'
|
16
|
+
require 'nonnative/start_error'
|
17
|
+
require 'nonnative/stop_error'
|
18
|
+
require 'nonnative/timeout'
|
19
|
+
require 'nonnative/port'
|
20
|
+
require 'nonnative/configuration'
|
21
|
+
require 'nonnative/configuration_process'
|
22
|
+
require 'nonnative/configuration_server'
|
23
|
+
require 'nonnative/configuration_proxy'
|
24
|
+
require 'nonnative/service'
|
25
|
+
require 'nonnative/command'
|
26
|
+
require 'nonnative/pool'
|
27
|
+
require 'nonnative/server'
|
28
|
+
require 'nonnative/http_client'
|
29
|
+
require 'nonnative/http_server'
|
30
|
+
require 'nonnative/grpc_server'
|
31
|
+
require 'nonnative/grpc_server'
|
32
|
+
require 'nonnative/observability'
|
33
|
+
require 'nonnative/proxy_factory'
|
34
|
+
require 'nonnative/proxy'
|
35
|
+
require 'nonnative/no_proxy'
|
36
|
+
require 'nonnative/fault_injection_proxy'
|
37
|
+
require 'nonnative/socket_pair'
|
38
|
+
require 'nonnative/close_all_socket_pair'
|
39
|
+
require 'nonnative/delay_socket_pair'
|
40
|
+
require 'nonnative/invalid_data_socket_pair'
|
41
|
+
require 'nonnative/socket_pair_factory'
|
42
|
+
|
43
|
+
module Nonnative
|
44
|
+
class << self
|
45
|
+
attr_reader :pool
|
46
|
+
|
47
|
+
def load_configuration(path)
|
48
|
+
@configuration ||= Nonnative::Configuration.load_file(path) # rubocop:disable Naming/MemoizedInstanceVariableName
|
49
|
+
end
|
50
|
+
|
51
|
+
def configuration
|
52
|
+
@configuration ||= Nonnative::Configuration.new
|
53
|
+
end
|
54
|
+
|
55
|
+
def configure
|
56
|
+
yield configuration
|
57
|
+
|
58
|
+
require "nonnative/#{configuration.strategy}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def start
|
62
|
+
@pool ||= Nonnative::Pool.new(configuration)
|
63
|
+
errors = []
|
64
|
+
|
65
|
+
@pool.start do |name, id, result|
|
66
|
+
errors << "Started #{name} with id #{id}, though did respond in time" unless result
|
67
|
+
end
|
68
|
+
|
69
|
+
raise Nonnative::StartError, errors.join("\n") unless errors.empty?
|
70
|
+
end
|
71
|
+
|
72
|
+
def stop
|
73
|
+
return if @pool.nil?
|
74
|
+
|
75
|
+
errors = []
|
76
|
+
|
77
|
+
@pool.stop do |name, id, result|
|
78
|
+
errors << "Stopped #{name} with id #{id}, though did respond in time" unless result
|
79
|
+
end
|
80
|
+
|
81
|
+
raise Nonnative::StopError, errors.join("\n") unless errors.empty?
|
82
|
+
end
|
83
|
+
|
84
|
+
def clear
|
85
|
+
@configuration = nil
|
86
|
+
@pool = nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nonnative
|
4
|
+
class Command < Nonnative::Service
|
5
|
+
def start
|
6
|
+
unless command_exists?
|
7
|
+
@pid = command_spawn
|
8
|
+
wait_start
|
9
|
+
end
|
10
|
+
|
11
|
+
pid
|
12
|
+
end
|
13
|
+
|
14
|
+
def stop
|
15
|
+
if command_exists?
|
16
|
+
command_kill
|
17
|
+
wait_stop
|
18
|
+
end
|
19
|
+
|
20
|
+
pid
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def wait_stop
|
26
|
+
timeout.perform do
|
27
|
+
Process.waitpid2(pid)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :pid
|
34
|
+
|
35
|
+
def command_kill
|
36
|
+
signal = Signal.list[service.signal || 'INT'] || Signal.list['INT']
|
37
|
+
Process.kill(signal, pid)
|
38
|
+
end
|
39
|
+
|
40
|
+
def command_spawn
|
41
|
+
spawn(service.command, %i[out err] => [service.log, 'a'])
|
42
|
+
end
|
43
|
+
|
44
|
+
def command_exists?
|
45
|
+
return false if pid.nil?
|
46
|
+
|
47
|
+
signal = Signal.list['EXIT']
|
48
|
+
Process.kill(signal, pid)
|
49
|
+
true
|
50
|
+
rescue Errno::ESRCH
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nonnative
|
4
|
+
class Configuration
|
5
|
+
class << self
|
6
|
+
def load_file(path)
|
7
|
+
file = YAML.load_file(path)
|
8
|
+
|
9
|
+
new.tap do |c|
|
10
|
+
c.strategy = file['strategy']
|
11
|
+
|
12
|
+
processes(file, c)
|
13
|
+
servers(file, c)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def processes(file, config)
|
20
|
+
processes = file['processes'] || []
|
21
|
+
processes.each do |fd|
|
22
|
+
config.process do |d|
|
23
|
+
d.name = fd['name']
|
24
|
+
d.command = fd['command']
|
25
|
+
d.timeout = fd['timeout']
|
26
|
+
d.port = fd['port']
|
27
|
+
d.log = fd['log']
|
28
|
+
d.signal = fd['signal']
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def servers(file, config)
|
34
|
+
servers = file['servers'] || []
|
35
|
+
servers.each do |fd|
|
36
|
+
config.server do |s|
|
37
|
+
s.name = fd['name']
|
38
|
+
s.klass = Object.const_get(fd['klass'])
|
39
|
+
s.timeout = fd['timeout']
|
40
|
+
s.port = fd['port']
|
41
|
+
s.log = fd['log']
|
42
|
+
|
43
|
+
proxy = fd['proxy']
|
44
|
+
|
45
|
+
if proxy
|
46
|
+
s.proxy = {
|
47
|
+
type: proxy['type'],
|
48
|
+
port: proxy['port'],
|
49
|
+
log: proxy['log'],
|
50
|
+
options: proxy['options']
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize
|
59
|
+
self.strategy = :before
|
60
|
+
self.processes = []
|
61
|
+
self.servers = []
|
62
|
+
end
|
63
|
+
|
64
|
+
attr_accessor :strategy, :processes, :servers
|
65
|
+
|
66
|
+
def process
|
67
|
+
process = Nonnative::ConfigurationProcess.new
|
68
|
+
yield process
|
69
|
+
|
70
|
+
processes << process
|
71
|
+
end
|
72
|
+
|
73
|
+
def server
|
74
|
+
server = Nonnative::ConfigurationServer.new
|
75
|
+
yield server
|
76
|
+
|
77
|
+
servers << server
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nonnative
|
4
|
+
class ConfigurationServer
|
5
|
+
attr_accessor :name, :klass, :timeout, :port, :log
|
6
|
+
attr_reader :proxy
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@proxy = Nonnative::ConfigurationProxy.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def proxy=(value)
|
13
|
+
proxy.type = value[:type]
|
14
|
+
proxy.port = value[:port]
|
15
|
+
proxy.log = value[:log]
|
16
|
+
proxy.options = value[:options]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nonnative
|
4
|
+
class FaultInjectionProxy < Nonnative::Proxy
|
5
|
+
def initialize(service)
|
6
|
+
@connections = Concurrent::Hash.new
|
7
|
+
@logger = Logger.new(service.proxy.log)
|
8
|
+
@mutex = Mutex.new
|
9
|
+
@state = :none
|
10
|
+
|
11
|
+
super service
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
@tcp_server = ::TCPServer.new('0.0.0.0', service.port)
|
16
|
+
@thread = Thread.new { perform_start }
|
17
|
+
end
|
18
|
+
|
19
|
+
def stop
|
20
|
+
thread.terminate
|
21
|
+
tcp_server.close
|
22
|
+
end
|
23
|
+
|
24
|
+
def close_all
|
25
|
+
apply_state :close_all
|
26
|
+
end
|
27
|
+
|
28
|
+
def delay
|
29
|
+
apply_state :delay
|
30
|
+
end
|
31
|
+
|
32
|
+
def invalid_data
|
33
|
+
apply_state :invalid_data
|
34
|
+
end
|
35
|
+
|
36
|
+
def reset
|
37
|
+
apply_state :none
|
38
|
+
end
|
39
|
+
|
40
|
+
def port
|
41
|
+
service.proxy.port
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :tcp_server, :thread, :connections, :mutex, :state, :logger
|
47
|
+
|
48
|
+
def perform_start
|
49
|
+
loop do
|
50
|
+
thread = Thread.start(tcp_server.accept) do |local_socket|
|
51
|
+
id = Thread.current.object_id
|
52
|
+
|
53
|
+
logger.info "started connection for #{id} with socket #{local_socket.inspect}"
|
54
|
+
|
55
|
+
connect local_socket
|
56
|
+
connections.delete(id)
|
57
|
+
|
58
|
+
logger.info "finished connection for #{id} with socket #{local_socket.inspect}"
|
59
|
+
end
|
60
|
+
|
61
|
+
thread.report_on_exception = false
|
62
|
+
connections[thread.object_id] = thread
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def connect(local_socket)
|
67
|
+
SocketPairFactory.create(read_state, service.proxy, logger).connect(local_socket)
|
68
|
+
end
|
69
|
+
|
70
|
+
def apply_state(state)
|
71
|
+
mutex.synchronize { @state = state }
|
72
|
+
end
|
73
|
+
|
74
|
+
def read_state
|
75
|
+
mutex.synchronize { state }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nonnative
|
4
|
+
class GRPCServer < Nonnative::Server
|
5
|
+
def initialize(service)
|
6
|
+
@server = GRPC::RpcServer.new
|
7
|
+
server.handle(svc)
|
8
|
+
|
9
|
+
# Unfortunately gRPC has only one logger so the first server wins.
|
10
|
+
GRPC.define_singleton_method(:logger) do
|
11
|
+
@logger ||= Logger.new(service.log)
|
12
|
+
end
|
13
|
+
|
14
|
+
super service
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def perform_start
|
20
|
+
server.add_http2_port("0.0.0.0:#{proxy.port}", :this_port_is_insecure)
|
21
|
+
server.run
|
22
|
+
end
|
23
|
+
|
24
|
+
def perform_stop
|
25
|
+
server.stop
|
26
|
+
end
|
27
|
+
|
28
|
+
def wait_start
|
29
|
+
timeout.perform do
|
30
|
+
super until server.running?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def wait_stop
|
35
|
+
timeout.perform do
|
36
|
+
super until server.stopped?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :server
|
43
|
+
end
|
44
|
+
end
|