resque-sliders 0.0.4

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 (34) hide show
  1. data/Gemfile +9 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +88 -0
  4. data/Rakefile +35 -0
  5. data/bin/kewatcher +68 -0
  6. data/config/config_example.yml +11 -0
  7. data/lib/resque-sliders/commander.rb +73 -0
  8. data/lib/resque-sliders/helpers.rb +72 -0
  9. data/lib/resque-sliders/kewatcher.rb +345 -0
  10. data/lib/resque-sliders/server/public/css/jqueryui-1.8.16/blitzer/jquery-ui.custom.css +568 -0
  11. data/lib/resque-sliders/server/public/css/sliders.css +30 -0
  12. data/lib/resque-sliders/server/public/images/ui-bg_diagonals-thick_75_f3d8d8_40x40.png +0 -0
  13. data/lib/resque-sliders/server/public/images/ui-bg_dots-small_65_a6a6a6_2x2.png +0 -0
  14. data/lib/resque-sliders/server/public/images/ui-bg_flat_0_333333_40x100.png +0 -0
  15. data/lib/resque-sliders/server/public/images/ui-bg_flat_65_ffffff_40x100.png +0 -0
  16. data/lib/resque-sliders/server/public/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  17. data/lib/resque-sliders/server/public/images/ui-bg_glass_55_fbf8ee_1x400.png +0 -0
  18. data/lib/resque-sliders/server/public/images/ui-bg_highlight-hard_100_eeeeee_1x100.png +0 -0
  19. data/lib/resque-sliders/server/public/images/ui-bg_highlight-hard_100_f6f6f6_1x100.png +0 -0
  20. data/lib/resque-sliders/server/public/images/ui-bg_highlight-soft_15_cc0000_1x100.png +0 -0
  21. data/lib/resque-sliders/server/public/images/ui-icons_004276_256x240.png +0 -0
  22. data/lib/resque-sliders/server/public/images/ui-icons_cc0000_256x240.png +0 -0
  23. data/lib/resque-sliders/server/public/images/ui-icons_ffffff_256x240.png +0 -0
  24. data/lib/resque-sliders/server/public/js/jquery-ui-1.8.16.custom.min.js +791 -0
  25. data/lib/resque-sliders/server/public/js/sliders.js +64 -0
  26. data/lib/resque-sliders/server/views/index.erb +59 -0
  27. data/lib/resque-sliders/server.rb +71 -0
  28. data/lib/resque-sliders/version.rb +7 -0
  29. data/lib/resque-sliders.rb +9 -0
  30. data/resque-sliders.gemspec +29 -0
  31. data/test/redis-test.conf +115 -0
  32. data/test/resque-sliders_test.rb +12 -0
  33. data/test/test_helper.rb +74 -0
  34. metadata +117 -0
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+ #source "http://gems.github.com"
3
+
4
+ gem 'resque', '1.15.0'
5
+ gem 'resque-sliders'
6
+
7
+ group :development do
8
+ gem "shotgun"
9
+ end
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) Kevin Mullin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ Resque Sliders
2
+ ==============
3
+
4
+ [github.com/kmullin/resque-sliders](https://github.com/kmullin/resque-sliders)
5
+
6
+ Description
7
+ -----------
8
+
9
+ ResqueSliders is a [Resque](https://github.com/defunkt/resque) plugin which allows you
10
+ to control Resque workers from the Web-UI.
11
+
12
+ From the Resque-Web UI, you can:
13
+
14
+ * Start workers with any queue, or combination of queues on any host, and specify how many of each should be running
15
+ * Pause / Stop / Restart ALL running workers
16
+
17
+
18
+ ResqueSliders comes with two parts:
19
+
20
+ * `KEWatcher`: A daemon that runs on any machine that needs to run Resque workers, watches over the workers and controls which ones are running
21
+ * `Resque-Web Plugin`: A bunch of slider bars, with text-input box to specify what queues to run on the workers
22
+
23
+ Installation
24
+ ------------
25
+
26
+ Install as a gem:
27
+
28
+ ```
29
+ $ gem install resque-sliders
30
+ ```
31
+
32
+ KEWatcher
33
+ ---------
34
+ This is the daemon component that runs on any host that you want to run Resque workers on. The daemon's job is to manage how many Resque workers should be running, and what they should be running. It also provides an easy way to stop all workers during maintenance or deploys.
35
+
36
+ When the daemon first runs, it will register itself, by hostname with Redis:
37
+
38
+ * Adds a few persistent settings to the hash key `resque:plugins:resque-sliders:host_configs` (max_children, current_children)
39
+ * Gets any queues that need to be running on the host by looking at `resque:plugins:resque-sliders:` + `hostname`
40
+
41
+ ```
42
+ Usage: kewatcher [options]
43
+
44
+ Options:
45
+ -c, --config CONFIG Resque Config (Yaml)
46
+ -r, --rakefile RAKEFILE Rakefile location
47
+ -p, --pidfile PIDFILE PID File location
48
+ -f, --force FORCE KILL ANY OTHER RUNNING KEWATCHERS
49
+ -v, --verbose Verbosity (Can be specified more than once, -vv)
50
+ -m, --max MAX Max Children
51
+ -h, --help This help
52
+ ```
53
+
54
+ **Important Options**
55
+
56
+ * `Max Children (-m|--max MAX)`: Maximum number of workers to run on host (default: 10)
57
+ * `Rakefile (-r|--rakefile RAKEFILE)`: Pass along a rakefile to use when calling `rake ... resque:work` - shouldn't be needed if run from project directory
58
+ * `Force (-f|--force)`: Force any currently running KEWatcher processes to QUIT, waiting for it to do so, and starting in its place
59
+
60
+ **Controlling the Daemon**
61
+
62
+ `KEWatcher` supports all the same signals as `Resque`:
63
+
64
+ * `TERM`, `INT`, `QUIT`: Shutdown. Gracefully kill all child Resque workers, and wait for them to finish before exiting
65
+ * `HUP`: Restart all Resque workers by gracefully killing them, and starting new ones in their place
66
+ * `USR1`: Stop all Resque workers, and don't start any more
67
+ * `USR2`: Pause spawning of new queues, but leave current ones running
68
+ * `CONT`: Unpause. Continue spawning/managing child Resque workers
69
+
70
+
71
+ Resque-Web Integration
72
+ ----------------------
73
+ **Main Screen:** showing 3 hosts (node01-03), and showing that nodes 1 and 3 aren't running their KEWatchers
74
+ ![Screen 1](https://github.com/kmullin/resque-sliders/raw/master/misc/resque-sliders_main-view.png)
75
+
76
+ **Host Screen:** showing 3 different `QUEUE` combinations (comma separated) and slider bars indicating how many of each of them should run on node02
77
+ ![Screen 2](https://github.com/kmullin/resque-sliders/raw/master/misc/resque-sliders_host-view.png)
78
+
79
+ To enable the Resque-Web Integration you'll need to load ResqueSliders to enable the Sliders tab. Just add:
80
+
81
+ ```ruby
82
+ require 'resque-sliders'
83
+ ```
84
+ to a file, like resque-web_init.rb, and run resque-web:
85
+
86
+ ```
87
+ resque-web resque-web_init.rb
88
+ ```
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'resque/tasks'
3
+ #
4
+ # Setup
5
+ #
6
+
7
+ $LOAD_PATH.unshift 'lib'
8
+
9
+ def command?(command)
10
+ system("type #{command} > /dev/null 2>&1")
11
+ end
12
+
13
+
14
+ #
15
+ # Tests
16
+ #
17
+
18
+ task :default => :test
19
+
20
+ desc "Run the test suite"
21
+ task :test do
22
+ rg = command?(:rg)
23
+ Dir['test/**/*_test.rb'].each do |f|
24
+ rg ? sh("rg #{f}") : ruby(f)
25
+ end
26
+ end
27
+
28
+ desc "Bump version"
29
+ task :git_tag_version do
30
+ require 'resque-sliders/version'
31
+ git_tags = `git tag -l`.split.map { |x| x.gsub(/v/, '') }
32
+ version = Resque::Plugins::ResqueSliders::Version
33
+ commit_sha = `git log -1 HEAD|head -n1|awk '{print $2}'`
34
+ (puts version, commit_sha; `git tag v#{version} #{commit_sha}`) unless git_tags.include?(version)
35
+ end
data/bin/kewatcher ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+
5
+ require 'rubygems'
6
+ require 'yaml'
7
+ require 'optparse'
8
+ require 'resque-sliders/kewatcher'
9
+
10
+ options = {
11
+ :verbosity => 0,
12
+ :max_children => 10
13
+ }
14
+
15
+ OptionParser.new do |opt|
16
+ opt.banner = "Usage: #{File.basename($0)} [options]"
17
+
18
+ opt.separator ""
19
+ opt.separator "Options:"
20
+
21
+ opt.on("-c", "--config CONFIG", "Resque Config (Yaml)") do |config|
22
+ options[:config] = YAML.load_file(File.expand_path(config)) rescue nil
23
+ options[:config] ||= config
24
+ end
25
+
26
+ opt.on("-r", "--rakefile RAKEFILE", "Rakefile location") do |rakefile|
27
+ options[:rakefile] = rakefile
28
+ end
29
+
30
+ opt.on("-p", "--pidfile PIDFILE", "PID File location") do |pidfile|
31
+ options[:pidfile] = pidfile
32
+ end
33
+
34
+ opt.on("-f", "--force", "FORCE KILL ANY OTHER RUNNING KEWATCHERS") do
35
+ options[:force] = true
36
+ end
37
+
38
+ opt.on("-v", "--verbose", "Verbosity") do
39
+ options[:verbosity] += 1
40
+ end
41
+
42
+ opt.on("-m", "--max MAX", "Max Children") do |max|
43
+ options[:max_children] = max.to_i
44
+ end
45
+
46
+ opt.on("-h", "--help", "This help") do
47
+ puts opt
48
+ exit
49
+ end
50
+
51
+ end.parse!
52
+
53
+ begin
54
+ options[:config] = case options[:config]
55
+ when Hash
56
+ options[:config][ENV['RAILS_ENV'] || 'development']
57
+ when String
58
+ options[:config]
59
+ end
60
+ rescue Object => e
61
+ puts e
62
+ exit 1
63
+ end
64
+
65
+ options[:config] ||= 'localhost:6379'
66
+
67
+ kewatcher = Resque::Plugins::ResqueSliders::KEWatcher.new(options)
68
+ kewatcher.run!
@@ -0,0 +1,11 @@
1
+ development:
2
+ localhost:6379
3
+
4
+ test:
5
+ localhost:6379
6
+
7
+ staging:
8
+ localhost:6379
9
+
10
+ production:
11
+ 192.168.0.1:6379
@@ -0,0 +1,73 @@
1
+ module Resque
2
+ module Plugins
3
+ module ResqueSliders
4
+ class Commander
5
+
6
+ include Helpers
7
+
8
+ # Hosts that have config data (queues and values), but the host is not running the daemon.
9
+ attr_reader :stale_hosts
10
+
11
+ def initialize
12
+ @host_status = redis_get_hash(host_config_key)
13
+ @stale_hosts = Resque.redis.keys("#{key_prefix}:*").map { |x| y = x.split(':').last; y unless x == host_config_key or hosts.include?(y) }.compact.sort
14
+ end
15
+
16
+ # Return Array of currently online hosts
17
+ def hosts
18
+ @host_status.keys.select { |x| x unless x.split(':').last == 'reload' }.map { |x| x.split(':').first }.uniq.sort
19
+ end
20
+
21
+ # Array of all hosts (current + stale)
22
+ def all_hosts
23
+ (hosts + stale_hosts).sort
24
+ end
25
+
26
+ # Return current children count or nil if Host hasn't registered itself.
27
+ def current_children(host)
28
+ @host_status["#{host}:current_children"].to_i if max_children(host)
29
+ end
30
+
31
+ # Return max children count or nil if Host hasn't registered itself.
32
+ def max_children(host)
33
+ max = @host_status["#{host}:max_children"].to_i
34
+ max == 0 ? nil : max # if Max isn't set its not running
35
+ end
36
+
37
+ # Override max_children on host (Dangerous!)
38
+ def max_children!(host, count)
39
+ @hostname = host
40
+ register_setting('max_children', count)
41
+ end
42
+
43
+ # Return Array of queues on host
44
+ def queues_on(host)
45
+ queue_values(host).keys if all_hosts.include?(host)
46
+ end
47
+
48
+ # Changes queues to quantiy for host.
49
+ # Returns boolean.
50
+ def change(host, queue, quantity)
51
+ # queue is sanitized by:
52
+ # replacing punctuation with spaces, strip end spaces, split on remaining whitespace, and join again on comma.
53
+ # FIXME
54
+ queue2 = queue.downcase.gsub(/[^a-z 0-9,_\-\*]/, '').strip.split(/, */).reject {|x| (x.length == 1 and %w(- _).include?(x)) or x.empty? }.join(',')
55
+ raise 'Queue Different' unless queue == queue2
56
+ redis_set_hash("#{key_prefix}:#{host}", queue2, quantity) unless queue2.empty?
57
+ end
58
+
59
+ # Deletes queue for host.
60
+ # Returns boolean.
61
+ def delete(host, queue)
62
+ redis_del_hash("#{key_prefix}:#{host}", queue)
63
+ end
64
+
65
+ # Sets reload flag on field of config_key to reload host's KEWatcher
66
+ def reload(host)
67
+ redis_set_hash(host_config_key, "#{host}:reload", 1)
68
+ end
69
+
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,72 @@
1
+ module Resque
2
+ module Plugins
3
+ module ResqueSliders
4
+ module Helpers
5
+
6
+ def key_prefix
7
+ "plugins:resque-sliders"
8
+ end
9
+
10
+ def host_config_key
11
+ "plugins:resque-sliders:host_configs"
12
+ end
13
+
14
+ def redis_get_hash(key)
15
+ Resque.redis.hgetall(key)
16
+ end
17
+
18
+ def redis_get_hash_field(key, field)
19
+ Resque.redis.hget(key, field)
20
+ end
21
+
22
+ def redis_set_hash(key, field, fvalue)
23
+ Resque.redis.hset(key, field, fvalue) == 1 ? true : false
24
+ end
25
+
26
+ def redis_del_hash(key, field)
27
+ Resque.redis.hdel(key, field) == 1 ? true : false
28
+ end
29
+
30
+ # Return Hash: { queue => # }
31
+ def queue_values(host)
32
+ redis_get_hash("#{key_prefix}:#{host}")
33
+ end
34
+
35
+ def register_setting(setting, value)
36
+ redis_set_hash(host_config_key, "#{@hostname}:#{setting}", value)
37
+ end
38
+
39
+
40
+ # Signal Checking
41
+
42
+
43
+ # Gets signal field in redis config_key for this host. Don't call directly
44
+ def check_signal(host)
45
+ sig = caller[0][/`([^']*)'/, 1].gsub('?', '')
46
+ raise 'Dont call me that' unless %w(reload pause stop).include?(sig)
47
+ redis_get_hash_field(host_config_key, "#{host}:#{sig}").to_i == 1 ? true : false
48
+ end
49
+
50
+ def reload?(host)
51
+ check_signal(host)
52
+ end
53
+
54
+ def pause?(host)
55
+ check_signal(host)
56
+ end
57
+
58
+ def stop?(host)
59
+ check_signal(host)
60
+ end
61
+
62
+ # Return Hash of signals for host and their values
63
+ #def check_redis_for_signals(host)
64
+ # configs = redis_get_hash(host_config_key)
65
+ # signals = %w(reload pause stop).map { |x| [host,x].join(':') }
66
+ # Hash[configs.delete_if { |k,v| ! signals.include?(k) }.map { |k,v| [k.split(':').last.to_sym ,v] }]
67
+ #end
68
+
69
+ end
70
+ end
71
+ end
72
+ end