jobmanager 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/FAQ.txt +151 -0
  2. data/History.txt +2 -0
  3. data/LICENSE +20 -0
  4. data/Manifest.txt +42 -0
  5. data/README.txt +395 -0
  6. data/Rakefile +17 -0
  7. data/bin/jobmanager +20 -0
  8. data/examples/email_settings.rb +14 -0
  9. data/examples/example.rb +23 -0
  10. data/examples/jobmanager.yaml +66 -0
  11. data/examples/mysql_backup.rb +18 -0
  12. data/lib/jobmanager.rb +1 -0
  13. data/lib/jobmanager/application.rb +455 -0
  14. data/lib/jobmanager/applicationconfig.rb +89 -0
  15. data/lib/jobmanager/applicationlogger.rb +306 -0
  16. data/lib/jobmanager/applicationoptionparser.rb +163 -0
  17. data/lib/jobmanager/system.rb +240 -0
  18. data/lib/jobmanager/teestream.rb +78 -0
  19. data/lib/jobmanager/util.rb +25 -0
  20. data/test/configs/all_values.yaml +33 -0
  21. data/test/configs/bad_email_condition.yaml +7 -0
  22. data/test/configs/bad_no_central_log_file.yaml +7 -0
  23. data/test/configs/bad_number_of_job_logs.yaml +7 -0
  24. data/test/configs/bad_timeout_zero.yaml +7 -0
  25. data/test/configs/email_settings.rb +16 -0
  26. data/test/configs/incomplete_1.yaml +23 -0
  27. data/test/configs/jobmanager_1.yaml +9 -0
  28. data/test/configs/jobmanager_2.yaml +11 -0
  29. data/test/configs/jobmanager_3.yaml +13 -0
  30. data/test/configs/jobmanager_4_bad_central_log_file.yaml +9 -0
  31. data/test/configs/jobmanager_4_bad_email_settings_file.yaml +9 -0
  32. data/test/configs/jobmanager_4_bad_job_logs_directory_1.yaml +9 -0
  33. data/test/configs/jobmanager_4_bad_job_logs_directory_2.yaml +9 -0
  34. data/test/configs/minimum_plus_email_settings.yaml +9 -0
  35. data/test/configs/minimum_required.yaml +5 -0
  36. data/test/helpers.rb +7 -0
  37. data/test/mock_syslog.rb +68 -0
  38. data/test/test_applicationlogger.rb +145 -0
  39. data/test/test_config.rb +208 -0
  40. data/test/test_jobmanager.rb +443 -0
  41. data/test/test_system.rb +206 -0
  42. data/test/test_teestream.rb +55 -0
  43. metadata +172 -0
@@ -0,0 +1,9 @@
1
+ job_logs_directory: temp/logs/<%=user_name%>
2
+
3
+ central_log_mode: file
4
+
5
+ central_log_file: temp/logs/<%=user_name%>/jobmanager.log
6
+
7
+ number_of_job_logs: 5
8
+
9
+ date_time_extension: false
@@ -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
@@ -0,0 +1,13 @@
1
+ email_condition: on_failure
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_name%> : <%=result%>"
10
+
11
+ debug: true
12
+
13
+
@@ -0,0 +1,9 @@
1
+ job_logs_directory: temp/logs/<%=user_name%>
2
+
3
+ central_log_mode: file
4
+
5
+ central_log_file: temp/permissioned_directory/jobmanager.log
6
+
7
+ email_condition: on_failure
8
+
9
+ email_settings_file: email_settings.rb
@@ -0,0 +1,9 @@
1
+ job_logs_directory: temp/permissioned_directory/job_logs_directory
2
+
3
+ central_log_mode: file
4
+
5
+ central_log_file: temp/logs/jobmanager.log
6
+
7
+ email_condition: always
8
+
9
+ email_settings_file: /no_directory/no_file.rb
@@ -0,0 +1,9 @@
1
+ job_logs_directory: temp/permissioned_directory/job_logs_directory
2
+
3
+ central_log_mode: file
4
+
5
+ central_log_file: temp/logs/jobmanager.log
6
+
7
+ email_condition: on_failure
8
+
9
+ email_settings_file: email_settings.rb
@@ -0,0 +1,9 @@
1
+ job_logs_directory: temp/permissioned_directory
2
+
3
+ central_log_mode: file
4
+
5
+ central_log_file: temp/logs/jobmanager.log
6
+
7
+ email_condition: on_failure
8
+
9
+ email_settings_file: email_settings.rb
@@ -0,0 +1,9 @@
1
+ job_logs_directory: temp/logs
2
+
3
+ job_name: ze_echoer
4
+
5
+ command: "echo hello"
6
+
7
+ email_condition: always
8
+
9
+ email_settings_file: email_settings.rb
@@ -0,0 +1,5 @@
1
+ job_logs_directory: temp/logs
2
+
3
+ job_name: ze_echoer
4
+
5
+ command: "echo hello"
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module TestHelpers
4
+
5
+ TEMP_DIR = "temp"
6
+
7
+ end
@@ -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
+
@@ -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
+