invoker 1.4.1 → 1.5.1

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.rubocop.yml +0 -1
  4. data/.travis.yml +4 -2
  5. data/Dockerfile +7 -0
  6. data/Gemfile +3 -0
  7. data/Rakefile +8 -0
  8. data/examples/hello_sinatra.rb +26 -0
  9. data/examples/sample.ini +3 -0
  10. data/invoker.gemspec +2 -1
  11. data/lib/invoker.rb +20 -3
  12. data/lib/invoker/cli.rb +32 -6
  13. data/lib/invoker/dns_cache.rb +2 -2
  14. data/lib/invoker/ipc/add_http_command.rb +1 -1
  15. data/lib/invoker/ipc/dns_check_command.rb +2 -1
  16. data/lib/invoker/ipc/message.rb +2 -2
  17. data/lib/invoker/parsers/config.rb +4 -1
  18. data/lib/invoker/power/balancer.rb +16 -4
  19. data/lib/invoker/power/config.rb +3 -2
  20. data/lib/invoker/power/dns.rb +1 -1
  21. data/lib/invoker/power/pf_migrate.rb +1 -1
  22. data/lib/invoker/power/setup.rb +43 -4
  23. data/lib/invoker/power/setup/distro/arch.rb +1 -4
  24. data/lib/invoker/power/setup/distro/base.rb +23 -21
  25. data/lib/invoker/power/setup/distro/debian.rb +1 -1
  26. data/lib/invoker/power/setup/distro/opensuse.rb +11 -0
  27. data/lib/invoker/power/setup/distro/redhat.rb +1 -7
  28. data/lib/invoker/power/setup/files/invoker_forwarder.sh.erb +17 -0
  29. data/lib/invoker/power/setup/files/socat_invoker.service +12 -0
  30. data/lib/invoker/power/setup/linux_setup.rb +46 -29
  31. data/lib/invoker/power/setup/osx_setup.rb +16 -28
  32. data/lib/invoker/power/url_rewriter.rb +6 -3
  33. data/lib/invoker/process_manager.rb +13 -7
  34. data/lib/invoker/version.rb +1 -1
  35. data/readme.md +4 -4
  36. data/spec/invoker/commander_spec.rb +19 -8
  37. data/spec/invoker/config_spec.rb +22 -27
  38. data/spec/invoker/invoker_spec.rb +2 -1
  39. data/spec/invoker/ipc/client_handler_spec.rb +11 -1
  40. data/spec/invoker/power/config_spec.rb +2 -1
  41. data/spec/invoker/power/pf_migrate_spec.rb +7 -0
  42. data/spec/invoker/power/setup/linux_setup_spec.rb +57 -9
  43. data/spec/invoker/power/setup/osx_setup_spec.rb +22 -8
  44. data/spec/invoker/power/url_rewriter_spec.rb +33 -1
  45. data/spec/invoker/power/web_sockets_spec.rb +61 -0
  46. data/spec/invoker/process_manager_spec.rb +34 -2
  47. data/spec/spec_helper.rb +12 -16
  48. metadata +27 -35
  49. data/spec/support/mock_setup_file.rb +0 -64
@@ -134,59 +134,53 @@ command = ls
134
134
  end
135
135
  end
136
136
 
137
- describe "loading power config" do
137
+ describe "loading power config", fakefs: true do
138
138
  before do
139
- @file = Tempfile.new(["config", ".ini"])
139
+ FileUtils.mkdir_p('/tmp')
140
+ FileUtils.mkdir_p(inv_conf_dir)
141
+ File.open("/tmp/foo.ini", "w") { |fl| fl.write("") }
140
142
  end
141
143
 
142
144
  it "does not load config if platform is darwin but there is no power config file" do
143
145
  Invoker::Power::Config.expects(:load_config).never
144
- Invoker::Parsers::Config.new(@file.path, 9000)
146
+ Invoker::Parsers::Config.new("/tmp/foo.ini", 9000)
145
147
  end
146
148
 
