invoker 1.0.4 → 1.1.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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +30 -0
  4. data/.travis.yml +1 -0
  5. data/Gemfile +1 -0
  6. data/bin/invoker +4 -8
  7. data/invoker.gemspec +10 -11
  8. data/lib/invoker.rb +95 -21
  9. data/lib/invoker/cli.rb +126 -0
  10. data/lib/invoker/cli/pinger.rb +23 -0
  11. data/lib/invoker/cli/question.rb +15 -0
  12. data/lib/invoker/cli/tail.rb +34 -0
  13. data/lib/invoker/cli/tail_watcher.rb +34 -0
  14. data/lib/invoker/command_worker.rb +28 -2
  15. data/lib/invoker/commander.rb +34 -236
  16. data/lib/invoker/config.rb +5 -0
  17. data/lib/invoker/daemon.rb +126 -0
  18. data/lib/invoker/dns_cache.rb +23 -0
  19. data/lib/invoker/errors.rb +1 -0
  20. data/lib/invoker/ipc.rb +45 -0
  21. data/lib/invoker/ipc/add_command.rb +12 -0
  22. data/lib/invoker/ipc/add_http_command.rb +10 -0
  23. data/lib/invoker/ipc/base_command.rb +24 -0
  24. data/lib/invoker/ipc/client_handler.rb +26 -0
  25. data/lib/invoker/ipc/dns_check_command.rb +16 -0
  26. data/lib/invoker/ipc/list_command.rb +11 -0
  27. data/lib/invoker/ipc/message.rb +170 -0
  28. data/lib/invoker/ipc/message/list_response.rb +33 -0
  29. data/lib/invoker/ipc/message/tail_response.rb +10 -0
  30. data/lib/invoker/ipc/ping_command.rb +10 -0
  31. data/lib/invoker/ipc/reload_command.rb +12 -0
  32. data/lib/invoker/ipc/remove_command.rb +12 -0
  33. data/lib/invoker/{command_listener → ipc}/server.rb +6 -11
  34. data/lib/invoker/ipc/tail_command.rb +11 -0
  35. data/lib/invoker/ipc/unix_client.rb +60 -0
  36. data/lib/invoker/parsers/config.rb +1 -0
  37. data/lib/invoker/power/balancer.rb +17 -7
  38. data/lib/invoker/power/config.rb +6 -3
  39. data/lib/invoker/power/dns.rb +22 -21
  40. data/lib/invoker/power/http_response.rb +1 -1
  41. data/lib/invoker/power/power.rb +3 -0
  42. data/lib/invoker/power/powerup.rb +3 -2
  43. data/lib/invoker/power/setup.rb +6 -4
  44. data/lib/invoker/process_manager.rb +187 -0
  45. data/lib/invoker/process_printer.rb +27 -38
  46. data/lib/invoker/reactor.rb +19 -38
  47. data/lib/invoker/reactor/reader.rb +53 -0
  48. data/lib/invoker/version.rb +1 -1
  49. data/readme.md +1 -1
  50. data/spec/invoker/cli/pinger_spec.rb +22 -0
  51. data/spec/invoker/cli/tail_watcher_spec.rb +39 -0
  52. data/spec/invoker/cli_spec.rb +27 -0
  53. data/spec/invoker/command_worker_spec.rb +30 -0
  54. data/spec/invoker/commander_spec.rb +57 -127
  55. data/spec/invoker/config_spec.rb +21 -0
  56. data/spec/invoker/daemon_spec.rb +34 -0
  57. data/spec/invoker/invoker_spec.rb +31 -0
  58. data/spec/invoker/ipc/client_handler_spec.rb +44 -0
  59. data/spec/invoker/ipc/dns_check_command_spec.rb +32 -0
  60. data/spec/invoker/ipc/message/list_response_spec.rb +22 -0
  61. data/spec/invoker/ipc/message_spec.rb +45 -0
  62. data/spec/invoker/ipc/unix_client_spec.rb +29 -0
  63. data/spec/invoker/power/setup_spec.rb +1 -1
  64. data/spec/invoker/process_manager_spec.rb +98 -0
  65. data/spec/invoker/reactor_spec.rb +6 -0
  66. data/spec/spec_helper.rb +15 -24
  67. metadata +107 -77
  68. data/lib/invoker/command_listener/client.rb +0 -45
  69. data/lib/invoker/parsers/option_parser.rb +0 -106
  70. data/lib/invoker/power.rb +0 -7
  71. data/lib/invoker/runner.rb +0 -98
  72. data/spec/invoker/command_listener/client_spec.rb +0 -52
