invoker 1.0.4 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,23 @@
1
+ module Invoker
2
+ class DNSCache
3
+ attr_accessor :dns_data
4
+
5
+ def initialize(config)
6
+ self.dns_data = {}
7
+ @dns_mutex = Mutex.new
8
+ Invoker.config.processes.each do |process|
9
+ if process.port
10
+ dns_data[process.label] = { 'port' => process.port }
11
+ end
12
+ end
13
+ end
14
+
15
+ def [](process_name)
16
+ @dns_mutex.synchronize { dns_data[process_name] }
17
+ end
18
+
19
+ def add(name, port)
20
+ @dns_mutex.synchronize { dns_data[name] = { 'port' => port } }
21
+ end
22
+ end
23
+ end
@@ -12,5 +12,6 @@ module Invoker
12
12
  class NoValidPortFound < StandardError; end
13
13
  class InvalidConfig < StandardError; end
14
14
  class InvalidFile < StandardError; end
15
+ class ClientDisconnected < StandardError; end
15
16
  end
16
17
  end
@@ -0,0 +1,45 @@
1
+ require "invoker/ipc/base_command"
2
+ require 'invoker/ipc/message'
3
+ require 'invoker/ipc/add_command'
4
+ require 'invoker/ipc/add_http_command'
5
+ require 'invoker/ipc/client_handler'
6
+ require 'invoker/ipc/dns_check_command'
7
+ require 'invoker/ipc/list_command'
8
+ require 'invoker/ipc/remove_command'
9
+ require 'invoker/ipc/server'
10
+ require "invoker/ipc/reload_command"
11
+ require 'invoker/ipc/tail_command'
12
+ require 'invoker/ipc/unix_client'
13
+ require "invoker/ipc/ping_command"
14
+
15
+ module Invoker
16
+ module IPC
17
+ INITIAL_PACKET_SIZE = 9
18
+ def self.message_from_io(io)
19
+ json_size = io.read(INITIAL_PACKET_SIZE)
20
+ json_string = io.read(json_size.to_i)
21
+ ruby_object_hash = JSON.parse(json_string)
22
+ command_name = camelize(ruby_object_hash['type'])
23
+ command_klass = Invoker::IPC::Message.const_get(command_name)
24
+ command_klass.new(ruby_object_hash)
25
+ end
26
+
27
+ # Taken from Rails without inflection support
28
+ def self.camelize(term)
29
+ string = term.to_s
30
+ string = string.sub(/^[a-z\d]*/) { $&.capitalize }
31
+ string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }
32
+ string.gsub!('/', '::')
33
+ string
34
+ end
35
+
36
+ def self.underscore(term)
37
+ word = term.to_s.gsub('::', '/')
38
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
39
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
40
+ word.tr!("-", "_")
41
+ word.downcase!
42
+ word
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ module Invoker
2
+ module IPC
3
+ class AddCommand < BaseCommand
4
+ def run_command(message_object)
5
+ Invoker.commander.on_next_tick(message_object.process_name) do |process_name|
6
+ start_process_by_name(process_name)
7
+ end
8
+ true
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ module Invoker
2
+ module IPC
3
+ class AddHttpCommand < BaseCommand
4
+ def run_command(message_object)
5
+ Invoker.dns_cache.add(message_object.process_name, message_object.port)
6
+ true
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ module Invoker
2
+ module IPC
3
+ class BaseCommand
4
+ attr_accessor :client_socket
5
+ def initialize(client_socket)
6
+ @client_socket = client_socket
7
+ end
8
+
9
+ def send_data(message_object)
10
+ client_socket.write(message_object.encoded_message)
11
+ end
12
+
13
+ # Invoke the command that actual processes incoming message
14
+ # returning true from this message means, command has been processed
15
+ # and client socket can be closed. returning false means, it is a
16
+ # long running command and socket should not be closed immediately
17
+ # @param [Invoker::IPC::Message] incoming message
18
+ # @return [Boolean] true or false
19
+ def run_command(message_object)
20
+ raise "Not implemented"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ module Invoker
2
+ module IPC
3
+ class ClientHandler
4
+ attr_accessor :client_socket
5
+ def initialize(client_socket)
6
+ @client_socket = client_socket
7
+ end
8
+
9
+ def read_and_execute
10
+ client_handler, message_object = read_incoming_command
11
+ client_socket.close if client_handler.run_command(message_object)
12
+ rescue StandardError => error
13
+ Invoker::Logger.puts error.message
14
+ Invoker::Logger.puts error.backtrace
15
+ client_socket.close
16
+ end
17
+
18
+ private
19
+
20
+ def read_incoming_command
21
+ message_object = Invoker::IPC.message_from_io(client_socket)
22
+ [message_object.command_handler_klass.new(client_socket), message_object]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ module Invoker
2
+ module IPC
3
+ class DnsCheckCommand < BaseCommand
4
+ def run_command(message_object)
5
+ process_detail = Invoker.dns_cache[message_object.process_name]
6
+
7
+ dns_check_response = Invoker::IPC::Message::DnsCheckResponse.new(
8
+ process_name: message_object.process_name,
9
+ port: process_detail ? process_detail['port'] : nil
10
+ )
11
+ send_data(dns_check_response)
12
+ true
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module Invoker
2
+ module IPC
3
+ class ListCommand < BaseCommand
4
+ def run_command(message_object)
5
+ list_response = Invoker.commander.process_list
6
+ send_data(list_response)
7
+ true
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,170 @@
1
+ module Invoker
2
+ module IPC
3
+ module Message
4
+ module Serialization
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ def as_json
10
+ attributes.merge(type: message_type)
11
+ end
12
+
13
+ def to_json
14
+ JSON.generate(as_json)
15
+ end
16
+
17
+ def message_attributes
18
+ self.class.message_attributes
19
+ end
20
+
21
+ def encoded_message
22
+ json_data = to_json
23
+ json_size = json_data.length.to_s
24
+ length_str = json_size.rjust(Invoker::IPC::INITIAL_PACKET_SIZE, '0')
25
+ length_str + json_data
26
+ end
27
+
28
+ def eql?(other)
29
+ other.class == self.class &&
30
+ compare_attributes(other)
31
+ end
32
+
33
+ def attributes
34
+ message_attribute_keys = message_attributes || []
35
+ message_attribute_keys.reduce({}) do |mem, obj|
36
+ value = send(obj)
37
+ if value.is_a?(Array)
38
+ mem[obj] = serialize_array(value)
39
+ elsif value.is_a?(Hash)
40
+ mem[obj] = serialize_hash(value)
41
+ else
42
+ mem[obj] = value.respond_to?(:as_json) ? value.as_json : encode_as_utf(value)
43
+ end
44
+ mem
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def compare_attributes(other)
51
+ message_attributes.all? do |attribute_name|
52
+ send(attribute_name).eql?(other.send(attribute_name))
53
+ end
54
+ end
55
+
56
+ def encode_as_utf(value)
57
+ return value unless value.is_a?(String)
58
+ value.encode("utf-8", invalid: :replace, undef: :replace, replace: '_')
59
+ end
60
+
61
+ def serialize_array(attribute_array)
62
+ attribute_array.map do |x|
63
+ x.respond_to?(:as_json) ? x.as_json : encode_as_utf(x)
64
+ end
65
+ end
66
+
67
+ def serialize_hash(attribute_hash)
68
+ attribute_hash.inject({}) do |temp_mem, (temp_key, temp_value)|
69
+ if temp_value.respond_to?(:as_json)
70
+ temp_mem[temp_key] = temp_value.as_json
71
+ else
72
+ temp_mem[temp_key] = encode_as_utf(temp_value)
73
+ end
74
+ end
75
+ end
76
+
77
+ module ClassMethods
78
+ def message_attributes(*incoming_attributes)
79
+ if incoming_attributes.empty? && defined?(@message_attributes)
80
+ @message_attributes
81
+ else
82
+ @message_attributes ||= []
83
+ new_attributes = incoming_attributes.flatten
84
+ @message_attributes += new_attributes
85
+ attr_accessor *new_attributes
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ class Base
92
+ def initialize(options)
93
+ options.each do |key, value|
94
+ if self.respond_to?("#{key}=")
95
+ send("#{key}=", value)
96
+ end
97
+ end
98
+ end
99
+
100
+ def message_type
101
+ Invoker::IPC.underscore(self.class.name).split("/").last
102
+ end
103
+
104
+ def command_handler_klass
105
+ Invoker::IPC.const_get("#{IPC.camelize(message_type)}Command")
106
+ end
107
+ end
108
+
109
+ class Add < Base
110
+ include Serialization
111
+ message_attributes :process_name
112
+ end
113
+
114
+ class Tail < Base
115
+ include Serialization
116
+ message_attributes :process_names
117
+ end
118
+
119
+ class AddHttp < Base
120
+ include Serialization
121
+ message_attributes :process_name, :port
122
+ end
123
+
124
+ class Reload < Base
125
+ include Serialization
126
+ message_attributes :process_name, :signal
127
+
128
+ def remove_message
129
+ Remove.new(process_name: process_name, signal: signal)
130
+ end
131
+ end
132
+
133
+ class List < Base
134
+ include Serialization
135
+ end
136
+
137
+ class Process < Base
138
+ include Serialization
139
+ message_attributes :process_name, :shell_command, :dir, :pid
140
+ end
141
+
142
+ class Remove < Base
143
+ include Serialization
144
+ message_attributes :process_name, :signal
145
+ end
146
+
147
+ class DnsCheck < Base
148
+ include Serialization
149
+ message_attributes :process_name
150
+ end
151
+
152
+ class DnsCheckResponse < Base
153
+ include Serialization
154
+ message_attributes :process_name, :port
155
+ end
156
+
157
+ class Ping < Base
158
+ include Serialization
159
+ end
160
+
161
+ class Pong < Base
162
+ include Serialization
163
+ message_attributes :status
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ require "invoker/ipc/message/list_response"
170
+ require "invoker/ipc/message/tail_response"
@@ -0,0 +1,33 @@
1
+ module Invoker
2
+ module IPC
3
+ module Message
4
+ class ListResponse < Base
5
+ include Serialization
6
+ message_attributes :processes
7
+ def initialize(options)
8
+ self.processes = []
9
+ process_array = options[:processes] || options['processes']
10
+ process_array.each do |process_hash|
11
+ processes << Process.new(process_hash)
12
+ end
13
+ end
14
+
15
+ def self.from_workers(workers)
16
+ process_array = []
17
+ Invoker.config.processes.each do |process|
18
+ worker_attrs = {
19
+ :shell_command => process.cmd, :process_name => process.label,
20
+ :dir => process.dir
21
+ }
22
+ if worker = workers[process.label]
23
+ worker_attrs.update(pid: worker.pid)
24
+ end
25
+ process_array << worker_attrs
26
+ end
27
+
28
+ new(processes: process_array)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,10 @@
1
+ module Invoker
2
+ module IPC
3
+ module Message
4
+ class TailResponse < Base
5
+ include Serialization
6
+ message_attributes :tail_line
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Invoker
2
+ module IPC
3
+ class PingCommand < BaseCommand
4
+ def run_command(message_object)
5
+ pong = Invoker::IPC::Message::Pong.new(status: 'pong')
6
+ send_data(pong)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ module Invoker
2
+ module IPC
3
+ class ReloadCommand < BaseCommand
4
+ def run_command(message_object)
5
+ Invoker.commander.on_next_tick(message_object) do |reload_message|
6
+ restart_process(reload_message)
7
+ end
8
+ true
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Invoker
2
+ module IPC
3
+ class RemoveCommand < BaseCommand
4
+ def run_command(message_object)
5
+ Invoker.commander.on_next_tick(message_object) do |remove_message|
6
+ stop_process(remove_message)
7
+ end
8
+ true
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,31 +1,26 @@
1
1
  require "fileutils"
2
2
 
3
3
  module Invoker
4
- module CommandListener
4
+ module IPC
5
5
  class Server
6
6
  SOCKET_PATH = "/tmp/invoker"
7
7
  def initialize
8
8
  @open_clients = []
9
- Socket.unix_server_loop(SOCKET_PATH) {|sock, client_addrinfo|
10
- begin
11
- process_client(sock)
12
- ensure
13
- sock.close
14
- end
15
- }
9
+ Socket.unix_server_loop(SOCKET_PATH) do |sock, client_addrinfo|
10
+ Thread.new { process_client(sock) }
11
+ end
16
12
  end
17
13
 
18
14
  def clean_old_socket
19
- if File.exists?(SOCKET_PATH)
15
+ if File.exist?(SOCKET_PATH)
20
16
  FileUtils.rm(SOCKET_PATH, :force => true)
21
17
  end
22
18
  end
23
19
 
24
20
  def process_client(client_socket)
25
- client = Invoker::CommandListener::Client.new(client_socket)
21
+ client = Invoker::IPC::ClientHandler.new(client_socket)
26
22
  client.read_and_execute
27
23
  end
28
24
  end
29
25
  end
30
-
31
26
  end