iyyov 1.0.0-java

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/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