isono 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,17 +5,9 @@ module Rack
5
5
  class Job < Decorator
6
6
  include Logger
7
7
 
8
- # Response class for nothing response.
9
- # It is used when the job request type is :submit.
10
- class NullResponse < Response
11
- def progress(ret)
12
- end
13
-
14
- def response(ret)
15
- end
16
- end
17
-
18
8
  class JobResponse < Response
9
+ attr_reader :job
10
+
19
11
  # @param [NodeModules::RpcChannel::ResponseContext] ctx
20
12
  # @param [NodeModules::JobWorker::JobContext] jobctx
21
13
  def initialize(ctx, jobctx)
@@ -30,6 +22,8 @@ module Rack
30
22
  end
31
23
 
32
24
  class JobRequest < Request
25
+ attr_reader :job
26
+
33
27
  # @param [Hash] request_hash
34
28
  # @param [NodeModules::JobWorker::JobContext] jobctx
35
29
  def initialize(request_hash, jobctx)
@@ -44,30 +38,23 @@ module Rack
44
38
  end
45
39
 
46
40
  def call(req, res)
47
- orig_res = res
48
- case req.r[:job_request_type]
49
- when :submit
50
- res = NullResponse.new(res.ctx)
41
+ job = @job_worker.start do |job|
42
+ job.job_id = req.r[:job_id]
43
+ job.parent_job_id = req.r[:parent_job_id]
44
+ job.run_cb = proc {
45
+ begin
46
+ @app.call(JobRequest.new(req.r, job), JobResponse.new(res.ctx, job))
47
+ res.response(nil) unless res.responded?
48
+ rescue Exception => e
49
+ res.response(e) unless res.responded?
50
+ raise e
51
+ end
52
+ }
51
53
  end
52
-
53
- job = @job_worker.run(req.r[:parent_job_id]){
54
- begin
55
- @app.call(JobRequest.new(req.r, job), JobResponse.new(res.ctx, job))
56
- res.response(nil) unless res.responded?
57
- rescue Exception => e
58
- res.response(e) unless res.responded?
59
- raise e
60
- end
61
- }
62
54
 
63
- case req.r[:job_request_type]
64
- when :submit
65
- orig_res.response(job.to_hash)
66
- else
67
- # send job context info back at the first progress message.
68
- # following progress messages to be handled as usual.
69
- res.progress(job.to_hash)
70
- end
55
+ # send the new job context info back as the first progress message.
56
+ # the progress messages that follow will be handled as usual.
57
+ res.progress(job.to_hash)
71
58
  end
72
59
  end
73
60
  end