147
149
  it "loads config if platform is darwin and power config file exists" do
148
150
  File.open(Invoker::Power::Config.config_file, "w") { |fl| fl.puts "sample" }
149
151
  Invoker::Power::Config.expects(:load_config).once
150
- Invoker::Parsers::Config.new(@file.path, 9000)
152
+ Invoker::Parsers::Config.new("/tmp/foo.ini", 9000)
151
153
  end
152
154
  end
153
155
 
154
156
  describe "Procfile" do
155
157
  it "should load Procfiles and create config object" do
156
- begin
157
- File.open("/tmp/Procfile", "w") {|fl|
158
- fl.write <<-EOD
158
+ File.open("/tmp/Procfile", "w") {|fl|
159
+ fl.write <<-EOD
159
160
  web: bundle exec rails s -p $PORT
160
161
  EOD
161
- }
162
- config = Invoker::Parsers::Config.new("/tmp/Procfile", 9000)
163
- command1 = config.processes.first
162
+ }
163
+ config = Invoker::Parsers::Config.new("/tmp/Procfile", 9000)
164
+ command1 = config.processes.first
164
165
 
165
- expect(command1.port).to eq(9000)
166
- expect(command1.cmd).to match(/bundle exec rails/)
167
- ensure
168
- File.delete("/tmp/Procfile")
169
- end
166
+ expect(command1.port).to eq(9000)
167
+ expect(command1.cmd).to match(/bundle exec rails/)
170
168
  end
171
169
  end
172
170
 
173
171
  describe "Copy of DNS information" do
174
172
  it "should allow copy of DNS information" do
175
- begin
176
- File.open("/tmp/Procfile", "w") {|fl|
177
- fl.write <<-EOD
173
+ File.open("/tmp/Procfile", "w") {|fl|
174
+ fl.write <<-EOD
178
175
  web: bundle exec rails s -p $PORT
179
176
  EOD
180
- }
181
- Invoker.load_invoker_config("/tmp/Procfile", 9000)
182
- dns_cache = Invoker::DNSCache.new(Invoker.config)
177
+ }
178
+ Invoker.load_invoker_config("/tmp/Procfile", 9000)
179
+ dns_cache = Invoker::DNSCache.new(Invoker.config)
183
180
 
184
- expect(dns_cache.dns_data).to_not be_empty
185
- expect(dns_cache.dns_data['web']).to_not be_empty
186
- expect(dns_cache.dns_data['web']['port']).to eql 9000
187
- ensure
188
- File.delete("/tmp/Procfile")
189
- end
181
+ expect(dns_cache.dns_data).to_not be_empty
182
+ expect(dns_cache.dns_data['web']).to_not be_empty
183
+ expect(dns_cache.dns_data['web']['port']).to eql 9000
190
184
  end
191
185
  end
192
186
 
@@ -227,6 +221,7 @@ command = bundle exec rails s -p $PORT
227
221
  describe "global config file" do
228
222
  it "should use global config file if available" do
229
223
  begin
224
+ FileUtils.mkdir_p(Invoker::Power::Config.config_dir)
230
225
  filename = "#{Invoker::Power::Config.config_dir}/foo.ini"
231
226
  file = File.open(filename, "w")
232
227
  config_data =<<-EOD
@@ -13,7 +13,8 @@ describe "Invoker" do
13
13
  end
14
14
  end
15
15
 
16
- describe "#can_run_balancer?" do
16
+ describe "#can_run_balancer?", fakefs: true do
17
+ before { FileUtils.mkdir_p(Invoker::Power::Config.config_dir) }
17
18
  it "should return false if setup command was not run" do
18
19
  expect(Invoker.can_run_balancer?).to be_falsey
19
20
  end
@@ -35,7 +35,17 @@ describe Invoker::IPC::ClientHandler do
35
35
  describe "add_http command" do
36
36
  let(:message_object) { MM::AddHttp.new(process_name: 'foo', port: 9000)}
37
37
  it "adds the process name and port to dns cache" do
