ztk 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/ztk/base.rb +70 -24
- data/lib/ztk/benchmark.rb +12 -15
- data/lib/ztk/command.rb +62 -43
- data/lib/ztk/logger.rb +8 -2
- data/lib/ztk/parallel.rb +16 -15
- data/lib/ztk/rescue_retry.rb +10 -12
- data/lib/ztk/spinner.rb +12 -5
- data/lib/ztk/ssh.rb +139 -109
- data/lib/ztk/tcp_socket_check.rb +16 -24
- data/lib/ztk/version.rb +1 -1
- data/spec/spec_helper.rb +8 -0
- data/spec/ztk/base_spec.rb +41 -0
- data/spec/ztk/benchmark_spec.rb +10 -12
- data/spec/ztk/command_spec.rb +142 -9
- data/spec/ztk/config_spec.rb +16 -16
- data/spec/ztk/logger_spec.rb +1 -1
- data/spec/ztk/rescue_retry_spec.rb +3 -3
- data/spec/ztk/spinner_spec.rb +51 -0
- data/spec/ztk/ssh_spec.rb +385 -119
- data/spec/ztk/tcp_socket_check_spec.rb +6 -6
- metadata +8 -4
data/lib/ztk/base.rb
CHANGED
@@ -35,26 +35,70 @@ module ZTK
|
|
35
35
|
# and extend functionality as appropriate.
|
36
36
|
class Base
|
37
37
|
|
38
|
+
class << self
|
39
|
+
|
40
|
+
# @param [Hash] config Configuration options hash.
|
41
|
+
# @option config [IO] :stdout Instance of IO to be used for STDOUT.
|
42
|
+
# @option config [IO] :stderr Instance of IO to be used for STDERR.
|
43
|
+
# @option config [IO] :stdin Instance of IO to be used for STDIN.
|
44
|
+
# @option config [Logger] :logger Instance of Logger to be used for logging.
|
45
|
+
def build_config(configuration={})
|
46
|
+
if configuration.is_a?(OpenStruct)
|
47
|
+
configuration = configuration.send(:table)
|
48
|
+
end
|
49
|
+
|
50
|
+
rails_logger = Rails.logger if defined?(Rails)
|
51
|
+
|
52
|
+
config = OpenStruct.new({
|
53
|
+
:stdout => $stdout,
|
54
|
+
:stderr => $stderr,
|
55
|
+
:stdin => $stdin,
|
56
|
+
:logger => ($logger || rails_logger || ZTK::Logger.new("/dev/null"))
|
57
|
+
}.merge(configuration))
|
58
|
+
|
59
|
+
(config.stdout && config.stdout.respond_to?(:sync=)) and config.stdout.sync = true
|
60
|
+
(config.stderr && config.stderr.respond_to?(:sync=)) and config.stderr.sync = true
|
61
|
+
(config.stdin && config.stdin.respond_to?(:sync=)) and config.stdin.sync = true
|
62
|
+
(config.logger && config.logger.respond_to?(:sync=)) and config.logger.sync = true
|
63
|
+
|
64
|
+
config
|
65
|
+
end
|
66
|
+
|
67
|
+
# Removes all key-value pairs which are not core so values do not bleed
|
68
|
+
# into classes they are not meant for.
|
69
|
+
#
|
70
|
+
# This method will leave :stdout, :stderr, :stdin and :logger key-values
|
71
|
+
# intact, while removing all other key-value pairs.
|
72
|
+
def sanitize_config(configuration={})
|
73
|
+
if configuration.is_a?(OpenStruct)
|
74
|
+
configuration = configuration.send(:table)
|
75
|
+
end
|
76
|
+
|
77
|
+
config = configuration.reject do |key,value|
|
78
|
+
!(%w(stdout stderr stdin logger).map(&:to_sym).include?(key))
|
79
|
+
end
|
80
|
+
|
81
|
+
config
|
82
|
+
end
|
83
|
+
|
84
|
+
def log_and_raise(logger, exception, message, shift=1)
|
85
|
+
if logger.is_a?(ZTK::Logger)
|
86
|
+
logger.shift(:fatal, shift) { "EXCEPTION: #{exception.inspect} - #{message.inspect}" }
|
87
|
+
else
|
88
|
+
logger.fatal { "EXCEPTION: #{exception.inspect} - #{message.inspect}" }
|
89
|
+
end
|
90
|
+
raise exception, message
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
38
95
|
# @param [Hash] config Configuration options hash.
|
39
96
|
# @option config [IO] :stdout Instance of IO to be used for STDOUT.
|
40
97
|
# @option config [IO] :stderr Instance of IO to be used for STDERR.
|
41
98
|
# @option config [IO] :stdin Instance of IO to be used for STDIN.
|
42
99
|
# @option config [Logger] :logger Instance of Logger to be used for logging.
|
43
100
|
def initialize(config={})
|
44
|
-
|
45
|
-
@config = OpenStruct.new({
|
46
|
-
:stdout => $stdout,
|
47
|
-
:stderr => $stderr,
|
48
|
-
:stdin => $stdin,
|
49
|
-
:logger => $logger
|
50
|
-
}.merge(config))
|
51
|
-
|
52
|
-
@config.stdout.respond_to?(:sync=) and @config.stdout.sync = true
|
53
|
-
@config.stderr.respond_to?(:sync=) and @config.stderr.sync = true
|
54
|
-
@config.stdin.respond_to?(:sync=) and @config.stdin.sync = true
|
55
|
-
@config.logger.respond_to?(:sync=) and @config.logger.sync = true
|
56
|
-
|
57
|
-
log(:debug) { "config(#{@config.inspect})" }
|
101
|
+
@config = Base.build_config(config)
|
58
102
|
end
|
59
103
|
|
60
104
|
# Configuration OpenStruct accessor method.
|
@@ -73,7 +117,15 @@ module ZTK
|
|
73
117
|
end
|
74
118
|
end
|
75
119
|
|
76
|
-
|
120
|
+
def log_and_raise(exception, message)
|
121
|
+
Base.log_and_raise(config.logger, exception, message, 2)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Direct logging method.
|
125
|
+
#
|
126
|
+
# This method provides direct writing of data to the current log device.
|
127
|
+
# This is mainly used for pushing STDOUT and STDERR into the log file in
|
128
|
+
# ZTK::SSH and ZTK::Command, but could easily be used by other classes.
|
77
129
|
#
|
78
130
|
# The value returned in the block is passed down to the logger specified in
|
79
131
|
# the classes configuration.
|
@@ -81,24 +133,18 @@ module ZTK
|
|
81
133
|
# @param [Symbol] method_name This should be any one of [:debug, :info, :warn, :error, :fatal].
|
82
134
|
# @yield No value is passed to the block.
|
83
135
|
# @yieldreturn [String] The message to log.
|
84
|
-
def log(method_name, &block)
|
85
|
-
if block_given?
|
86
|
-
@config.logger and @config.logger.method(method_name.to_sym).call { yield }
|
87
|
-
else
|
88
|
-
raise BaseError, "You must supply a block to the log method!"
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
136
|
def direct_log(log_level, &blocK)
|
93
137
|
@config.logger.nil? and raise BaseError, "You must supply a logger for direct logging support!"
|
94
138
|
|
95
139
|
if !block_given?
|
96
|
-
|
140
|
+
log_and_raise(BaseError, "You must supply a block to the log method!")
|
97
141
|
elsif (@config.logger.level <= ZTK::Logger.const_get(log_level.to_s.upcase))
|
98
142
|
if @config.logger.respond_to?(:logdev)
|
99
143
|
@config.logger.logdev.write(yield)
|
144
|
+
@config.logger.logdev.respond_to?(:flush) and @config.logger.logdev.flush
|
100
145
|
else
|
101
146
|
@config.logger.instance_variable_get(:@logdev).instance_variable_get(:@dev).write(yield)
|
147
|
+
@config.logger.instance_variable_get(:@logdev).instance_variable_get(:@dev).respond_to?(:flush) and @config.logger.instance_variable_get(:@logdev).instance_variable_get(:@dev).flush
|
102
148
|
end
|
103
149
|
end
|
104
150
|
end
|
data/lib/ztk/benchmark.rb
CHANGED
@@ -34,30 +34,27 @@ module ZTK
|
|
34
34
|
class << self
|
35
35
|
|
36
36
|
def bench(options={}, &block)
|
37
|
-
|
37
|
+
options = Base.build_config(options)
|
38
|
+
options.logger.debug { "options(#{options.inspect})" }
|
38
39
|
|
39
|
-
|
40
|
+
!block_given? and Base.log_and_raise(options.logger, BenchmarkError, "You must supply a block!")
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
message = options[:message]
|
44
|
-
mark = options[:mark]
|
42
|
+
check = [options.message, options.mark]
|
43
|
+
(check.any?{ |z| !z.nil? } && !check.all?{ |z| !z.nil? }) and Base.log_and_raise(options.logger, BenchmarkError, "You must supply both a message and a mark!")
|
45
44
|
|
46
|
-
|
47
|
-
|
48
|
-
(!message.nil? && !mark.nil?) and stdout.print("#{message} ")
|
45
|
+
(options.message && options.mark) and options.stdout.print("#{options.message} ")
|
49
46
|
benchmark = ::Benchmark.realtime do
|
50
|
-
if message.
|
51
|
-
|
52
|
-
else
|
53
|
-
ZTK::Spinner.spin do
|
47
|
+
if (options.message && options.mark)
|
48
|
+
ZTK::Spinner.spin(Base.sanitize_config(options)) do
|
54
49
|
yield
|
55
50
|
end
|
51
|
+
else
|
52
|
+
yield
|
56
53
|
end
|
57
54
|
end
|
58
55
|
|
59
|
-
(
|
60
|
-
logger
|
56
|
+
(options.message && options.mark) and options.stdout.print("#{options.mark}\n" % benchmark)
|
57
|
+
options.logger.info { "#{options.message} #{options.mark}" }
|
61
58
|
|
62
59
|
benchmark
|
63
60
|
end
|
data/lib/ztk/command.rb
CHANGED
@@ -19,6 +19,7 @@
|
|
19
19
|
################################################################################
|
20
20
|
|
21
21
|
require "ostruct"
|
22
|
+
require "timeout"
|
22
23
|
|
23
24
|
module ZTK
|
24
25
|
|
@@ -41,8 +42,11 @@ module ZTK
|
|
41
42
|
# @author Zachary Patten <zachary@jovelabs.net>
|
42
43
|
class Command < ZTK::Base
|
43
44
|
|
44
|
-
def initialize(
|
45
|
-
super(
|
45
|
+
def initialize(configuration={})
|
46
|
+
super({
|
47
|
+
:timeout => 600
|
48
|
+
}.merge(configuration))
|
49
|
+
config.logger.debug { "config(#{config.inspect})" }
|
46
50
|
end
|
47
51
|
|
48
52
|
def inspect
|
@@ -56,8 +60,8 @@ module ZTK
|
|
56
60
|
# @param [Hash] options The options hash for executing the command.
|
57
61
|
#
|
58
62
|
# @return [OpenStruct#output] The output of the command, both STDOUT and
|
59
|
-
# STDERR.
|
60
|
-
# @return [OpenStruct#
|
63
|
+
# STDERR combined.
|
64
|
+
# @return [OpenStruct#exit_code] The exit code of the process.
|
61
65
|
#
|
62
66
|
# @example Execute a command:
|
63
67
|
#
|
@@ -73,16 +77,21 @@ module ZTK
|
|
73
77
|
end
|
74
78
|
|
75
79
|
options = OpenStruct.new({ :exit_code => 0, :silence => false }.merge(options))
|
76
|
-
|
77
|
-
|
80
|
+
|
81
|
+
config.logger.debug { "config(#{config.inspect})" }
|
82
|
+
config.logger.debug { "options(#{options.inspect})" }
|
83
|
+
config.logger.info { "command(#{command.inspect})" }
|
78
84
|
|
79
85
|
output = ""
|
86
|
+
exit_code = -1
|
80
87
|
stdout_header = false
|
81
88
|
stderr_header = false
|
82
89
|
|
83
90
|
parent_stdout_reader, child_stdout_writer = IO.pipe
|
84
91
|
parent_stderr_reader, child_stderr_writer = IO.pipe
|
85
92
|
|
93
|
+
start_time = Time.now.utc
|
94
|
+
|
86
95
|
pid = Process.fork do
|
87
96
|
parent_stdout_reader.close
|
88
97
|
parent_stderr_reader.close
|
@@ -105,61 +114,71 @@ module ZTK
|
|
105
114
|
direct_log(:debug) { log_header("COMMAND") }
|
106
115
|
direct_log(:debug) { "#{command}\n" }
|
107
116
|
direct_log(:debug) { log_header("STARTED") }
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
117
|
+
|
118
|
+
begin
|
119
|
+
Timeout.timeout(config.timeout) do
|
120
|
+
loop do
|
121
|
+
pipes = IO.select(reader_writer_map.keys, [], reader_writer_map.keys).first
|
122
|
+
pipes.each do |pipe|
|
123
|
+
data = pipe.read
|
124
|
+
next if (data.nil? || data.empty?)
|
125
|
+
|
126
|
+
case reader_writer_key[pipe]
|
127
|
+
when :stdout then
|
128
|
+
if !stdout_header
|
129
|
+
direct_log(:debug) { log_header("STDOUT") }
|
130
|
+
stdout_header = true
|
131
|
+
stderr_header = false
|
132
|
+
end
|
133
|
+
reader_writer_map[pipe].write(data) unless options.silence
|
134
|
+
direct_log(:debug) { data }
|
135
|
+
|
136
|
+
when :stderr then
|
137
|
+
if !stderr_header
|
138
|
+
direct_log(:warn) { log_header("STDERR") }
|
139
|
+
stderr_header = true
|
140
|
+
stdout_header = false
|
141
|
+
end
|
142
|
+
reader_writer_map[pipe].write(data) unless options.silence
|
143
|
+
direct_log(:warn) { data }
|
144
|
+
end
|
145
|
+
|
146
|
+
output += data
|
131
147
|
end
|
132
|
-
reader_writer_map
|
133
|
-
direct_log(:warn) { data }
|
148
|
+
break if reader_writer_map.keys.all?{ |reader| reader.eof? }
|
134
149
|
end
|
135
|
-
|
136
|
-
output += data
|
137
150
|
end
|
151
|
+
rescue Timeout::Error => e
|
152
|
+
direct_log(:debug) { log_header("TIMEOUT") }
|
153
|
+
log_and_raise(CommandError, "Process timed out after #{config.timeout} seconds!")
|
138
154
|
end
|
139
|
-
direct_log(:debug) { log_header("STOPPED") }
|
140
155
|
|
141
156
|
Process.waitpid(pid)
|
157
|
+
direct_log(:debug) { log_header("STOPPED") }
|
142
158
|
|
143
159
|
parent_stdout_reader.close
|
144
160
|
parent_stderr_reader.close
|
145
161
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
log(:fatal) { message }
|
151
|
-
raise CommandError, message
|
162
|
+
if RUBY_VERSION >= "1.9"
|
163
|
+
exit_code = $?.exitstatus
|
164
|
+
else
|
165
|
+
exit_code = $?
|
152
166
|
end
|
153
167
|
|
154
|
-
|
168
|
+
config.logger.debug { "exit_code(#{exit_code})" }
|
169
|
+
|
170
|
+
if (exit_code != options.exit_code)
|
171
|
+
log_and_raise(CommandError, "exec(#{command.inspect}, #{options.inspect}) failed! [#{exit_code}]")
|
172
|
+
end
|
173
|
+
OpenStruct.new(:output => output, :exit_code => exit_code)
|
155
174
|
end
|
156
175
|
|
157
176
|
def upload(*args)
|
158
|
-
|
177
|
+
log_and_raise(CommandError, "Not Supported")
|
159
178
|
end
|
160
179
|
|
161
180
|
def download(*args)
|
162
|
-
|
181
|
+
log_and_raise(CommandError, "Not Supported")
|
163
182
|
end
|
164
183
|
|
165
184
|
end
|
data/lib/ztk/logger.rb
CHANGED
@@ -56,6 +56,11 @@ module ZTK
|
|
56
56
|
self.instance_variable_get(:@logdev).instance_variable_get(:@dev)
|
57
57
|
end
|
58
58
|
|
59
|
+
def shift(severity, shift=0, &block)
|
60
|
+
severity = ZTK::Logger.const_get(severity.to_s.upcase)
|
61
|
+
add(severity, nil, nil, shift, &block)
|
62
|
+
end
|
63
|
+
|
59
64
|
|
60
65
|
private
|
61
66
|
|
@@ -81,17 +86,18 @@ module ZTK
|
|
81
86
|
# @param [String] progname Optional name of the program to prefix the log
|
82
87
|
# entry with.
|
83
88
|
# @yieldreturn [String] The block should return the desired log message.
|
84
|
-
def add(severity, message
|
89
|
+
def add(severity, message=nil, progname=nil, shift=0, &block)
|
85
90
|
return if (@level > severity)
|
86
91
|
|
87
92
|
msg = (block && block.call)
|
88
93
|
(msg.nil? || msg.strip.empty?) and return
|
89
94
|
@hostname ||= %x(hostname -s).chomp.strip
|
90
|
-
called_by = "#{@hostname}:#{parse_caller(caller[1])}"
|
95
|
+
called_by = "#{@hostname}:#{parse_caller(caller[1+shift])}"
|
91
96
|
message = [message, progname, msg].flatten.compact.join(": ")
|
92
97
|
message = "%19s.%06d|%05d|%5s|%60s%s\n" % [Time.now.utc.strftime("%Y-%m-%d|%H:%M:%S"), Time.now.utc.usec, Process.pid, SEVERITIES[severity], called_by, message]
|
93
98
|
|
94
99
|
@logdev.write(message)
|
100
|
+
@logdev.respond_to?(:flush) and @logdev.flush
|
95
101
|
|
96
102
|
true
|
97
103
|
end
|
data/lib/ztk/parallel.rb
CHANGED
@@ -76,12 +76,13 @@ module ZTK
|
|
76
76
|
# @option config [Integer] :max_forks Maximum number of forks to use.
|
77
77
|
# @option config [Proc] :before_fork (nil) Proc to call before forking.
|
78
78
|
# @option config [Proc] :after_fork (nil) Proc to call after forking.
|
79
|
-
def initialize(
|
79
|
+
def initialize(configuration={})
|
80
80
|
super({
|
81
81
|
:max_forks => MAX_FORKS
|
82
|
-
}.merge(
|
82
|
+
}.merge(configuration))
|
83
|
+
config.logger.debug { "config(#{config.inspect})" }
|
83
84
|
|
84
|
-
|
85
|
+
(config.max_forks < 1) and log_and_raise(ParallelError, "max_forks must be equal to or greater than one!")
|
85
86
|
|
86
87
|
@forks = Array.new
|
87
88
|
@results = Array.new
|
@@ -95,26 +96,26 @@ module ZTK
|
|
95
96
|
# the parent processes result set.
|
96
97
|
# @return [Integer] Returns the pid of the child process forked.
|
97
98
|
def process(&block)
|
98
|
-
|
99
|
+
!block_given? and log_and_raise(ParallelError, "You must supply a block to the process method!")
|
99
100
|
|
100
|
-
|
101
|
+
config.logger.debug { "forks(#{@forks.inspect})" }
|
101
102
|
|
102
|
-
while (@forks.count >=
|
103
|
+
while (@forks.count >= config.max_forks) do
|
103
104
|
wait
|
104
105
|
end
|
105
106
|
|
106
107
|
child_reader, parent_writer = IO.pipe
|
107
108
|
parent_reader, child_writer = IO.pipe
|
108
109
|
|
109
|
-
|
110
|
+
config.before_fork and config.before_fork.call(Process.pid)
|
110
111
|
pid = Process.fork do
|
111
|
-
|
112
|
+
config.after_fork and config.after_fork.call(Process.pid)
|
112
113
|
|
113
114
|
parent_writer.close
|
114
115
|
parent_reader.close
|
115
116
|
|
116
117
|
if !(data = block.call).nil?
|
117
|
-
|
118
|
+
config.logger.debug { "write(#{data.inspect})" }
|
118
119
|
child_writer.write(Base64.encode64(Marshal.dump(data)))
|
119
120
|
end
|
120
121
|
|
@@ -122,7 +123,7 @@ module ZTK
|
|
122
123
|
child_writer.close
|
123
124
|
Process.exit!(0)
|
124
125
|
end
|
125
|
-
|
126
|
+
config.after_fork and config.after_fork.call(Process.pid)
|
126
127
|
|
127
128
|
child_reader.close
|
128
129
|
child_writer.close
|
@@ -142,12 +143,12 @@ module ZTK
|
|
142
143
|
# status and data returned from the process block. If wait2() fails nil
|
143
144
|
# is returned.
|
144
145
|
def wait
|
145
|
-
|
146
|
-
|
146
|
+
config.logger.debug { "wait" }
|
147
|
+
config.logger.debug { "forks(#{@forks.inspect})" }
|
147
148
|
pid, status = (Process.wait2(-1) rescue nil)
|
148
149
|
if !pid.nil? && !status.nil? && !(fork = @forks.select{ |f| f[:pid] == pid }.first).nil?
|
149
150
|
data = (Marshal.load(Base64.decode64(fork[:reader].read.to_s)) rescue nil)
|
150
|
-
|
151
|
+
config.logger.debug { "read(#{data.inspect})" }
|
151
152
|
!data.nil? and @results.push(data)
|
152
153
|
fork[:reader].close
|
153
154
|
fork[:writer].close
|
@@ -162,7 +163,7 @@ module ZTK
|
|
162
163
|
#
|
163
164
|
# @return [Array<Object>] The results from all of the *process* blocks.
|
164
165
|
def waitall
|
165
|
-
|
166
|
+
config.logger.debug { "waitall" }
|
166
167
|
while @forks.count > 0
|
167
168
|
self.wait
|
168
169
|
end
|
@@ -173,7 +174,7 @@ module ZTK
|
|
173
174
|
#
|
174
175
|
# @return [Integer] Current number of active forks.
|
175
176
|
def count
|
176
|
-
|
177
|
+
config.logger.debug { "count(#{@forks.count})" }
|
177
178
|
@forks.count
|
178
179
|
end
|
179
180
|
|