@@ -0,0 +1,150 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'optparse'
4
+ require 'digest/sha1'
5
+ require 'etc'
6
+
7
+ require 'isono'
8
+ require 'isono/amqp_client'
9
+
10
+ module Isono
11
+ module Runner
12
+
13
+ module Daemonize
14
+ # Change privileges of the process
15
+ # to the specified user and group.
16
+ def self.change_privilege(user, group=user)
17
+ logger.info("Changing process privilege to #{user}:#{group}")
18
+
19
+ uid, gid = Process.euid, Process.egid
20
+ target_uid = Etc.getpwnam(user).uid
21
+ target_gid = Etc.getgrnam(group).gid
22
+
23
+ if uid != target_uid || gid != target_gid
24
+ # Change process ownership
25
+ Process.initgroups(user, target_gid)
26
+ Process::GID.change_privilege(target_gid)
27
+ Process::UID.change_privilege(target_uid)
28
+ end
29
+ rescue Errno::EPERM => e
30
+ logger.error("Couldn't change user and group to #{user}:#{group}: #{e}")
31
+ end
32
+
33
+ def self.daemonize(log_io=STDOUT)
34
+ exit if fork
35
+ srand
36
+ trap 'SIGHUP', 'DEFAULT'
37
+
38
+ STDIN.reopen('/dev/null')
39
+
40
+ STDOUT.reopen(log_io)
41
+ STDERR.reopen(log_io)
42
+ end
43
+ end
44
+
45
+ class Base
46
+ include Logger
47
+
48
+ def initialize()
49
+ @options = {
50
+ :amqp_server_uri => URI.parse('amqp://guest:guest@localhost/'),
51
+ :log_file => nil,
52
+ :pid_file => nil,
53
+ :daemonize => false,
54
+ :config_path => nil,
55
+ :manifest_path => nil,
56
+ }
57
+ end
58
+
59
+ def optparse(args)
60
+ optparse = OptionParser.new do |opts|
61
+ opts.banner = "Usage: #{$0} [options]"
62
+
63
+ opts.separator ""
64
+ opts.separator "Options:"
65
+ opts.on( "-i", "--id ID", "Manually specify the Agent ID" ) {|str| @options[:node_id] = str }
66
+ opts.on( "-p", "--pid PIDFILE", "pid file path" ) {|str| @options[:pid_file] = str }
67
+ opts.on( "--log LOGFILE", "log file path" ) {|str| @options[:log_file] = str }
68
+ opts.on( "--config CONFFILE", "config file path" ) {|str| @options[:config_file] = str }
69
+ opts.on( "-s", "--server AMQP_URI", "amqp broker server to connect" ) {|str|
70
+ begin
71
+ @options[:amqp_server_uri] = URI.parse(str)
72
+ rescue URI::InvalidURIError => e
73
+ abort "#{e}"
74
+ end
75
+ }
76
+ opts.on("-b", "Run in background" ) { @options[:daemonize] = false }
77
+ end
78
+
79
+ optparse.parse!(args)
80
+ end
81
+
82
+ def run(manifest=nil, opts={}, &blk)
83
+ @options = @options.merge(opts)
84
+ optparse(ARGV.dup)
85
+
86
+ if manifest.is_a?(String)
87
+ # load manifest file
88
+ manifest = Manifest.load_file(manifest)
89
+ elsif manifest.nil? && @options[:manifest_path]
90
+ manifest = Manifest.load_file(@options[:manifest_path])
91
+ end
92
+
93
+ if @options[:node_id]
94
+ # force overwrite node_id if the command line arg was given.
95
+ manifest.node_instance_id(@options[:node_id])
96
+ elsif manifest.node_id.nil?
97
+ # nobody specified the node_id then set the ID in the
98
+ # default manner.
99
+ manifest.node_id(default_node_id)
100
+ end
101
+
102
+ @options[:log_file] ||= "/var/log/%s.log" % [manifest.node_name]
103
+ @options[:pid_file] ||= "/var/run/%s.pid" % [manifest.node_name]
104
+
105
+ if @options[:daemonize]
106
+ if @options[:log_file]
107
+ logio = File.open(@options[:log_file], "a")
108
+ end
109
+ Daemonize.daemonize(logio || STDOUT)
110
+ end
111
+
112
+ # EM's reactor is shutdown already when EXIT signal is
113
+ # caught. so that set handler for TERM, INT
114
+ %w(TERM INT).each { |i|
115
+ Signal.trap(i) {
116
+ if @node
117
+ # force the block to push next loop.
118
+ # EM.schedule gets the current thread stucked.
119
+ EventMachine.next_tick {
120
+ @node.close { EventMachine.stop }
121
+ @node = nil
122
+ }
123
+ end
124
+ }
125
+ }
126
+ Signal.trap(:EXIT) { remove_pidfile if @options[:daemonize] }
127
+
128
+ EventMachine.epoll
129
+ EventMachine.run {
130
+ @node = run_main(manifest, &blk)
131
+ raise "run_main() must return Isono::Node object: #{@node.class}" unless @node.is_a?(Isono::Node)
132
+ }
133
+ end
134
+
135
+ protected
136
+ def run_main(manifest, &blk)
137
+ Isono::Node.new(manifest).connect(@options[:amqp_server_uri]) { |n|
138
+ @node = n
139
+ self.instance_eval &blk
140
+ }
141
+ end
142
+
143
+ def default_node_id
144
+ raise NotImplementedError
145
+ end
146
+
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ require 'isono'
4
+
5
+ module Isono
6
+ module Runner
7
+ module CLI
8
+
9
+ def run_cli(manifest=nil, &blk)
10
+ Oneshot.new.run(manifest, &blk)
11
+ end
12
+ module_function :run_cli
13
+
14
+ class Oneshot < Runner::Base
15
+ include Logger
16
+
17
+ def run_main(manifest, &blk)
18
+ Isono::Node.new(manifest).connect(@options[:amqp_server_uri]) do |n|
19
+ exit(1) unless n.connected?
20
+ @node = n
21
+ self.instance_eval &blk
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -54,8 +54,10 @@ module Isono
54
54
  def job(command, run_cb=nil, fail_cb=nil, &blk)