38
- invoker_dns_cache.expects(:add).with('foo', 9000)
38
+ invoker_dns_cache.expects(:add).with('foo', 9000, nil)
39
+ client_socket.string = message_object.encoded_message
40
+
41
+ client.read_and_execute
42
+ end
43
+ end
44
+
45
+ describe "add_http command with optional ip" do
46
+ let(:message_object) { MM::AddHttp.new(process_name: 'foo', port: 9000, ip: '192.168.0.1')}
47
+ it "adds the process name, port and host ip to dns cache" do
48
+ invoker_dns_cache.expects(:add).with('foo', 9000, '192.168.0.1')
39
49
  client_socket.string = message_object.encoded_message
40
50
 
41
51
  client.read_and_execute
@@ -1,8 +1,9 @@
1
1
  require "spec_helper"
2
2
 
3
- describe "Invoker Power configuration" do
3
+ describe "Invoker Power configuration", fakefs: true do
4
4
  describe "#create" do
5
5
  it "should create a config file given a hash" do
6
+ FileUtils.mkdir_p(inv_conf_dir)
6
7
  config = Invoker::Power::Config.create(
7
8
  dns_port: 1200, http_port: 1201, ipfw_rule_number: 010
8
9
  )
@@ -2,6 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe Invoker::Power::PfMigrate do
4
4
  before do
5
+ FileUtils.mkdir_p("/tmp/.invoker")
5
6
  @old_firewall_file = Invoker::Power::OsxSetup::FIREWALL_PLIST_FILE
6
7
  Invoker::Power::OsxSetup.const_set(:FIREWALL_PLIST_FILE, "/tmp/.invoker/firewall")
7
8
  end
@@ -54,12 +55,18 @@ describe Invoker::Power::PfMigrate do
54
55
 
55
56
  describe "#migrate" do
56
57
  before do
58
+ @original_invoker_config = Invoker.config
57
59
  mock_config = mock()
58
60
  mock_config.stubs(:http_port).returns(80)
59
61
  mock_config.stubs(:https_port).returns(443)
62
+ mock_config.stubs(:tld).returns('dev')
60
63
  Invoker.config = mock_config
61
64
  end
62
65
 
66
+ after do
67
+ Invoker.config = @original_invoker_config
68
+ end
69
+
63
70
  it "should migrate firewall to new system" do
64
71
  pf_migrator.expects(:firewall_config_requires_migration?).returns(true)
65
72
  pf_migrator.expects(:ask_user_for_migration).returns(true)
@@ -1,9 +1,31 @@
1
1
  require "spec_helper"
2
2
  require "invoker/power/setup/distro/ubuntu"
3
+ require "invoker/power/setup/distro/opensuse"
3
4
 
4
- describe Invoker::Power::LinuxSetup do
5
- let(:invoker_setup) { Invoker::Power::LinuxSetup.new }
6
- let(:distro_installer) { Invoker::Power::Distro::Ubuntu.new }
5
+ def mock_socat_scripts
6
+ FakeFS.deactivate!
7
+ socat_content = File.read(invoker_setup.forwarder_script)
8
+ socat_systemd = File.read(invoker_setup.socat_unit)
9
+ FakeFS.activate!
10
+ FileUtils.mkdir_p(File.dirname(invoker_setup.forwarder_script))
11
+ FileUtils.mkdir_p(File.dirname(invoker_setup.socat_unit))
12
+ File.open(invoker_setup.socat_unit, "w") do |fl|
13
+ fl.write(socat_systemd)
14
+ end
15
+ File.open(invoker_setup.forwarder_script, "w") do |fl|
16
+ fl.write(socat_content)
17
+ end
18
+ FileUtils.mkdir_p("/usr/bin")
19
+ end
20
+
21
+ describe Invoker::Power::LinuxSetup, fakefs: true do
22
+ before do
23
+ FileUtils.mkdir_p(inv_conf_dir)
24
+ FileUtils.mkdir_p(Invoker::Power::Distro::Base::RESOLVER_DIR)
25
+ end
26
+
27
+ let(:invoker_setup) { Invoker::Power::LinuxSetup.new('dev') }
28
+ let(:distro_installer) { Invoker::Power::Distro::Ubuntu.new('dev') }
7
29
 
