iyyov 1.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
data/lib/iyyov/task.rb ADDED
@@ -0,0 +1,179 @@
1
+ #--
2
+ # Copyright (C) 2010 David Kellum
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
5
+ # may not use this file except in compliance with the License. You
6
+ # may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
+ # implied. See the License for the specific language governing
14
+ # permissions and limitations under the License.
15
+ #++
16
+
17
+ require 'date'
18
+ require 'time'
19
+ require 'thread'
20
+
21
+ module Iyyov
22
+
23
+ # A task to be scheduled and run.
24
+ class Task
25
+
26
+ # Regular interval between subsequent executions in seconds.
27
+ #
28
+ # Numeric (default: nil, use fixed_times)
29
+ attr_accessor :period
30
+
31
+ # One or more fixed time values in 24hour format, local
32
+ # timezone, i.e: [ "11:30", "23:30" ]
33
+ #
34
+ # ~to_a[String] (default: nil, use period)
35
+ attr_accessor :fixed_times
36
+
37
+ # Array or range for days of week in which fixed_times apply. Days
38
+ # are 0 (Sunday) .. 6 (Saturday). Example: M-F == (1..5)
39
+ #
40
+ # ~include?( day_of_week ) (default: (0..6))
41
+ attr_accessor :fixed_days
42
+
43
+ # Name the task for log reporting.
44
+ #
45
+ # String (default: nil)
46
+ attr_accessor :name
47
+
48
+ # Execution mode. If :async, run in separate thread, but only
49
+ # allow one thread for this task to run at any time.
50
+ #
51
+ # Symbol :sync|:async (default: :sync)
52
+ attr_accessor :mode
53
+
54
+ # Once schedule succeeds, the absolute next time to execute.
55
+ attr_reader :next_time
56
+
57
+ # New task given options matching accessors and block containing
58
+ # work.
59
+ def initialize( opts = {}, &block )
60
+ @name = nil
61
+ @next_time = nil
62
+ @period = nil
63
+ @fixed_times = nil
64
+ @fixed_days = (0..6) #all
65
+
66
+ opts.each { |k,v| send( k.to_s + '=', v ) }
67
+
68
+ @block = block
69
+
70
+ #FIXME: Validation?
71
+
72
+ @log = SLF4J[ [ SLF4J[ self.class ].name, name ].compact.join( '.' ) ]
73
+
74
+ @lock = ( Mutex.new if mode == :async )
75
+ @async_rc = nil
76
+
77
+ @log.info { "Task created : #{ opts.inspect }" }
78
+ end
79
+
80
+ # Execute the task, after which the task will be scheduled again
81
+ # in period time or for the next of fixed_times, unless :stop is
82
+ # returned.
83
+ def run
84
+ rc = :continue
85
+ if mode == :async
86
+ rc = test_async_return_code
87
+ run_thread if rc == :continue
88
+ else
89
+ rc = run_direct
90
+ end
91
+ rc
92
+ end
93
+
94
+ def test_async_return_code
95
+ rc = :continue
96
+ # Note: Currently only the main event loop thread goes here and
97
+ # so the only case for contention is this task already running
98
+ # in run_thread. In this case we can warn + :skip early.
99
+ if @lock.try_lock
100
+ begin
101
+ rc = @async_rc if ( @async_rc == :stop ) || ( @async_rc == :shutdown )
102
+ ensure
103
+ @lock.unlock
104
+ end
105
+ else
106
+ @log.warn "Already running, (pre) skipping this run."
107
+ rc = :skip
108
+ end
109
+ rc
110
+ end
111
+
112
+ def run_thread
113
+ Thread.new do
114
+ if @lock.try_lock
115
+ begin
116
+ @async_rc = run_direct
117
+ ensure
118
+ @lock.unlock
119
+ end
120
+ else
121
+ @log.warn "Already running, skipping this run."
122
+ end
123
+ end
124
+ end
125
+
126
+ def run_direct
127
+ @log.debug "Running."
128
+ rc = ( @block.call if @block )
129
+ #FIXME: Errors to handle?
130
+ filter( rc )
131
+ end
132
+
133
+ def filter( rc )
134
+ rc.is_a?( Symbol ) ? rc : :continue
135
+ end
136
+
137
+ # Determine next_time from now based on period or fixed_times
138
+ def schedule( now )
139
+ @next_time = nil
140
+
141
+ if fixed_times
142
+ @next_time = next_fixed_time( now )
143
+ elsif period
144
+ @next_time = ( now + period )
145
+ end
146
+
147
+ if @next_time && ( @next_time - now ) > 60.0
148
+ @log.debug { "Next run scheduled @ #{ next_time_to_s }" }
149
+ end
150
+
151
+ @next_time
152
+ end
153
+
154
+ def next_time_to_s
155
+ @next_time.strftime( '%Y-%m-%dT%H:%M:%S' ) if @next_time
156
+ end
157
+
158
+ def next_fixed_time( now )
159
+ day = Date.civil( now.year, now.month, now.day )
160
+ last = day + 7
161
+ ntime = nil
162
+ while ntime.nil? && day <= last
163
+ if fixed_days.include?( day.wday )
164
+ fixed_times.to_a.each do |ft|
165
+ ft = time_on_date( day, Time.parse( ft, now ) )
166
+ ntime = ft if ( ( ft > now ) && ( ntime.nil? || ft < ntime ) )
167
+ end
168
+ end
169
+ day += 1
170
+ end
171
+ ntime
172
+ end
173
+
174
+ def time_on_date( d, t )
175
+ Time.local( d.year, d.month, d.day, t.hour, t.min, t.sec, t.usec )
176
+ end
177
+
178
+ end
179
+ end
data/lib/iyyov.rb ADDED
@@ -0,0 +1,78 @@
1
+ #--
2
+ # Copyright (C) 2010 David Kellum
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
5
+ # may not use this file except in compliance with the License. You
6
+ # may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
+ # implied. See the License for the specific language governing
14
+ # permissions and limitations under the License.
15
+ #++
16
+
17
+ require 'iyyov/base'
18
+ require 'iyyov/context'
19
+
20
+ module Iyyov
21
+
22
+ # Load configuration root_files and run the event loop, not
23
+ # returning. Yields new Context to optional block for extra
24
+ # configuration to be applied before any root_files are loaded.
25
+ def self.run( root_files, &block )
26
+ @extra_config_block = block
27
+ load_root_files( root_files )
28
+
29
+ continue = true
30
+ while( continue && @context )
31
+ rc = @context.event_loop
32
+ continue = ( rc == :shutdown )
33
+ end
34
+ end
35
+
36
+ # Yields current context to block. Called from configuration
37
+ # scripts.
38
+ def self.context
39
+ raise "Iyyov.context called before run" unless @context
40
+ yield @context if block_given?
41
+ @context
42
+ end
43
+
44
+ # Load root configuration files.
45
+ def self.load_root_files( files )
46
+ old_context = @context
47
+ @context = Context.new
48
+
49
+ @extra_config_block.call( @context ) if @extra_config_block
50
+
51
+ all_success = true
52
+ files.each { |cfile| all_success &&= @context.load_file( cfile, true ) }
53
+
54
+ if old_context
55
+ if all_success
56
+ # Stop old daemons that are no longer in the newly configured
57
+ # context, or who's exec_key has changed
58
+ old_context.daemons.each do |name,odaemon|
59
+ ndaemon = @context.daemons[name]
60
+ odaemon.stop unless ndaemon && ndaemon.exec_key == odaemon.exec_key
61
+ end
62
+ else
63
+ @context = old_context
64
+ end
65
+ end
66
+
67
+ all_success
68
+ end
69
+
70
+ #Class Instance Variables
71
+
72
+ #Presently active context
73
+ @context = nil
74
+
75
+ #Optional extra configuration block
76
+ @extra_config_block = nil
77
+
78
+ end
data/test/setup.rb ADDED
@@ -0,0 +1,50 @@
1
+ #--
2
+ # Copyright (C) 2010 David Kellum
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
5
+ # may not use this file except in compliance with the License. You
6
+ # may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
+ # implied. See the License for the specific language governing
14
+ # permissions and limitations under the License.
15
+ #++
16
+
17
+ #### General test setup: LOAD_PATH, logging, console output ####
18
+
19
+ ldir = File.join( File.dirname( __FILE__ ), "..", "lib" )
20
+ $LOAD_PATH.unshift( ldir ) unless $LOAD_PATH.include?( ldir )
21
+
22
+ def time_it( name )
23
+ start = Time.now
24
+ output = ARGV.include?( '-v' )
25
+ $stdout.write( "Loading %10s..." % name ) if output
26
+ yield
27
+ puts "%6.3fs" % (Time.now - start) if output
28
+ end
29
+
30
+ time_it( "rubygems" ) do
31
+ require 'rubygems'
32
+ end
33
+
34
+ time_it( "logback" ) do
35
+ require 'rjack-logback'
36
+ RJack::Logback.config_console( :level => Logback::INFO, :stderr => true )
37
+ end
38
+
39
+ time_it( "test/unit" ) do
40
+ require 'minitest/unit'
41
+ require 'minitest/autorun'
42
+
43
+ # Make test output logging compatible: no partial lines.
44
+ class TestOut
45
+ def print( *a ); $stdout.puts( *a ); end
46
+ def puts( *a ); $stdout.puts( *a ); end
47
+ end
48
+ MiniTest::Unit.output = TestOut.new
49
+
50
+ end
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env jruby
2
+ #.hashdot.profile += jruby-shortlived
3
+ #--
4
+ # Copyright (C) 2010 David Kellum
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
7
+ # may not use this file except in compliance with the License. You
8
+ # may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15
+ # implied. See the License for the specific language governing
16
+ # permissions and limitations under the License.
17
+ #++
18
+
19
+ require File.join( File.dirname( __FILE__ ), "setup" )
20
+
21
+ require 'iyyov'
22
+ require 'fileutils'
23
+
24
+ class TestDaemon < MiniTest::Unit::TestCase
25
+ include Iyyov
26
+
27
+ def setup
28
+ @log = RJack::SLF4J[ self.class ]
29
+ @tdir = File.dirname( __FILE__ )
30
+ @context = Context.new do |c|
31
+ c.base_dir = @tdir
32
+ end
33
+ end
34
+
35
+ def teardown
36
+ @context.event_loop #Confirm return
37
+ @context.shutdown
38
+ %w[ hashdot-test-daemon hashdot-noexist ].each do |rdir|
39
+ FileUtils.rm_rf( File.join( @tdir, rdir ) )
40
+ end
41
+ end
42
+
43
+ def test_init
44
+ d = Daemon.new( @context ) do |h|
45
+ h.name = "myname"
46
+ h.instance = '33'
47
+ end
48
+
49
+ assert_equal( "myname", d.gem_name )
50
+ assert_equal( "#{@tdir}/myname-33", d.run_dir )
51
+ assert_equal( "#{@tdir}/myname-33/myname.pid", d.pid_file )
52
+ end
53
+
54
+ def test_exe_path
55
+ d = Daemon.new( @context ) { |h| h.name = "hashdot-test-daemon" }
56
+ assert File.executable?( d.exe_path )
57
+ @log.info d.exe_path
58
+ end
59
+
60
+ def test_invalid_init_name
61
+ d = Daemon.new( @context ) do |h|
62
+ h.name = "hashdot-test-daemon"
63
+ h.init_name = "no-exist"
64
+ end
65
+ assert_equal( :stop, d.do_first( nil ) )
66
+ end
67
+
68
+ def test_invalid_run_dir
69
+ d = Daemon.new( @context ) do |h|
70
+ h.name = "hashdot-test-daemon"
71
+ h.run_dir = "/no-permission"
72
+ end
73
+ assert_equal( :stop, d.do_first( nil ) )
74
+ end
75
+
76
+ def test_invalid_gem_name
77
+ d = Daemon.new( @context ) { |h| h.name = "hashdot-noexist" }
78
+ assert_equal( :stop, d.do_first( nil ) )
79
+ end
80
+
81
+ def test_invalid_gem_version
82
+ d = Daemon.new( @context ) do |h|
83
+ h.name = "hashdot-test-daemon"
84
+ h.version = "= 6.6.6"
85
+ end
86
+ assert_equal( :stop, d.do_first( nil ) )
87
+ end
88
+
89
+ end
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env jruby
2
+ #.hashdot.profile += jruby-shortlived
3
+ #--
4
+ # Copyright (C) 2010 David Kellum
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
7
+ # may not use this file except in compliance with the License. You
8
+ # may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15
+ # implied. See the License for the specific language governing
16
+ # permissions and limitations under the License.
17
+ #++
18
+
19
+ require File.join( File.dirname( __FILE__ ), "setup" )
20
+
21
+ require 'iyyov/scheduler'
22
+ require 'iyyov/task'
23
+
24
+ class TestScheduler < MiniTest::Unit::TestCase
25
+ include Iyyov
26
+
27
+ def test_run
28
+ counter = 0
29
+ s = Scheduler.new
30
+ tk = Task.new( :name => "test_run", :period => 0.001 ) do
31
+ counter += 1
32
+ assert( counter <= 2 )
33
+ :stop unless counter < 2
34
+ end
35
+ s.add( tk )
36
+ s.event_loop
37
+ end
38
+
39
+ def test_fixed_times
40
+ tk = Task.new( :fixed_times => %w[ 6:00 8:00 10:00 12:00 ] )
41
+
42
+ assert_next_time_from( tk, '2010-02-08T08:00', '2010-02-08T10:00' )
43
+
44
+ assert_next_time_from( tk, '2010-02-08T12:00', '2010-02-09T06:00' )
45
+
46
+ tk.fixed_days = 3..6
47
+ assert_next_time_from( tk, '2010-02-08T08:00', '2010-02-10T06:00' )
48
+ end
49
+
50
+ def assert_next_time_from( tk, now, expected )
51
+ 2.times do
52
+ assert_equal( tp( expected ), tk.next_fixed_time( tp( now ) ) )
53
+ tk.fixed_times = tk.fixed_times.reverse
54
+ end
55
+ end
56
+
57
+ def test_shutdown
58
+ s = Scheduler.new
59
+ s.on_exit { flunk "Shouldn't make it here" }
60
+ counter = 0
61
+ tk = Task.new( :name => "test_shutdown", :period => 0.001 ) do
62
+ counter += 1
63
+ assert( counter <= 2 )
64
+ :shutdown unless counter < 2
65
+ end
66
+ s.add( tk )
67
+ s.add( Task.new( :period => 5.0 ) { flunk "nor here" } )
68
+ assert_equal( :shutdown, s.event_loop )
69
+ end
70
+
71
+ def tp( t )
72
+ Time.parse( t )
73
+ end
74
+
75
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iyyov
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: java
6
+ authors:
7
+ - David Kellum
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-03-07 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rjack-sfl4j
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 1.5.11
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rjack-logback
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.18
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: logrotate
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.2.1
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: minitest
47
+ type: :development
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 1.5.0
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: hashdot-test-daemon
57
+ type: :development
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 1.0.0
64
+ version:
65
+ - !ruby/object:Gem::Dependency
66
+ name: rjack-tarpit
67
+ type: :development
68
+ version_requirement:
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ version: 1.2.0
74
+ version:
75
+ description: |-
76
+ Down-to-earth job control and monitoring. Features include:
77
+
78
+ * Runs on JRuby (no fork)
79
+ * Ruby-based configuration
80
+ * Gem packaged/Hashdot daemon based launching
81
+ * Log rotation with daemon SIGHUP support
82
+ * Fixed and periodic time job scheduling (basic cron replacement).
83
+ email:
84
+ - dek-oss@gravitext.com
85
+ executables:
86
+ - iyyov-fg
87
+ extensions: []
88
+
89
+ extra_rdoc_files:
90
+ - Manifest.txt
91
+ - README.rdoc
92
+ - History.rdoc
93
+ files:
94
+ - Manifest.txt
95
+ - History.rdoc
96
+ - README.rdoc
97
+ - Rakefile
98
+ - bin/iyyov-fg
99
+ - init/iyyov-daemon
100
+ - lib/iyyov/base.rb
101
+ - lib/iyyov.rb
102
+ - lib/iyyov/context.rb
103
+ - lib/iyyov/daemon.rb
104
+ - lib/iyyov/errors.rb
105
+ - lib/iyyov/log_rotator.rb
106
+ - lib/iyyov/scheduler.rb
107
+ - lib/iyyov/shutdown_handler.rb
108
+ - lib/iyyov/task.rb
109
+ - test/setup.rb
110
+ - test/test_daemon.rb
111
+ - test/test_scheduler.rb
112
+ has_rdoc: true
113
+ homepage: http://github.com/dekellum/iyyov
114
+ licenses: []
115
+
116
+ post_install_message:
117
+ rdoc_options:
118
+ - --main
119
+ - README.rdoc
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: "0"
127
+ version:
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: "0"
133
+ version:
134
+ requirements: []
135
+
136
+ rubyforge_project: iyyov
137
+ rubygems_version: 1.3.5
138
+ signing_key:
139
+ specification_version: 3
140
+ summary: Down-to-earth job control and monitoring
141
+ test_files:
142
+ - test/test_scheduler.rb
143
+ - test/test_daemon.rb