@@ -146,4 +146,25 @@ web: bundle exec rails s -p $PORT
146
146
  end
147
147
  end
148
148
  end
149
+
150
+ describe "Copy of DNS information" do
151
+ it "should allow copy of DNS information" do
152
+ begin
153
+ File.open("/tmp/Procfile", "w") {|fl|
154
+ fl.write <<-EOD
155
+ web: bundle exec rails s -p $PORT
156
+ EOD
157
+ }
158
+ Invoker.load_invoker_config("/tmp/Procfile", 9000)
159
+ dns_cache = Invoker::DNSCache.new(Invoker.config)
160
+
161
+ expect(dns_cache.dns_data).to_not be_empty
162
+ expect(dns_cache.dns_data['web']).to_not be_empty
163
+ expect(dns_cache.dns_data['web']['port']).to eql 9001
164
+ ensure
165
+ File.delete("/tmp/Procfile")
166
+ end
167
+ end
168
+ end
169
+
149
170
  end
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+
3
+ describe Invoker::Daemon do
4
+ let(:daemon) { Invoker::Daemon.new}
5
+
6
+ describe "#start" do
7
+ context "when daemon is aleady running" do
8
+ it "exits without any error" do
9
+ daemon.expects(:running?).returns(true)
10
+ begin
11
+ daemon.start
12
+ rescue SystemExit => e
13
+ expect(e.status).to be(0)
14
+ end
15
+ end
16
+ end
17
+
18
+ context "when daemon is not running" do
19
+ it "starts the daemon" do
20
+ daemon.expects(:dead?).returns(false)
21
+ daemon.expects(:running?).returns(false)
22
+ daemon.expects(:daemonize)
23
+ daemon.start
24
+ end
25
+ end
26
+ end
27
+
28
+ describe "#stop" do
29
+ it "stops the daemon" do
30
+ daemon.expects(:kill_process)
31
+ daemon.stop
32
+ end
33
+ end
34
+ end
@@ -49,5 +49,36 @@ describe "Invoker" do
49
49
  Invoker.can_run_balancer?(false)
50
50
  end
51
51
  end
52
+
53
+ describe "#setup_config_location" do
54
+ before do
55
+ Dir.stubs(:home).returns('/tmp')
56
+ @config_location = File.join('/tmp', '.invoker')
57
+ FileUtils.rm_rf(@config_location)
58
+ end
59
+
60
+ context "when the old config file does not exist" do
61
+ it "creates the new config directory" do
62
+ Invoker.setup_config_location
63
+ expect(Dir.exist?(@config_location)).to be_true
64
+ end
65
+ end
66
+
67
+ context "when the old config file exists" do
68
+ before do
69
+ File.open(@config_location, 'w') do |file|
70
+ file.write('invoker config')
71
+ end
72
+ end
73
+
74
+ it "moves the file to the new directory" do
75
+ Invoker.setup_config_location
76
+ expect(Dir.exist?(@config_location)).to be_true
77
+ new_config_file = File.join(@config_location, 'config')
78
+ expect(File.exist?(new_config_file)).to be_true
79
+ expect(File.read(new_config_file)).to match('invoker config')
80
+ end
81
+ end
82
+ end
52
83
  end
53
84
 
