File: command.rb

package info (click to toggle)
ruby-god 0.12.1-1
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 752 kB
  • sloc: ruby: 5,913; ansic: 217; makefile: 3
file content (269 lines) | stat: -rw-r--r-- 7,012 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
module God
  module CLI

    class Command
      def initialize(command, options, args)
        @command = command
        @options = options
        @args = args

        dispatch
      end

      def setup
        # connect to drb unix socket
        DRb.start_service("druby://127.0.0.1:0")
        @server = DRbObject.new(nil, God::Socket.socket(@options[:port]))

        # ping server to ensure that it is responsive
        begin
          @server.ping
        rescue DRb::DRbConnError
          puts "The server is not available (or you do not have permissions to access it)"
          abort
        end
      end

      def dispatch
        if %w{load status signal log quit terminate}.include?(@command)
          setup
          send("#{@command}_command")
        elsif %w{start stop restart monitor unmonitor remove}.include?(@command)
          setup
          lifecycle_command
        elsif @command == 'check'
          check_command
        else
          puts "Command '#{@command}' is not valid. Run 'god --help' for usage"
          abort
        end
      end

      def load_command
        file = @args[1]
        action = @args[2] || 'leave'

        unless ['stop', 'remove', 'leave', ''].include?(action)
          puts "Command '#{@command}' action must be either 'stop', 'remove' or 'leave'"
          exit(1)
        end

        puts "Sending '#{@command}' command with action '#{action}'"
        puts

        unless File.exist?(file)
          abort "File not found: #{file}"
        end

        affected, errors, removed = *@server.running_load(File.read(file), File.expand_path(file), action)

        # output response
        unless affected.empty?
          puts 'The following tasks were affected:'
          affected.each do |w|
            puts '  ' + w
          end
        end

        unless removed.empty?
          puts 'The following tasks were removed:'
          removed.each do |w|
            puts '  ' + w
          end
        end

        unless errors.empty?
          puts errors
          exit(1)
        end
      end

      def status_command
        exitcode = 0
        statuses = @server.status
        groups = {}
        statuses.each do |name, status|
          g = status[:group] || ''
          groups[g] ||= {}
          groups[g][name] = status
        end

        if item = @args[1]
          if single = statuses[item]
            # specified task (0 -> up, 1 -> unmonitored, 2 -> other)
            state = single[:state]
            puts "#{item}: #{state}"
            exitcode = state == :up ? 0 : (state == :unmonitored ? 1 : 2)
          elsif groups[item]
            # specified group (0 -> up, N -> other)
            puts "#{item}:"
            groups[item].keys.sort.each do |name|
              state = groups[item][name][:state]
              print "  "
              puts "#{name}: #{state}"
              exitcode += 1 unless state == :up
            end
          else
            puts "Task or Group '#{item}' not found."
            exit(1)
          end
        else
          # show all groups and watches
          groups.keys.sort.each do |group|
            puts "#{group}:" unless group.empty?
            groups[group].keys.sort.each do |name|
              state = groups[group][name][:state]
              print "  " unless group.empty?
              puts "#{name}: #{state}"
            end
          end
        end

        exit(exitcode)
      end

      def signal_command
        # get the name of the watch/group
        name = @args[1]
        signal = @args[2]

        puts "Sending signal '#{signal}' to '#{name}'"

        t = Thread.new { loop { sleep(1); STDOUT.print('.'); STDOUT.flush; sleep(1) } }

        watches = @server.signal(name, signal)

        # output response
        t.kill; STDOUT.puts
        unless watches.empty?
          puts 'The following watches were affected:'
          watches.each do |w|
            puts '  ' + w
          end
        else
          puts 'No matching task or group'
        end
      end

      def log_command
        begin
          Signal.trap('INT') { exit }
          name = @args[1]

          unless name
            puts "You must specify a Task or Group name"
            exit!
          end

          puts "Please wait..."
          t = Time.at(0)
          loop do
            print @server.running_log(name, t)
            t = Time.now
            sleep 0.25
          end
        rescue God::NoSuchWatchError
          puts "No such watch"
        rescue DRb::DRbConnError
          puts "The server went away"
        end
      end

      def quit_command
        begin
          @server.terminate
          abort 'Could not stop god'
        rescue DRb::DRbConnError
          puts 'Stopped god'
        end
      end

      def terminate_command
        t = Thread.new { loop { STDOUT.print('.'); STDOUT.flush; sleep(1) } }
        if @server.stop_all
          t.kill; STDOUT.puts
          puts 'Stopped all watches'
        else
          t.kill; STDOUT.puts
          puts "Could not stop all watches within #{@server.terminate_timeout} seconds"
        end

        begin
          @server.terminate
          abort 'Could not stop god'
        rescue DRb::DRbConnError
          puts 'Stopped god'
        end
      end

      def check_command
        Thread.new do
          begin
            event_system = God::EventHandler.event_system
            puts "using event system: #{event_system}"

            if God::EventHandler.loaded?
              puts "starting event handler"
              God::EventHandler.start
            else
              puts "[fail] event system did not load"
              exit(1)
            end

            puts 'forking off new process'

            pid = fork do
              loop { sleep(1) }
            end

            puts "forked process with pid = #{pid}"

            God::EventHandler.register(pid, :proc_exit) do
              puts "[ok] process exit event received"
              exit!(0)
            end

            sleep(1)

            puts "killing process"

            ::Process.kill('KILL', pid)
            ::Process.waitpid(pid)
          rescue => e
            puts e.message
            puts e.backtrace.join("\n")
          end
        end

        sleep(2)

        puts "[fail] never received process exit event"
        exit(1)
      end

      def lifecycle_command
        # get the name of the watch/group
        name = @args[1]

        puts "Sending '#{@command}' command"

        t = Thread.new { loop { sleep(1); STDOUT.print('.'); STDOUT.flush; sleep(1) } }

        # send @command
        watches = @server.control(name, @command)

        # output response
        t.kill; STDOUT.puts
        unless watches.empty?
          puts 'The following watches were affected:'
          watches.each do |w|
            puts '  ' + w
          end
        else
          puts 'No matching task or group'
        end
      end
    end # Command

  end
end