nonnative 1.27.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|