@@ -0,0 +1,44 @@
1
+ require "spec_helper"
2
+
3
+ describe Invoker::IPC::ClientHandler do
4
+ let(:client_socket) { StringIO.new }
5
+ let(:client) { Invoker::IPC::ClientHandler.new(client_socket) }
6
+
7
+ describe "add command" do
8
+ let(:message_object) { MM::Add.new(process_name: 'foo') }
9
+ it "should run if read from socket" do
10
+ invoker_commander.expects(:on_next_tick).with("foo")
11
+ client_socket.string = message_object.encoded_message
12
+
13
+ client.read_and_execute
14
+ end
15
+ end
16
+
17
+ describe "remove command" do
18
+ it "with specific signal" do
19
+ message_object = MM::Remove.new(process_name: 'foo', signal: 'INT')
20
+ invoker_commander.expects(:on_next_tick)
21
+ client_socket.string = message_object.encoded_message
22
+
23
+ client.read_and_execute
24
+ end
25
+
26
+ it "with default signal" do
27
+ message_object = MM::Remove.new(process_name: 'foo')
28
+ invoker_commander.expects(:on_next_tick)
29
+ client_socket.string = message_object.encoded_message
30
+
31
+ client.read_and_execute
32
+ end
33
+ end
34
+
35
+ describe "add_http command" do
36
+ let(:message_object) { MM::AddHttp.new(process_name: 'foo', port: 9000)}
37
+ it "adds the process name and port to dns cache" do
38
+ invoker_dns_cache.expects(:add).with('foo', 9000)
39
+ client_socket.string = message_object.encoded_message
40
+
41
+ client.read_and_execute
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Invoker::IPC::DnsCheckCommand do
4
+ let(:client_socket) { StringIO.new }
5
+ let(:client) { Invoker::IPC::ClientHandler.new(client_socket) }
6
+
7
+ describe "dns check for valid process" do
8
+ let(:message_object) { MM::DnsCheck.new(process_name: 'lolbro') }
9
+ it "should response with dns check response" do
10
+ invoker_dns_cache.expects(:[]).returns('port' => 9000)
11
+ client_socket.string = message_object.encoded_message
12
+
13
+ client.read_and_execute
14
+
15
+ dns_check_response = client_socket.string
16
+ expect(dns_check_response).to match(/9000/)
17
+ end
18
+ end
19
+
20
+ describe "dns check for invalid process" do
21
+ let(:message_object) { MM::DnsCheck.new(process_name: 'foo') }
22
+ it "should response with dns check response" do
23
+ invoker_dns_cache.expects(:[]).returns('port' => nil)
24
+ client_socket.string = message_object.encoded_message
25
+
26
+ client.read_and_execute
27
+
28
+ dns_check_response = client_socket.string
29
+ expect(dns_check_response).to match(/null/)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+
3
+ describe MM::ListResponse do
4
+ context "serializing a response" do
5
+ let(:process_array) do
6
+ [
7
+ { shell_command: 'foo', process_name: 'foo', dir: '/tmp', pid: 100 },
8
+ { shell_command: 'bar', process_name: 'bar', dir: '/tmp', pid: 200 }
9
+ ]
10
+ end
11
+
12
+ let(:message) { MM::ListResponse.new(processes: process_array) }
13
+
14
+ it "should prepare proper json" do
15
+ json_hash = message.as_json
16
+ expect(json_hash[:type]).to eql "list_response"
17
+ expect(json_hash[:processes]).to have(2).elements
18
+ expect(json_hash[:processes][0]).to be_a(Hash)
19
+ expect(json_hash[:processes][1]).to be_a(Hash)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,45 @@
1
+ require "spec_helper"
2
+
3
+ describe Invoker::IPC::Message do
4
+ describe "test equality of objects" do
5
+ context "for simple messages" do
6
+ let(:message) { MM::Add.new(process_name: 'foo') }
7
+
8
+ it "object should be reported same if same value" do
9
+ m2 = MM::Add.new(process_name: 'foo')
10
+ expect(message).to eql m2
11
+ end
12
+
13
+ it "should report objects to be not eql if differnt value" do
14
+ m2 = MM::Add.new(process_name: 'bar')
15
+ expect(message).to_not eql m2
16
+ end
17
+ end
18
+
19
+ context "for nested messages" do
20
+ let(:process_array) do
21
+ [
22
+ { shell_command: 'foo', process_name: 'foo', dir: '/tmp', pid: 100 },
23
+ { shell_command: 'bar', process_name: 'bar', dir: '/tmp', pid: 200 }
24
+ ]
25
+ end
26
+
27
+ let(:message) { MM::ListResponse.new(processes: process_array) }
28
+
29
+ it "should report eql for eql objects" do
30
+ m2 = MM::ListResponse.new(processes: process_array)
31
+ expect(message).to eql m2
32
+ end
33
+
34
+ it "should report not equal for different objects" do
35
+ another_process_array = [
36
+ { shell_command: 'baz', process_name: 'foo', dir: '/tmp', pid: 100 },
37
+ { shell_command: 'bar', process_name: 'bar', dir: '/tmp', pid: 200 }
38
+ ]
39
+
40
+ m2 = MM::ListResponse.new(processes: another_process_array)
41
+ expect(message).to_not eql m2
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ describe Invoker::IPC::UnixClient do
4
+ let(:unix_client) { described_class.new }
5
+ let(:socket) { StringIO.new }
6
+
7
+ describe "serializing a " do
8
+ it "list request should work" do
9
+ unix_client.expects(:open_client_socket).yields(socket)
10
+ unix_client.send_command("list")
11
+
12
+ expect(socket.string).to match(/list/)
13
+ end
14
+
15
+ it "add request should work" do
16
+ unix_client.expects(:open_client_socket).yields(socket)
17
+ unix_client.send_command("add", process_name: "hello")
18
+
19
+ expect(socket.string).to match(/hello/)
20
+ end
21
+ end
22
+
23
+ describe ".send_command" do
24
+ it "calls the send_command instance method" do
25
+ Invoker::IPC::UnixClient.any_instance.expects(:send_command).once
26
+ Invoker::IPC::UnixClient.send_command("list")
27
+ end
28
+ end
29
+ end
@@ -72,7 +72,7 @@ describe "Setup" do
72
72
  it "should uninstall firewall rules and remove all files created by setup" do
