jobmanager 1.0.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.
- data/FAQ.txt +151 -0
- data/History.txt +2 -0
- data/LICENSE +20 -0
- data/Manifest.txt +42 -0
- data/README.txt +395 -0
- data/Rakefile +17 -0
- data/bin/jobmanager +20 -0
- data/examples/email_settings.rb +14 -0
- data/examples/example.rb +23 -0
- data/examples/jobmanager.yaml +66 -0
- data/examples/mysql_backup.rb +18 -0
- data/lib/jobmanager.rb +1 -0
- data/lib/jobmanager/application.rb +455 -0
- data/lib/jobmanager/applicationconfig.rb +89 -0
- data/lib/jobmanager/applicationlogger.rb +306 -0
- data/lib/jobmanager/applicationoptionparser.rb +163 -0
- data/lib/jobmanager/system.rb +240 -0
- data/lib/jobmanager/teestream.rb +78 -0
- data/lib/jobmanager/util.rb +25 -0
- data/test/configs/all_values.yaml +33 -0
- data/test/configs/bad_email_condition.yaml +7 -0
- data/test/configs/bad_no_central_log_file.yaml +7 -0
- data/test/configs/bad_number_of_job_logs.yaml +7 -0
- data/test/configs/bad_timeout_zero.yaml +7 -0
- data/test/configs/email_settings.rb +16 -0
- data/test/configs/incomplete_1.yaml +23 -0
- data/test/configs/jobmanager_1.yaml +9 -0
- data/test/configs/jobmanager_2.yaml +11 -0
- data/test/configs/jobmanager_3.yaml +13 -0
- data/test/configs/jobmanager_4_bad_central_log_file.yaml +9 -0
- data/test/configs/jobmanager_4_bad_email_settings_file.yaml +9 -0
- data/test/configs/jobmanager_4_bad_job_logs_directory_1.yaml +9 -0
- data/test/configs/jobmanager_4_bad_job_logs_directory_2.yaml +9 -0
- data/test/configs/minimum_plus_email_settings.yaml +9 -0
- data/test/configs/minimum_required.yaml +5 -0
- data/test/helpers.rb +7 -0
- data/test/mock_syslog.rb +68 -0
- data/test/test_applicationlogger.rb +145 -0
- data/test/test_config.rb +208 -0
- data/test/test_jobmanager.rb +443 -0
- data/test/test_system.rb +206 -0
- data/test/test_teestream.rb +55 -0
- metadata +172 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
email_condition: always
|
2
|
+
|
3
|
+
job_logs_directory: "temp/logs"
|
4
|
+
|
5
|
+
central_log_mode: syslog
|
6
|
+
|
7
|
+
email_settings_file: email_settings.rb
|
8
|
+
|
9
|
+
email_subject: "jobmanager results for job <%=job_name%>, command <%=command%>, user <%=user_name%>, host <%=host_name%>, result <%=result%>"
|
10
|
+
|
11
|
+
zip_rotated_log_file: true
|
data/test/helpers.rb
ADDED
data/test/mock_syslog.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'etc'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'syslog_logger'
|
8
|
+
|
9
|
+
#
|
10
|
+
# Global Variables for testing purposes
|
11
|
+
#
|
12
|
+
$test_syslog_logger = nil
|
13
|
+
|
14
|
+
#
|
15
|
+
# This class is a mock up, or redefinition of the methods in
|
16
|
+
# SyslogLogger. The implementation simply saves the log messages
|
17
|
+
# written to this class, instead of writing them to syslog. It
|
18
|
+
# facilitates the unit tests to confirm that the messages logged to
|
19
|
+
# SyslogLogger are correct without requiring the test to be dependent
|
20
|
+
# on the system's syslog facilities. This class is only included by
|
21
|
+
# unit test files.
|
22
|
+
#
|
23
|
+
class SyslogLogger
|
24
|
+
attr_accessor :level
|
25
|
+
|
26
|
+
def string()
|
27
|
+
return @stringio.string()
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(program_name = "unknown")
|
31
|
+
@stringio = StringIO.new
|
32
|
+
@level = Logger::INFO
|
33
|
+
|
34
|
+
$test_syslog_logger = self
|
35
|
+
end
|
36
|
+
|
37
|
+
def add(severity, message = nil)
|
38
|
+
return true if severity < @level
|
39
|
+
@stringio << message << "\n"
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
|
43
|
+
def close()
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.make_methods(method)
|
47
|
+
str = "def #{method}(message = nil)\n"
|
48
|
+
str << " if (SyslogLogger::LOGGER_MAP.keys.find() {|key| :#{method} == key})\n"
|
49
|
+
str << " if (SyslogLogger::LOGGER_LEVEL_MAP[:#{method}] < @level) then return true end\n"
|
50
|
+
str << " @stringio << message << \"\\n\"\n"
|
51
|
+
str << " return true\n"
|
52
|
+
str << " end\n"
|
53
|
+
str << "end\n"
|
54
|
+
|
55
|
+
str << "def #{method}?\n"
|
56
|
+
str << " @level <= Logger::#{method.to_s.upcase}\n"
|
57
|
+
str << "end"
|
58
|
+
|
59
|
+
eval(str)
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
LOGGER_MAP.each_key do |level|
|
64
|
+
make_methods(level)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'jobmanager/applicationlogger.rb'
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require_relative('mock_syslog')
|
5
|
+
require_relative('helpers')
|
6
|
+
|
7
|
+
#
|
8
|
+
# This class tests the assorted JobManager::ApplicationLogger* classes.
|
9
|
+
#
|
10
|
+
|
11
|
+
class TestApplicationLogger < Test::Unit::TestCase
|
12
|
+
|
13
|
+
include TestHelpers
|
14
|
+
|
15
|
+
LOG_DIR = "#{TEMP_DIR}/logs"
|
16
|
+
LOG_FILE = "#{LOG_DIR}/application.log"
|
17
|
+
|
18
|
+
USER_NAME = Etc.getpwuid(Process::Sys.getuid()).name()
|
19
|
+
JOB_NAME = "ze_echoer"
|
20
|
+
|
21
|
+
|
22
|
+
LevelMessagePair = Struct.new(:level, :message)
|
23
|
+
|
24
|
+
def setup
|
25
|
+
teardown()
|
26
|
+
|
27
|
+
FileUtils.mkdir_p(LOG_DIR)
|
28
|
+
end
|
29
|
+
|
30
|
+
def teardown
|
31
|
+
FileUtils::rm_r(TEMP_DIR, :force => true)
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_log_em_all_list()
|
35
|
+
|
36
|
+
return [ LevelMessagePair.new("DEBUG", "debug"),
|
37
|
+
LevelMessagePair.new("INFO", "info"),
|
38
|
+
LevelMessagePair.new("WARN", "warn"),
|
39
|
+
LevelMessagePair.new("ERROR", "error"),
|
40
|
+
LevelMessagePair.new("FATAL", "fatal"),
|
41
|
+
LevelMessagePair.new("", "unknown"),
|
42
|
+
LevelMessagePair.new("INFO", "write"),
|
43
|
+
LevelMessagePair.new("INFO", "<<"),
|
44
|
+
LevelMessagePair.new("INFO", "print"),
|
45
|
+
LevelMessagePair.new("INFO", "puts_1"),
|
46
|
+
LevelMessagePair.new("INFO", "puts_2"),
|
47
|
+
LevelMessagePair.new("INFO", "add"),
|
48
|
+
LevelMessagePair.new("INFO", "log"),
|
49
|
+
LevelMessagePair.new("WARN", "warn"),
|
50
|
+
LevelMessagePair.new("ERROR", "error"),
|
51
|
+
LevelMessagePair.new("FATAL", "fatal"),
|
52
|
+
LevelMessagePair.new("", "unknown")
|
53
|
+
]
|
54
|
+
end
|
55
|
+
|
56
|
+
def log_em_all(logger)
|
57
|
+
logger.debug("debug")
|
58
|
+
logger.info("info")
|
59
|
+
logger.warn("warn")
|
60
|
+
logger.error("error")
|
61
|
+
logger.fatal("fatal")
|
62
|
+
logger.unknown("unknown")
|
63
|
+
|
64
|
+
logger.write("write")
|
65
|
+
logger << "<<"
|
66
|
+
logger.print "print"
|
67
|
+
logger.puts ["puts_1", "puts_2"]
|
68
|
+
logger.add(Logger::INFO, "add")
|
69
|
+
logger.log(Logger::INFO, "log")
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_log_file
|
73
|
+
logger = JobManager::ApplicationFileLogger.new(LOG_FILE, USER_NAME, JOB_NAME)
|
74
|
+
|
75
|
+
logger.level = Logger::DEBUG
|
76
|
+
assert_equal(Logger::DEBUG, logger.level)
|
77
|
+
log_em_all(logger)
|
78
|
+
|
79
|
+
logger.level = Logger::WARN
|
80
|
+
log_em_all(logger)
|
81
|
+
|
82
|
+
logger.close
|
83
|
+
|
84
|
+
file_contents = File.read(LOG_FILE)
|
85
|
+
lines = file_contents.split("\n")
|
86
|
+
|
87
|
+
print "\ntest_log_file file contents:\n"
|
88
|
+
print file_contents, "\n"
|
89
|
+
|
90
|
+
expected_list = get_log_em_all_list()
|
91
|
+
|
92
|
+
assert_equal(expected_list.length(), lines.length())
|
93
|
+
assert_equal(file_contents, logger.string())
|
94
|
+
|
95
|
+
0.upto(expected_list.length() - 1).each do |i|
|
96
|
+
assert_match(/\[#{USER_NAME}. #{JOB_NAME}\]/, lines[i])
|
97
|
+
assert_match(expected_list[i].level, lines[i])
|
98
|
+
assert_match(expected_list[i].message, lines[i])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_io
|
103
|
+
# We don't need to test much with this class as this was tested
|
104
|
+
# indirectly by the ApplicationFileLogger test. However, lets
|
105
|
+
# make sure calling close on ApplicationIOLogger will not close
|
106
|
+
# STDOUT.
|
107
|
+
logger = JobManager::ApplicationIOLogger.new(STDOUT, USER_NAME, JOB_NAME)
|
108
|
+
|
109
|
+
logger.close
|
110
|
+
assert_equal(false, STDOUT.closed?())
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_syslog
|
114
|
+
program_name = "program_name"
|
115
|
+
logger = JobManager::ApplicationSyslogLogger.new(program_name, USER_NAME, JOB_NAME)
|
116
|
+
|
117
|
+
logger.level = Logger::DEBUG
|
118
|
+
assert_equal(Logger::DEBUG, logger.level)
|
119
|
+
log_em_all(logger)
|
120
|
+
|
121
|
+
logger.level = Logger::WARN
|
122
|
+
log_em_all(logger)
|
123
|
+
|
124
|
+
logger.close
|
125
|
+
|
126
|
+
syslog_contents = $test_syslog_logger.string()
|
127
|
+
lines = syslog_contents.split("\n")
|
128
|
+
|
129
|
+
print "\ntest_syslog contents:\n"
|
130
|
+
print syslog_contents, "\n"
|
131
|
+
|
132
|
+
expected_list = get_log_em_all_list()
|
133
|
+
|
134
|
+
assert_equal(expected_list.length(), lines.length)
|
135
|
+
assert_equal(syslog_contents, logger.string())
|
136
|
+
|
137
|
+
0.upto(expected_list.length() - 1).each do |i|
|
138
|
+
assert_match(/\[#{USER_NAME}. #{JOB_NAME}\]/, lines[i])
|
139
|
+
assert_match(expected_list[i].level, lines[i])
|
140
|
+
assert_match(expected_list[i].message, lines[i])
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
data/test/test_config.rb
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'relative'
|
8
|
+
require 'assertions'
|
9
|
+
|
10
|
+
require 'jobmanager/applicationconfig.rb'
|
11
|
+
require 'jobmanager/applicationoptionparser.rb'
|
12
|
+
|
13
|
+
require 'configtoolkit/yamlreader'
|
14
|
+
require 'configtoolkit/hashreader'
|
15
|
+
require 'configtoolkit/overridereader'
|
16
|
+
|
17
|
+
require 'test/unit'
|
18
|
+
require_relative('helpers')
|
19
|
+
|
20
|
+
#
|
21
|
+
# This class tests the configuration related classes:
|
22
|
+
# JobManager::ApplicationConfig and
|
23
|
+
# JobManager::ApplicationOptionParser.
|
24
|
+
#
|
25
|
+
|
26
|
+
class ConfigTest < Test::Unit::TestCase
|
27
|
+
include TestHelpers
|
28
|
+
|
29
|
+
CONFIG_DIR = PathRelativeToCaller.new("./configs")
|
30
|
+
|
31
|
+
def load_good_config(config_file)
|
32
|
+
config_file = File.join(CONFIG_DIR, config_file)
|
33
|
+
config = JobManager::ApplicationConfig.load(ConfigToolkit::YAMLReader.new(config_file))
|
34
|
+
|
35
|
+
config
|
36
|
+
end
|
37
|
+
|
38
|
+
def confirm_all_values(config)
|
39
|
+
assert_equal(:always, config.email_condition)
|
40
|
+
assert_equal(4, config.number_of_job_logs)
|
41
|
+
assert_equal(Pathname.new("temp/logs"), config.job_logs_directory)
|
42
|
+
assert_equal("/usr/designingpatterns/bin", config.command_path)
|
43
|
+
assert_equal(true, config.date_time_extension)
|
44
|
+
assert_equal('%F', config.date_time_extension_format)
|
45
|
+
assert_equal(true, config.zip_rotated_log_file)
|
46
|
+
assert_equal(1800, config.timeout)
|
47
|
+
assert_equal(Pathname.new("email_settings.rb"), config.email_settings_file)
|
48
|
+
assert_equal(:file, config.central_log_mode)
|
49
|
+
assert_equal(Pathname.new("temp/logs/jobmanager.log"), config.central_log_file)
|
50
|
+
assert_equal("ze_echoer", config.job_name)
|
51
|
+
assert_equal("echo hello", config.command)
|
52
|
+
assert_equal("sender_address@gmail.com", config.email_from_address)
|
53
|
+
assert_equal("receiver_address@gmail.com", config.email_to_address)
|
54
|
+
|
55
|
+
result = 'Success'
|
56
|
+
host_name = "jackfruit"
|
57
|
+
job_name = "mysql_backup"
|
58
|
+
|
59
|
+
email_subject = ERB.new(config.email_subject).result(binding)
|
60
|
+
assert_equal("jobmanager results for mysql_backup on jackfruit : Success",
|
61
|
+
email_subject)
|
62
|
+
end
|
63
|
+
|
64
|
+
def confirm_minimum_required(config)
|
65
|
+
assert_equal("ze_echoer", config.job_name)
|
66
|
+
assert_equal("echo hello", config.command)
|
67
|
+
assert_equal(Pathname.new("temp/logs"), config.job_logs_directory)
|
68
|
+
|
69
|
+
assert_equal(:on_failure, config.email_condition)
|
70
|
+
assert_equal(Pathname.new("email_settings.rb"), config.email_settings_file)
|
71
|
+
assert_equal(:syslog, config.central_log_mode)
|
72
|
+
assert_equal(3, config.number_of_job_logs)
|
73
|
+
assert_equal(true, config.date_time_extension)
|
74
|
+
assert_equal("%F_%T", config.date_time_extension_format)
|
75
|
+
assert_equal(false, config.zip_rotated_log_file)
|
76
|
+
assert_equal(false, config.debug)
|
77
|
+
assert_equal(nil, config.central_log_file)
|
78
|
+
assert_equal("jobmanager results for <%=job_name%> on <%=host_name%> : <%=result%>",
|
79
|
+
config.email_subject)
|
80
|
+
assert_equal(ENV['PATH'], config.command_path)
|
81
|
+
|
82
|
+
assert_equal(nil, config.timeout)
|
83
|
+
assert_equal(nil, config.email_to_address)
|
84
|
+
assert_equal(nil, config.email_from_address)
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_good_configs
|
88
|
+
confirm_all_values(load_good_config("all_values.yaml"))
|
89
|
+
|
90
|
+
confirm_minimum_required(load_good_config("minimum_required.yaml"))
|
91
|
+
|
92
|
+
# just make sure no errors are thrown
|
93
|
+
config = load_good_config("minimum_plus_email_settings.yaml")
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_bad_configs
|
97
|
+
msg = "error setting timeout with value 0: timeout must be a positive integer (seconds)."
|
98
|
+
confirm_bad_config_throws_error("bad_timeout_zero.yaml", msg)
|
99
|
+
|
100
|
+
msg = "error setting number_of_job_logs with value 0: value must be > 0."
|
101
|
+
confirm_bad_config_throws_error("bad_number_of_job_logs.yaml", msg)
|
102
|
+
|
103
|
+
msg = "error setting email_condition with value invalid: "
|
104
|
+
msg << "email_condition must be one of the following values: always, never, on_failure."
|
105
|
+
confirm_bad_config_throws_error("bad_email_condition.yaml", msg)
|
106
|
+
|
107
|
+
msg = "JobManager::ApplicationConfig#validate_all_values error: "
|
108
|
+
msg << "If central_log_mode is set to file, central_log_file must be specified."
|
109
|
+
confirm_bad_config_throws_error("bad_no_central_log_file.yaml",
|
110
|
+
msg)
|
111
|
+
end
|
112
|
+
|
113
|
+
def confirm_bad_config_throws_error(config_file, msg)
|
114
|
+
config_file = File.join(CONFIG_DIR, config_file)
|
115
|
+
|
116
|
+
assert_raise_message(msg, ConfigToolkit::Error) do
|
117
|
+
config = JobManager::ApplicationConfig.load(ConfigToolkit::YAMLReader.new(config_file))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_good_option_parser()
|
122
|
+
program_name = "jobmanager"
|
123
|
+
|
124
|
+
# make sure that the job_name is calculated if it isn't provided
|
125
|
+
args = [ "--job_logs_directory", "temp/logs",
|
126
|
+
"/bin/echo hello" ]
|
127
|
+
|
128
|
+
hash = JobManager::ApplicationOptionParser.parse(program_name, args)
|
129
|
+
config = JobManager::ApplicationConfig.load(ConfigToolkit::HashReader.new(hash))
|
130
|
+
assert_equal("echo", config.job_name)
|
131
|
+
|
132
|
+
# match the config specified in minimum_required.yaml
|
133
|
+
args = [ "--job_name", "ze_echoer",
|
134
|
+
"--job_logs_directory", "temp/logs",
|
135
|
+
"echo hello" ]
|
136
|
+
|
137
|
+
hash = JobManager::ApplicationOptionParser.parse(program_name, args)
|
138
|
+
config = JobManager::ApplicationConfig.load(ConfigToolkit::HashReader.new(hash))
|
139
|
+
confirm_minimum_required(config)
|
140
|
+
|
141
|
+
# match the config specified in all_values.yaml
|
142
|
+
email_subject = "jobmanager results for <%=job_name%> on <%=host_name%> : <%=result%>"
|
143
|
+
args = [ "--job_name", "ze_echoer",
|
144
|
+
"--job_logs_directory", "temp/logs",
|
145
|
+
"--email_condition", "always",
|
146
|
+
"--central_log_mode", "file",
|
147
|
+
"--central_log_file", "temp/logs/jobmanager.log",
|
148
|
+
"--number_of_job_logs", "4",
|
149
|
+
"--date_time_extension",
|
150
|
+
"--date_time_extension_format", "%F",
|
151
|
+
"--zip_rotated_log_file",
|
152
|
+
"--timeout", "1800",
|
153
|
+
"--email_settings_file", "email_settings.rb",
|
154
|
+
"--email_from_address", "sender_address@gmail.com",
|
155
|
+
"--email_to_address", "receiver_address@gmail.com",
|
156
|
+
"--command_path", "/usr/designingpatterns/bin",
|
157
|
+
"--email_subject", email_subject,
|
158
|
+
"--debug",
|
159
|
+
"echo hello"
|
160
|
+
]
|
161
|
+
|
162
|
+
hash = JobManager::ApplicationOptionParser.parse(program_name, args)
|
163
|
+
config = JobManager::ApplicationConfig.load(ConfigToolkit::HashReader.new(hash))
|
164
|
+
confirm_all_values(config)
|
165
|
+
|
166
|
+
# confirm the no- boolean configuration parameters work
|
167
|
+
args = [ "--job_logs_directory", "temp/logs",
|
168
|
+
"--no-date_time_extension",
|
169
|
+
"--no-zip_rotated_log_file",
|
170
|
+
"--no-debug",
|
171
|
+
"echo hello"
|
172
|
+
]
|
173
|
+
hash = JobManager::ApplicationOptionParser.parse(program_name, args)
|
174
|
+
config = JobManager::ApplicationConfig.load(ConfigToolkit::HashReader.new(hash))
|
175
|
+
assert_equal(false, config.date_time_extension())
|
176
|
+
assert_equal(false, config.zip_rotated_log_file())
|
177
|
+
assert_equal(false, config.debug())
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_yaml_and_option_parser()
|
182
|
+
program_name = "jobmanager"
|
183
|
+
|
184
|
+
# test the optionparser and the yaml reader together - both configs are incomplete on their own.
|
185
|
+
config_file = File.join(CONFIG_DIR, "incomplete_1.yaml")
|
186
|
+
yaml_reader = ConfigToolkit::YAMLReader.new(config_file)
|
187
|
+
|
188
|
+
args = [ "--job_name", "ze_echoer",
|
189
|
+
"--email_to_address", "receiver_address@gmail.com",
|
190
|
+
"--central_log_file", "temp/logs/jobmanager.log",
|
191
|
+
"--email_settings_file", "email_settings.rb",
|
192
|
+
"--timeout", "1800",
|
193
|
+
"--zip_rotated_log_file",
|
194
|
+
"--debug",
|
195
|
+
"echo hello"
|
196
|
+
]
|
197
|
+
|
198
|
+
hash = JobManager::ApplicationOptionParser.parse(program_name, args)
|
199
|
+
hash_reader = ConfigToolkit::HashReader.new(hash)
|
200
|
+
|
201
|
+
override_reader = ConfigToolkit::OverrideReader.new(yaml_reader, hash_reader)
|
202
|
+
confirm_all_values(JobManager::ApplicationConfig.load(override_reader))
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
|