nonnative 1.27.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +38 -0
  3. data/.config/cucumber.yml +1 -0
  4. data/.editorconfig +24 -0
  5. data/.gitignore +11 -0
  6. data/.rubocop.yml +87 -0
  7. data/.ruby-version +1 -0
  8. data/Gemfile +8 -0
  9. data/Gemfile.lock +202 -0
  10. data/LICENSE +24 -0
  11. data/Makefile +27 -0
  12. data/README.md +366 -0
  13. data/Rakefile +4 -0
  14. data/bin/console +15 -0
  15. data/bin/setup +9 -0
  16. data/lib/nonnative.rb +89 -0
  17. data/lib/nonnative/before.rb +11 -0
  18. data/lib/nonnative/close_all_socket_pair.rb +9 -0
  19. data/lib/nonnative/command.rb +54 -0
  20. data/lib/nonnative/configuration.rb +80 -0
  21. data/lib/nonnative/configuration_process.rb +7 -0
  22. data/lib/nonnative/configuration_proxy.rb +13 -0
  23. data/lib/nonnative/configuration_server.rb +19 -0
  24. data/lib/nonnative/delay_socket_pair.rb +12 -0
  25. data/lib/nonnative/error.rb +6 -0
  26. data/lib/nonnative/fault_injection_proxy.rb +78 -0
  27. data/lib/nonnative/grpc_server.rb +44 -0
  28. data/lib/nonnative/http_client.rb +53 -0
  29. data/lib/nonnative/http_server.rb +40 -0
  30. data/lib/nonnative/invalid_data_socket_pair.rb +11 -0
  31. data/lib/nonnative/manual.rb +3 -0
  32. data/lib/nonnative/no_proxy.rb +17 -0
  33. data/lib/nonnative/observability.rb +13 -0
  34. data/lib/nonnative/pool.rb +60 -0
  35. data/lib/nonnative/port.rb +44 -0
  36. data/lib/nonnative/proxy.rb +13 -0
  37. data/lib/nonnative/proxy_factory.rb +18 -0
  38. data/lib/nonnative/server.rb +41 -0
  39. data/lib/nonnative/service.rb +26 -0
  40. data/lib/nonnative/socket_pair.rb +53 -0
  41. data/lib/nonnative/socket_pair_factory.rb +22 -0
  42. data/lib/nonnative/start_error.rb +6 -0
  43. data/lib/nonnative/startup.rb +7 -0
  44. data/lib/nonnative/stop_error.rb +6 -0
  45. data/lib/nonnative/timeout.rb +21 -0
  46. data/lib/nonnative/version.rb +5 -0
  47. data/nonnative.gemspec +42 -0
  48. data/sonar-project.properties +4 -0
  49. metadata +340 -0
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ task default: :spec
@@ -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__)
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ gem install bundler
7
+ bundle config set path 'vendor/bundle'
8
+ bundle check || bundle install
9
+ bundle clean --force
@@ -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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber'
4
+
5
+ Before do
6
+ Nonnative.start
7
+ end
8
+
9
+ After do
10
+ Nonnative.stop
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class CloseAllSocketPair < SocketPair
5
+ def connect(local_socket)
6
+ local_socket.close
7
+ end
8
+ end
9
+ 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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class ConfigurationProcess
5
+ attr_accessor :name, :command, :timeout, :port, :log, :signal
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class ConfigurationProxy
5
+ attr_accessor :type, :port, :log, :options
6
+
7
+ def initialize
8
+ self.type = 'none'
9
+ self.port = 0
10
+ self.options = {}
11
+ end
12
+ end
13
+ 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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class DelaySocketPair < SocketPair
5
+ def read(socket)
6
+ duration = proxy.options.dig(:delay) || 2
7
+ sleep duration
8
+
9
+ super socket
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class Error < StandardError
5
+ end
6
+ 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