73
73
  setup = Invoker::Power::Setup.new
74
74
 
75
- HighLine.any_instance.expects(:agree).returns(true)
75
+ Invoker::CLI::Question.expects(:agree).returns(true)
76
76
  setup.expects(:remove_resolver_file).once
77
77
  setup.expects(:unload_firewall_rule).with(true).once
78
78
  setup.expects(:flush_dns_rules).once
@@ -0,0 +1,98 @@
1
+ require "spec_helper"
2
+
3
+ describe Invoker::ProcessManager do
4
+ let(:process_manager) { Invoker::ProcessManager.new }
5
+ describe "#start_process_by_name" do
6
+ it "should find command by label and start it, if found" do
7
+ invoker_config.stubs(:processes).returns([OpenStruct.new(:label => "resque", :cmd => "foo", :dir => "bar")])
8
+ invoker_config.expects(:process).returns(OpenStruct.new(:label => "resque", :cmd => "foo", :dir => "bar"))
9
+ process_manager.expects(:start_process).returns(true)
10
+
11
+ process_manager.start_process_by_name("resque")
12
+ end
13
+
14
+ it "should not start already running process" do
15
+ process_manager.workers.expects(:[]).returns(OpenStruct.new(:pid => "bogus"))
16
+ expect(process_manager.start_process_by_name("resque")).to be_false
17
+ end
18
+ end
19
+
20
+ describe "#stop_process" do
21
+ let(:message) { MM::Remove.new(options) }
22
+ describe "when a worker is found" do
23
+ before do
24
+ process_manager.workers.expects(:[]).returns(OpenStruct.new(:pid => "bogus"))
25
+ end
26
+
27
+ describe "if a signal is specified" do
28
+ let(:options) { { process_name: 'bogus', signal: 'HUP' } }
29
+ it "should use that signal to kill the worker" do
30
+ process_manager.expects(:process_kill).with("bogus", "HUP").returns(true)
31
+ expect(process_manager.stop_process(message)).to be_true
32
+ end
33
+ end
34
+
35
+ describe "if no signal is specified" do
36
+ let(:options) { { process_name: 'bogus' } }
37
+ it "should use INT signal" do
38
+ process_manager.expects(:process_kill).with("bogus", "INT").returns(true)
39
+ expect(process_manager.stop_process(message)).to be_true
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "when no worker is found" do
45
+ let(:options) { { process_name: 'bogus', signal: 'HUP' } }
46
+ before do
47
+ process_manager.workers.expects(:[]).returns(nil)
48
+ end
49
+
50
+ it "should not kill anything" do
51
+ process_manager.expects(:process_kill).never
52
+ process_manager.stop_process(message)
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "#load_env" do
58
+ it "should load .env file from the specified directory" do
59
+ dir = "/tmp"
60
+ begin
61
+ env_file = File.new("#{dir}/.env", "w")
62
+ env_data =<<-EOD
63
+ FOO=foo
64
+ BAR=bar
65
+ EOD
66
+ env_file.write(env_data)
67
+ env_file.close
68
+ env_options = process_manager.load_env(dir)
69
+ expect(env_options).to include("FOO" => "foo", "BAR" => "bar")
70
+ ensure
71
+ File.delete(env_file.path)
72
+ end
73
+ end
74
+
75
+ it "should default to current directory if no directory is specified" do
76
+ dir = ENV["HOME"]
77
+ ENV.stubs(:[]).with("PWD").returns(dir)
78
+ begin
79
+ env_file = File.new("#{dir}/.env", "w")
80
+ env_data =<<-EOD
81
+ FOO=bar
82
+ BAR=foo
83
+ EOD
84
+ env_file.write(env_data)
85
+ env_file.close
86
+ env_options = process_manager.load_env
87
+ expect(env_options).to include("FOO" => "bar", "BAR" => "foo")
88
+ ensure
89
+ File.delete(env_file.path)
90
+ end
91
+ end
92
+
93
+ it "should return empty hash if there is no .env file" do
94
+ dir = "/tmp"
95
+ expect(process_manager.load_env(dir)).to eq({})
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,6 @@
1
+ require "spec_helper"
2
+
3
+ describe Invoker::Reactor do
4
+ describe "writing to socket" do
5
+ end
6
+ end
@@ -1,19 +1,15 @@
1
1
  require "pry"
