webapp_worker 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.markdown +37 -0
- data/Rakefile +1 -0
- data/bin/waw +58 -0
- data/lib/webapp_worker/application.rb +88 -0
- data/lib/webapp_worker/job.rb +204 -0
- data/lib/webapp_worker/version.rb +3 -0
- data/lib/webapp_worker.rb +7 -0
- data/webapp_worker.gemspec +20 -0
- metadata +67 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
## Information
|
2
|
+
|
3
|
+
Provides a way to have workers on your webapp servers, espeically useful for webapps that tend to scale up and down with X amount of servers in the load balancer. Also good way to not use another dependent resource like a job scheduler/queue. Keeps your application all packaged up nicely, no cron jobs to set, nothing else to think about setting up and nothing else to maintain.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
`gem install webapp_worker`
|
8
|
+
|
9
|
+
or use it in your Gemfile
|
10
|
+
|
11
|
+
`gem 'webapp_worker'`
|
12
|
+
|
13
|
+
## Quick Test
|
14
|
+
|
15
|
+
``
|
16
|
+
|
17
|
+
## Using in your webapp
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
## Contributing
|
22
|
+
|
23
|
+
- Fork the project and do your work in a topic branch.
|
24
|
+
- Rebase your branch to make sure everything is up to date.
|
25
|
+
- Commit your changes and send a pull request.
|
26
|
+
|
27
|
+
## Copyright
|
28
|
+
|
29
|
+
(The MIT License)
|
30
|
+
|
31
|
+
Copyright © 2011 nictrix (Nick Willever)
|
32
|
+
|
33
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
34
|
+
|
35
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
36
|
+
|
37
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/waw
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'webapp_worker'
|
4
|
+
require 'trollop'
|
5
|
+
|
6
|
+
opts = Trollop::options do
|
7
|
+
banner <<-EOS
|
8
|
+
Usage:
|
9
|
+
#{File.basename($0)} [options]
|
10
|
+
|
11
|
+
where [options] are:
|
12
|
+
EOS
|
13
|
+
|
14
|
+
opt :environment, "Enviroment (i.e. local, development..)", :type => String, :short => "-e"
|
15
|
+
opt :jobfile, "A YAML config file", :type => String, :short => "-f"
|
16
|
+
opt :run, "Run the jobs", :default => false, :short => "-r"
|
17
|
+
opt :jobs, "Show the jobs", :default => false, :short => "-j"
|
18
|
+
opt :nextrun, "Find the next possible command(s) to run (i.e. 1,2...)", :type => Integer, :short => "-n"
|
19
|
+
opt :debug, "Local Debug", :short => "-d"
|
20
|
+
opt :verbose, "Verbose Output", :short => "-v"
|
21
|
+
end
|
22
|
+
|
23
|
+
Trollop::die :jobfile, "must specify jobfile" unless File.exist?(opts[:jobfile]) if opts[:jobfile]
|
24
|
+
Trollop::die :environment, "must specify environment" unless opts[:environment]
|
25
|
+
|
26
|
+
job_file = File.absolute_path(opts[:jobfile])
|
27
|
+
|
28
|
+
a = WebappWorker::Application.new(environment:opts[:environment])
|
29
|
+
a.parse_yaml(job_file)
|
30
|
+
|
31
|
+
if opts[:nextrun] != nil
|
32
|
+
a.next_command_run?(opts[:nextrun]).each do |command,time|
|
33
|
+
puts "Next Command Run: #{command}"
|
34
|
+
puts " Next Run: #{time}"
|
35
|
+
end
|
36
|
+
elsif opts[:run] == false && opts[:jobs] == false
|
37
|
+
puts
|
38
|
+
puts "Host: #{a.hostname}"
|
39
|
+
puts "Mailto: #{a.mailto}"
|
40
|
+
puts "Environment: #{a.environment}"
|
41
|
+
puts "Amount of Jobs: #{a.jobs.length}"
|
42
|
+
elsif opts[:run] == false && opts[:jobs] == true
|
43
|
+
puts "Job File: #{job_file}"
|
44
|
+
puts
|
45
|
+
puts "Host: #{a.hostname}"
|
46
|
+
puts "Mailto: #{a.mailto}"
|
47
|
+
puts "Environment: #{a.environment}"
|
48
|
+
puts "Amount of Jobs: #{a.jobs.length}"
|
49
|
+
puts
|
50
|
+
a.jobs.each do |job|
|
51
|
+
j = WebappWorker::Job.new(job)
|
52
|
+
puts "Command to Run: #{j.command}"
|
53
|
+
puts " Next Run: #{j.next_run?}"
|
54
|
+
end
|
55
|
+
else
|
56
|
+
puts "Running Jobs"
|
57
|
+
a.run
|
58
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module WebappWorker
|
4
|
+
class Application
|
5
|
+
attr_accessor :hostname, :mailto, :environment, :jobs
|
6
|
+
|
7
|
+
def initialize(user_supplied_hash={})
|
8
|
+
standard_hash = { hostname:"#{self.hostname}", mailto:"", environment:"local", jobs:"" }
|
9
|
+
|
10
|
+
user_supplied_hash = {} unless user_supplied_hash
|
11
|
+
user_supplied_hash = standard_hash.merge(user_supplied_hash)
|
12
|
+
|
13
|
+
user_supplied_hash.each do |key,value|
|
14
|
+
self.instance_variable_set("@#{key}", value)
|
15
|
+
self.class.send(:define_method, key, proc{self.instance_variable_get("@#{key}")})
|
16
|
+
self.class.send(:define_method, "#{key}=", proc{|x| self.instance_variable_set("@#{key}", x)})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_yaml(yaml)
|
21
|
+
@mailto = (YAML.load_file(yaml))[@environment]["mailto"] unless @mailto
|
22
|
+
@jobs = (YAML.load_file(yaml))[@environment][@hostname] unless @hostname.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def hostname
|
26
|
+
return Socket.gethostname.downcase
|
27
|
+
end
|
28
|
+
|
29
|
+
def next_command_run?(til)
|
30
|
+
commands = {}
|
31
|
+
c = {}
|
32
|
+
next_commands = {}
|
33
|
+
|
34
|
+
@jobs.each do |job|
|
35
|
+
j = WebappWorker::Job.new(job)
|
36
|
+
commands.store(j.command,j.next_run?)
|
37
|
+
end
|
38
|
+
(commands.sort_by { |key,value| value }).collect { |key,value| c.store(key,value) }
|
39
|
+
|
40
|
+
counter = 0
|
41
|
+
c.each do |key,value|
|
42
|
+
next_commands.store(key,value)
|
43
|
+
counter = counter + 1
|
44
|
+
break if counter >= til
|
45
|
+
end
|
46
|
+
|
47
|
+
return next_commands
|
48
|
+
end
|
49
|
+
|
50
|
+
def run
|
51
|
+
#Going to need to do memory/process management, or fork processes not threads...
|
52
|
+
p = Process.fork do
|
53
|
+
Signal.trap('HUP', 'IGNORE')
|
54
|
+
|
55
|
+
@threads = {}
|
56
|
+
|
57
|
+
loop do
|
58
|
+
@threads.each do |thread,command|
|
59
|
+
if thread.status == false
|
60
|
+
@threads.delete(thread)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
data = self.next_command_run?(1)
|
65
|
+
|
66
|
+
data.each do |command,time|
|
67
|
+
time = time[0]
|
68
|
+
now = Time.now.utc
|
69
|
+
range = (time - now).to_i
|
70
|
+
|
71
|
+
if @threads.detect { |thr,com| com == command }
|
72
|
+
sleep(range) unless range <= 0
|
73
|
+
else
|
74
|
+
t = Thread.new do
|
75
|
+
sleep(range) unless range <= 0
|
76
|
+
`#{command}`
|
77
|
+
end
|
78
|
+
@threads.store(t,command)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
Process.detach(p)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module WebappWorker
|
5
|
+
class Job
|
6
|
+
attr_accessor :command, :minute, :hour, :day, :month, :weekday
|
7
|
+
|
8
|
+
DIVISION = /\d+-\d+[\/]\d+/
|
9
|
+
DIGIT = /^\d+$/
|
10
|
+
|
11
|
+
def initialize(user_supplied_hash={})
|
12
|
+
standard_hash = { command:"", minute:"*", hour:"*", day:"*", month:"*", weekday:"*" }
|
13
|
+
|
14
|
+
user_supplied_hash = {} unless user_supplied_hash
|
15
|
+
user_supplied_hash = standard_hash.merge(user_supplied_hash)
|
16
|
+
|
17
|
+
user_supplied_hash.each do |key,value|
|
18
|
+
self.instance_variable_set("@#{key}", value)
|
19
|
+
self.class.send(:define_method, key, proc{self.instance_variable_get("@#{key}")})
|
20
|
+
self.class.send(:define_method, "#{key}=", proc{|x| self.instance_variable_set("@#{key}", x)})
|
21
|
+
end
|
22
|
+
|
23
|
+
self.parse_datetime
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.new_from_yaml(yaml,environment)
|
27
|
+
job = self.new((YAML.load_file(yaml))[environment])
|
28
|
+
end
|
29
|
+
|
30
|
+
def current_time
|
31
|
+
return "#{Time.now.strftime("%w %m-%d %H:%M")}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def make_string(n)
|
35
|
+
test_n = n.to_s.length
|
36
|
+
|
37
|
+
if test_n < 2 && test_n > 0
|
38
|
+
return "0#{n}"
|
39
|
+
else
|
40
|
+
return n.to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def fix_every(value)
|
45
|
+
divider = /\d+$/
|
46
|
+
range = /^\d+-\d+/
|
47
|
+
first_range = /^\d+/
|
48
|
+
second_range = /\d+$/
|
49
|
+
|
50
|
+
every = value.match(divider).to_s.to_i
|
51
|
+
number_range = value.match(range).to_s
|
52
|
+
first = number_range.match(first_range).to_s.to_i
|
53
|
+
second = number_range.match(second_range).to_s.to_i
|
54
|
+
|
55
|
+
range = []
|
56
|
+
until first >= second do
|
57
|
+
first = first + every
|
58
|
+
break if first > second
|
59
|
+
range << self.make_string(first)
|
60
|
+
end
|
61
|
+
|
62
|
+
return range
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_datetime
|
66
|
+
self.fix_minute
|
67
|
+
self.fix_hour
|
68
|
+
self.fix_day
|
69
|
+
self.fix_month
|
70
|
+
self.fix_weekday
|
71
|
+
end
|
72
|
+
|
73
|
+
def fix_minute
|
74
|
+
case @minute.to_s
|
75
|
+
when "*", nil, ""
|
76
|
+
@minute = []
|
77
|
+
(0..59).each do |m|
|
78
|
+
@minute << self.make_string(m)
|
79
|
+
end
|
80
|
+
when DIVISION
|
81
|
+
@minute = self.fix_every(@minute)
|
82
|
+
when DIGIT
|
83
|
+
@minute = [self.make_string(@minute)]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def fix_hour
|
88
|
+
case @hour.to_s
|
89
|
+
when "*", nil, ""
|
90
|
+
@hour = []
|
91
|
+
(0..23).each do |h|
|
92
|
+
@hour << self.make_string(h)
|
93
|
+
end
|
94
|
+
when DIVISION
|
95
|
+
@hour = self.fix_every(@hour)
|
96
|
+
when DIGIT
|
97
|
+
@hour = [self.make_string(@hour)]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def fix_day
|
102
|
+
case @day.to_s
|
103
|
+
when "*", nil, ""
|
104
|
+
@day = []
|
105
|
+
(1..31).each do |d|
|
106
|
+
@day << self.make_string(d)
|
107
|
+
end
|
108
|
+
when DIVISION
|
109
|
+
@day = self.fix_every(@day)
|
110
|
+
when DIGIT
|
111
|
+
@day = [self.make_string(@day)]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def fix_month
|
116
|
+
case @month.to_s
|
117
|
+
when "*", nil, ""
|
118
|
+
@month = []
|
119
|
+
(1..12).each do |m|
|
120
|
+
@month << self.make_string(m)
|
121
|
+
end
|
122
|
+
when DIVISION
|
123
|
+
@month = self.fix_every(@month)
|
124
|
+
when DIGIT
|
125
|
+
@month = [self.make_string(@month)]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def fix_weekday
|
130
|
+
case @weekday.to_s
|
131
|
+
when "*", nil, ""
|
132
|
+
@weekday = []
|
133
|
+
(0..6).each do |w|
|
134
|
+
@weekday << w.to_s
|
135
|
+
end
|
136
|
+
when DIVISION
|
137
|
+
@weekday = self.fix_every(@weekday)
|
138
|
+
when DIGIT
|
139
|
+
@weekday = [@weekday.to_s]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def next_times(numbers,amount,time)
|
144
|
+
future = {}
|
145
|
+
|
146
|
+
numbers.each do |number|
|
147
|
+
calculated = number.to_i - time
|
148
|
+
calculated = (calculated + amount) unless calculated >= 0
|
149
|
+
future.store(number,calculated)
|
150
|
+
end
|
151
|
+
|
152
|
+
sub = {}
|
153
|
+
(future.sort_by { |key,value| value }).collect { |key,value| sub.store(key,value) }
|
154
|
+
fn = sub.collect { |key,value| key }
|
155
|
+
|
156
|
+
return fn
|
157
|
+
end
|
158
|
+
|
159
|
+
def next_run?
|
160
|
+
self.next_runs?(1)
|
161
|
+
end
|
162
|
+
|
163
|
+
def next_runs?(til)
|
164
|
+
self.parse_datetime
|
165
|
+
|
166
|
+
next_runs = []
|
167
|
+
|
168
|
+
now = Time.now
|
169
|
+
@weekday = self.next_times(@weekday,6,now.strftime("%w").to_i)
|
170
|
+
@year = now.strftime("%Y")
|
171
|
+
@month = self.next_times(@month,12,now.strftime("%m").to_i)
|
172
|
+
@day = self.next_times(@day,31,now.strftime("%d").to_i)
|
173
|
+
@hour = self.next_times(@hour,23,now.strftime("%H").to_i)
|
174
|
+
@minute = self.next_times(@minute,60,now.strftime("%M").to_i)
|
175
|
+
|
176
|
+
counter = 0
|
177
|
+
catch :done do
|
178
|
+
@month.each do |month|
|
179
|
+
@day.each do |day|
|
180
|
+
@weekday.each do |weekday|
|
181
|
+
@hour.each do |hour|
|
182
|
+
@minute.each do |minute|
|
183
|
+
begin
|
184
|
+
next_time = (DateTime.strptime("#{weekday} #{@year}-#{month}-#{day} #{hour}:#{minute}","%w %Y-%m-%d %H:%M")).to_time + 25200
|
185
|
+
|
186
|
+
next unless next_time >= now
|
187
|
+
next_runs << next_time
|
188
|
+
counter = counter + 1
|
189
|
+
rescue ArgumentError
|
190
|
+
next
|
191
|
+
end
|
192
|
+
|
193
|
+
throw :done, next_runs if counter >= til
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
return next_runs
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "webapp_worker/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "webapp_worker"
|
7
|
+
s.version = WebappWorker::VERSION
|
8
|
+
s.authors = ["Nick Willever"]
|
9
|
+
s.email = ["nickwillever@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Provides a worker for your webapp}
|
12
|
+
s.description = %q{Allow the webapp to handle your workers, no need to use a job scheduler}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_runtime_dependency 'trollop'
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: webapp_worker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nick Willever
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-15 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: trollop
|
16
|
+
requirement: &81315550 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *81315550
|
25
|
+
description: Allow the webapp to handle your workers, no need to use a job scheduler
|
26
|
+
email:
|
27
|
+
- nickwillever@gmail.com
|
28
|
+
executables:
|
29
|
+
- waw
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- .gitignore
|
34
|
+
- Gemfile
|
35
|
+
- README.markdown
|
36
|
+
- Rakefile
|
37
|
+
- bin/waw
|
38
|
+
- lib/webapp_worker.rb
|
39
|
+
- lib/webapp_worker/application.rb
|
40
|
+
- lib/webapp_worker/job.rb
|
41
|
+
- lib/webapp_worker/version.rb
|
42
|
+
- webapp_worker.gemspec
|
43
|
+
homepage: ''
|
44
|
+
licenses: []
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.8.11
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: Provides a worker for your webapp
|
67
|
+
test_files: []
|