55
55
  app = if run_cb.is_a?(Proc)
56
56
  proc {
57
- request.fail_cb do
58
- self.instance_eval(&fail_cb)
57
+ if fail_cb.is_a?(Proc)
58
+ response.fail_cb do
59
+ self.instance_eval(&fail_cb)
60
+ end
59
61
  end
60
62
 
61
63
  self.instance_eval(&run_cb)
@@ -131,66 +133,32 @@ module Isono
131
133
  load_module NodeModules::JobChannel
132
134
  end
133
135
 
134
- def start(manifest=nil, &blk)
135
- rpcsvr = Server.new(ARGV)
136
- rpcsvr.run(manifest, &blk)
136
+ def start(manifest=nil, opts={}, &blk)
137
+ rpcsvr = Server.new(blk)
138
+ rpcsvr.run(manifest)
137
139
  end
138
140
  module_function :start
139
141
 
140
- class Server
141
- def initialize(argv)
142
- @argv = argv.dup
143
-
144
- @options = {
145
- :amqp_server_uri => URI.parse('amqp://guest:guest@localhost/'),
146
- }
147
-
148
- parser.parse! @argv
149
- end
150
-
151
- def parser
152
- @parser ||= OptionParser.new do |opts|
153
- opts.banner = "Usage: agent [options]"
154
-
155
- opts.separator ""
156
- opts.separator "Agent options:"
157
- opts.on( "-i", "--id ID", "Manually specify the Node ID" ) {|str| @options[:node_id] = str }
158
- opts.on( "-s", "--server AMQP_URI", "amqp broker server to connect" ) {|str|
159
- begin
160
- @options[:amqp_server_uri] = URI.parse(str)
161
- rescue URI::InvalidURIError => e
162
- abort "#{e}"
163
- end
164
- }
165
- end
166
- end
167
-
168
- def run(manifest=nil, &blk)
169
- %w(EXIT).each { |i|
170
- Signal.trap(i) { Isono::Node.stop }
171
- }
172
-
173
- # .to_s to avoid nil -> String conversion failure.
174
- if @options[:node_id]
175
- manifest.node_instance_id(@options[:node_id])
176
- elsif manifest.node_instance_id.nil?
177
- abort("[ERROR]: manifest.node_istance_id is not set")
178
- end
179
-
180
- EventMachine.epoll
181
- EventMachine.run {
182
- @node = Isono::Node.new(manifest)
183
- @node.connect(@options[:amqp_server_uri], @options) do
184
- self.instance_eval(&blk) if blk
185
- end
186
- }
142
+ class Server < Base
143
+ def initialize(builder_block)
144
+ super()
145
+ @builder_block = builder_block
187
146
  end
188
-
147
+
148
+ # DSL method
189
149
  def endpoint(endpoint, builder)
190
150
  raise TypeError unless builder.respond_to?(:build)
191
151
  builder.build(endpoint, @node)
192
152
  end
193
153
 
154
+ protected
155
+ def run_main(manifest=nil)
156
+ @node = Isono::Node.new(manifest)
157
+ @node.connect(@options[:amqp_server_uri]) do
158
+ self.instance_eval(&@builder_block) if @builder_block
159
+ end
160
+ end
161
+
194
162
  end
195
163
 
196
164
  end
@@ -18,25 +18,29 @@ module Isono
18
18
  @worker_threads = {}
19
19
  worker_num.times {
20
20
  t = Thread.new {
21
- loop {
22
- begin
23
- while op = @queue.pop
24
- if @queue.size > @opts[:stucked_queue_num] && Time.now - @last_stuck_warn_at > 5.0
25
- logger.warn("too many stacked workers: #{@queue.size}")
26
- @last_stuck_warn_at = Time.now
27
- end
28
- op.call
21
+ begin
22
+ while op = @queue.pop
23
+ if @queue.size > @opts[:stucked_queue_num] && Time.now - @last_stuck_warn_at > 5.0
24
+ logger.warn("too many stacked jobs: #{@queue.size}")
25
+ @last_stuck_warn_at = Time.now
29
26
  end
30
- rescue WorkerTerminateError
31
- # someone indicated to terminate this thread
32
- # exit from the current loop
33
- break
34
- rescue Exception => e
35
- self.logger.error(e)
27
+
28
+ op.call
36
29
  end
37
- }
38
- @worker_threads.delete(Thread.current.__id__)
39
- logger.info("#{Thread.current} is being terminated")
30
+ rescue WorkerTerminateError
31
+ # someone indicated to terminate this thread
32
+ # exit from the current loop
33
+ break
34
+ rescue Exception => e
35
+ logger.error(e)
36
+ retry
37
+ ensure
38
+ EM.schedule {
39
+ @worker_threads.delete(Thread.current.__id__)
40
+ logger.debug("#{Thread.current} is being terminated")
41
+ }
42
+ end
43
+
40
44
  }
41
45
  @worker_threads[t.__id__] = t
42
46
  }
@@ -100,8 +104,9 @@ module Isono
100
104
 
101
105
  # Immediatly shutdown all the worker threads
102
106
  def shutdown()
103
- @worker_threads.each {|t|
104
- t.raise WorkerTerminateError
107
+ @worker_threads.each {|id, t|
108
+ t.__send__(:raise, WorkerTerminateError)
109
+ Thread.pass
105
110
  }
106
111
  end
107
112
 
@@ -1,7 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  require 'digest/sha1'
4
- require 'hmac-sha1'
5
4
  require 'thread'
6
5
  require 'stringio'
7
6
 
@@ -44,7 +43,7 @@ module Isono
44
43
  when 'Linux'
45
44
  `/sbin/ip route get 8.8.8.8`.split("\n")[0].split.last
46
45
  when 'SunOS'
47
- `/sbin/ifconfig $(route get 1.1.1.1 | awk '$1 == "interface:" {print $2}') | awk '$1 == "inet" { print $2 }'`
46
+ `/sbin/ifconfig $(/usr/sbin/route -n get 1.1.1.1 | awk '$1 == "interface:" {print $2}') | awk '$1 == "inet" { print $2 }'`
48
47
  else
49
48
  raise "Unsupported platform to detect gateway IP address: #{`/bin/uname`}"
50
49
  end
@@ -171,6 +170,7 @@ module Isono
171
170
  super()
172
171
  @thread_wait = th
173
172
  @timer_sig = EventMachine.add_timer(timeout) {
173
+ @on_timeout_hook.call if @on_timeout_hook
174
174
  error(TimeoutError.new)
175
175
  }
176
176
  end
@@ -180,13 +180,21 @@ module Isono
180
180
  @thread_called = Thread.current
181
181
  end
182
182
 
183
-
184
183
  def error(ex)
185
184
  raise TypeError unless ex.is_a?(Exception)
186
185
  self.enq(ex)
187
186
  @thread_called = Thread.current
188
187
  end
189
188
 
189
+ def on_timeout(&blk)
190
+ @on_timeout_hook = blk
191
+ end
192
+
193
+ def cancel
194
+ EventMachine.cancel_timer(@timer_sig) rescue nil
195
+ @on_timeout_hook = nil
196
+ end
197
+
190
198
  def wait
191
199
  if (@thread_called == @thread_wait || @thread_called == Thread.current ) &&
192
200
  self.empty?
@@ -203,7 +211,7 @@ module Isono
203
211
  end
204
212
  ensure
205
213
  @thread_wait = nil
206
- EventMachine.cancel_timer(@timer_sig) rescue nil
214
+ self.cancel
207
215
  end
208
216
  end
209
217