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.
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
+