8
30
  describe "should only proceed after user confirmation" do
9
31
  before { invoker_setup.distro_installer = distro_installer }
@@ -27,8 +49,20 @@ describe Invoker::Power::LinuxSetup do
27
49
  end
28
50
  end
29
51
 
30
- describe "configuring dnsmasq and rinetd" do
31
- before { invoker_setup.distro_installer = distro_installer }
52
+ describe "configuring dnsmasq and socat" do
53
+ before(:all) do
54
+ @original_invoker_config = Invoker.config
55
+ Invoker.config = mock
56
+ end
57
+
58
+ after(:all) do
59
+ Invoker.config = @original_invoker_config
60
+ end
61
+
62
+ before(:each) do
63
+ invoker_setup.distro_installer = distro_installer
64
+ mock_socat_scripts
65
+ end
32
66
 
33
67
  it "should create proper config file" do
34
68
  invoker_setup.expects(:initialize_distro_installer).returns(true)
@@ -46,10 +80,24 @@ describe Invoker::Power::LinuxSetup do
46
80
  expect(dnsmasq_content.strip).to_not be_empty
47
81
  expect(dnsmasq_content).to match(/dev/)
48
82
 
49
- rinetd_content = File.read(distro_installer.rinetd_file)
50
- expect(rinetd_content.strip).to_not be_empty
51
- expect(rinetd_content.strip).to match(/#{config.https_port}/)
52
- expect(rinetd_content.strip).to match(/#{config.http_port}/)
83
+ socat_content = File.read(Invoker::Power::Distro::Base::SOCAT_SHELLSCRIPT)
84
+ expect(socat_content.strip).to_not be_empty
85
+ expect(socat_content.strip).to match(/#{config.https_port}/)
86
+ expect(socat_content.strip).to match(/#{config.http_port}/)
87
+
88
+ service_file = File.read(Invoker::Power::Distro::Base::SOCAT_SYSTEMD)
89
+ expect(service_file.strip).to_not be_empty
90
+ end
91
+ end
92
+
93
+ describe 'resolver file' do
94
+ context 'user sets up a custom top level domain' do
95
+ it 'should create the correct resolver file' do
96
+ linux_setup = Invoker::Power::LinuxSetup.new('local')
97
+ suse_installer = Invoker::Power::Distro::Opensuse.new('local')
98
+ linux_setup.distro_installer = suse_installer
99
+ expect(linux_setup.resolver_file).to eq('/etc/dnsmasq.d/local-tld')
100
+ end
53
101
  end
54
102
  end
55
103
  end
@@ -1,9 +1,14 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Invoker::Power::OsxSetup do
4
- describe "When no setup exists" do
3
+ describe Invoker::Power::OsxSetup, fakefs: true do
4
+ before do
5
+ FileUtils.mkdir_p(inv_conf_dir)
6
+ FileUtils.mkdir_p(Invoker::Power::OsxSetup::RESOLVER_DIR)
7
+ end
8
+
9
+ describe "when no setup exists" do
5
10
  it "should create a config file with port etc" do
6
- setup = Invoker::Power::OsxSetup.new
11
+ setup = Invoker::Power::OsxSetup.new('dev')
7
12
  setup.expects(:install_resolver).returns(true)
8
13
  setup.expects(:drop_to_normal_user).returns(true)
9
14
  setup.expects(:install_firewall).once
@@ -23,16 +28,16 @@ describe Invoker::Power::OsxSetup do
23
28
  fl.write("foo test")
24
29
  }
25
30
  Invoker::Power::Setup.any_instance.expects(:setup_invoker).never
26
- Invoker::Power::Setup.install()
31
+ Invoker::Power::Setup.install('dev')
27
32
  end
28
33
  end
29
34
 
30
35
  describe "when pow like setup exists" do
31
36
  before {
32
- File.open(Invoker::Power::OsxSetup::RESOLVER_FILE, "w") {|fl|
37
+ File.open(File.join(Invoker::Power::OsxSetup::RESOLVER_DIR, "dev"), "w") { |fl|
33
38
  fl.write("hello")
34
39
  }
35
- @setup = Invoker::Power::OsxSetup.new
40
+ @setup = Invoker::Power::OsxSetup.new('dev')
36
41
  }
37
42
 
38
43
  describe "when user selects to overwrite it" do
@@ -60,7 +65,7 @@ describe Invoker::Power::OsxSetup do
60
65
 
61
66
  describe "uninstalling firewall rules" do
62
67
  it "should uninstall firewall rules and remove all files created by setup" do
63
- setup = Invoker::Power::OsxSetup.new
68
+ setup = Invoker::Power::OsxSetup.new('dev')
64
69
 
65
70
  Invoker::CLI::Question.expects(:agree).returns(true)
66
71
  setup.expects(:remove_resolver_file).once
@@ -74,7 +79,7 @@ describe Invoker::Power::OsxSetup do
74
79
  describe "setup on fresh osx install" do
75
80
  context "when resolver directory does not exist" do
76
81
  before do
77
- @setup = Invoker::Power::OsxSetup.new
82
+ @setup = Invoker::Power::OsxSetup.new('dev')
78
83
  FileUtils.rm_rf(Invoker::Power::OsxSetup::RESOLVER_DIR)
79
84
  end
80
85
 
@@ -88,4 +93,13 @@ describe Invoker::Power::OsxSetup do
88
93
  end
89
94
  end
90
95
  end
96
+
97
+ describe '.resolver_file' do
98
+ context 'user sets up a custom top level domain' do
99
+ it 'should create the correct resolver file' do
100
+ setup = Invoker::Power::OsxSetup.new('local')
101
+ expect(setup.resolver_file).to eq('/etc/resolver/local')
102
+ end
103
+ end
104
+ end
91
105
  end
@@ -4,7 +4,18 @@ describe Invoker::Power::UrlRewriter do
4
4
  let(:rewriter) { Invoker::Power::UrlRewriter.new }
5
5
 
6
6
  context "matching domain part of incoming request" do
7
- it "should do foo.dev match" do
7
+ before(:all) do
8
+ @original_invoker_config = Invoker.config
9
+
10
+ Invoker.config = mock
11
+ Invoker.config.stubs(:tld).returns("dev")
12
+ end
13
+
14
+ after(:all) do
15
+ Invoker.config = @original_invoker_config
16
+ end
17
+
18
+ it "should match foo.dev" do
8
19
  match = rewriter.extract_host_from_domain("foo.dev")
9
20
  expect(match).to_not be_empty
10
21
 
@@ -34,5 +45,26 @@ describe Invoker::Power::UrlRewriter do
34
45
 
35
46
  expect(match[0]).to eq("hello-world")
36
47
  end
48
+
49
+ context 'user sets up a custom top level domain' do
50
+ before(:all) do
51
+ @original_invoker_config = Invoker.config
52
+
53
+ Invoker.config = mock
54
+ Invoker.config.stubs(:tld).returns("local")
55
+ end
56
+
57
+ it 'should match domain part of incoming request correctly' do
58
+ match = rewriter.extract_host_from_domain("foo.local")
59
+ expect(match).to_not be_empty
60
+
61
+ matching_string = match[0]
62
+ expect(matching_string).to eq("foo")
63
+ end
64
+
65
+ after(:all) do
66
+ Invoker.config = @original_invoker_config
67
+ end
68
+ end
37
69
  end
38
70
  end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ # Full integration test. Start a server, and client. Let client interact with
4
+ # server do a ping-pong. Client checks whether ping pong is successful or not.
5
+ # Also, mock rewriter so that it returns valid port for request proxying.
6
+ # - Server will run on port 28080.
7
+ # - Balancer will run on port 28081 proxying to 28080
8
+ # - Client will connect to 28081 performing ping-pong
9
+
10
+ def websocket_server
11
+ require 'websocket-eventmachine-server'
12
+
13
+ EM.run do
14
+ WebSocket::EventMachine::Server.start(host: "0.0.0.0", port: 28080) do |ws|
15
+ ws.onerror { |e| p e }
16
+ ws.onmessage { ws.send "pong" }
17
+ end
18
+
19
+ EM.add_timer(2) { EM.stop }
20
+ end
21
+ end
22
+
23
+ def websocket_client
24
+ require 'websocket-eventmachine-client'
25
+
26
+ @message = ""
27
+
28
+ EM.run do
29
+ ws = WebSocket::EventMachine::Client.connect(uri: 'ws://0.0.0.0:28081')
30
+ ws.onerror { |e| p e }
31
+ ws.onopen { ws.send("ping") }
32
+ ws.onmessage { |m, _| @message = m }
33
+
34
+ EM.add_timer(2) do
35
+ expect(@message).to eq "pong"
36
+ EM.stop
37
+ end
38
+ end
39
+ end
40
+
41
+
42
+ describe 'Web sockets support' do
43
+ it 'can ping pong via balancer' do
44
+ dns_response = Struct.new(:port, :ip).new(28080, "0.0.0.0")
45
+ Invoker::Power::UrlRewriter.any_instance
46
+ .stubs(:select_backend_config)
47
+ .returns(dns_response)
48
+
49
+ EM.run do
50
+ EM.start_server("0.0.0.0", 28081, EM::ProxyServer::Connection, {}) do |conn|
51
+ Invoker::Power::Balancer.new(conn, "http").install_callbacks
52
+ end
53
+
54
+ fork { websocket_server }
55
+ fork { websocket_client }
56
+ EM.add_timer(3) { EM.stop }
57
+ end
58
+
59
+ Process.waitall
60
+ end
61
+ end
@@ -2,13 +2,19 @@ require "spec_helper"
2
2
 
3
3
  describe Invoker::ProcessManager do
4
4
  let(:process_manager) { Invoker::ProcessManager.new }
5
+
5
6
  describe "#start_process_by_name" do
6
7
  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"))
8
+ @original_invoker_config = Invoker.config
9
+ Invoker.config = mock
10
+
11
+ Invoker.config.stubs(:processes).returns([OpenStruct.new(:label => "resque", :cmd => "foo", :dir => "bar")])
12
+ Invoker.config.expects(:process).returns(OpenStruct.new(:label => "resque", :cmd => "foo", :dir => "bar"))
9
13
  process_manager.expects(:start_process).returns(true)
10
14
 
11
15
  process_manager.start_process_by_name("resque")
16
+
17
+ Invoker.config = @original_invoker_config
12
18
  end
13
19
 
14
20
  it "should not start already running process" do
@@ -94,5 +100,31 @@ BAR=foo
94
100
  dir = "/tmp"
95
101
  expect(process_manager.load_env(dir)).to eq({})
96
102
  end
103
+
104
+ it "should load .local.env file if it exists" do
105
+ dir = "/tmp"
106
+ begin
107
+ env_file = File.new("#{dir}/.env", "w")
108
+ env_data =<<-EOD
109
+ FOO=foo
110
+ BAR=bar
111
+ EOD
112
+ env_file.write(env_data)
113
+ env_file.close
114
+
115
+ local_env_file = File.new("#{dir}/.env.local", "w")
116
+ local_env_data =<<-EOD
117
+ FOO=emacs
118
+ EOD
119
+ local_env_file.write(local_env_data)
120
+ local_env_file.close
121
+
122
+ env_options = process_manager.load_env(dir)
123
+ expect(env_options).to include("FOO" => "emacs", "BAR" => "bar")
124
+ ensure
125
+ File.delete(env_file.path)
126
+ File.delete(local_env_file.path)
127
+ end
128
+ end
97
129
  end
98
130
  end