jobmanager 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|