2
- require 'coveralls'
3
- Coveralls.wear!
2
+ require "simplecov"
3
+ SimpleCov.start do
4
+ add_filter "/spec/"
5
+ end
6
+
4
7
 
5
8
  require "invoker"
9
+ require "invoker/power/power"
10
+ MM = Invoker::IPC::Message
6
11
 
7
- # This file was generated by the `rspec --init` command. Conventionally, all
8
- # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
9
- # Require this file using `require "spec_helper"` to ensure that it is only
10
- # loaded once.
11
- #
12
- # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
13
12
  RSpec.configure do |config|
14
- config.expect_with :rspec do |c|
15
- c.syntax = :expect
16
- end
17
13
  config.treat_symbols_as_metadata_keys_with_true_values = true
18
14
  config.run_all_when_everything_filtered = true
19
15
  config.filter_run :focus
@@ -23,7 +19,8 @@ RSpec.configure do |config|
23
19
  @original_verbosity = $VERBOSE
24
20
  $VERBOSE = nil
25
21
  @old_config = Invoker::Power::Config::CONFIG_LOCATION
26
- Invoker::Power::Config.const_set(:CONFIG_LOCATION, "/tmp/.invoker")
22
+ Invoker::Power::Config.const_set(:CONFIG_LOCATION, "/tmp/.invoker/config")
23
+ FileUtils.mkdir("/tmp/.invoker") unless Dir.exist?("/tmp/.invoker")
27
24
 
28
25
  File.exists?(Invoker::Power::Config::CONFIG_LOCATION) &&
29
26
  File.delete(Invoker::Power::Config::CONFIG_LOCATION)
@@ -65,19 +62,13 @@ end
65
62
  ENV["INVOKER_TESTS"] = "true"
66
63
 
67
64
  def invoker_config
68
- if Invoker.const_defined?(:CONFIG)
69
- Invoker::CONFIG
70
- else
71
- Invoker.const_set(:CONFIG, mock())
72
- Invoker::CONFIG
73
- end
65
+ Invoker.config ||= mock
74
66
  end
75
67
 
76
68
  def invoker_commander
77
- if Invoker.const_defined?(:COMMANDER)
78
- Invoker::COMMANDER
79
- else
80
- Invoker.const_set(:COMMANDER, mock())
81
- Invoker::COMMANDER
82
- end
69
+ Invoker.commander ||= mock
70
+ end
71
+
72
+ def invoker_dns_cache
73
+ Invoker.dns_cache ||= mock
83
74
  end