isono 0.1.0 → 0.2.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.
@@ -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