qtrix 0.0.1

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.
@@ -0,0 +1,77 @@
1
+ module Qtrix
2
+ class Override
3
+ extend Qtrix::Namespacing
4
+ REDIS_KEY = :overrides
5
+ REDIS_CLAIMS_KEY = :override_claims
6
+
7
+ class << self
8
+ def add(*args)
9
+ namespace, queues, processes = extract_args(2, *args)
10
+ validate!(processes)
11
+ processes.times do
12
+ redis(namespace).rpush(REDIS_KEY, queues.join(","))
13
+ end
14
+ Qtrix::Matrix.clear!(namespace)
15
+ end
16
+
17
+ def all(ns=:current)
18
+ [].tap do |result|
19
+ raw_list(ns).each_with_index do |queues, index|
20
+ host = redis(ns).lindex(REDIS_CLAIMS_KEY, index)
21
+ result << self.new(queues.split(",").map(&:to_sym), host)
22
+ end
23
+ end
24
+ end
25
+
26
+ def remove(*args)
27
+ namespace, queues, processes = extract_args(2, *args)
28
+ redis(namespace).lrem(REDIS_KEY, processes, queues.join(","))
29
+ Qtrix::Matrix.clear!(namespace)
30
+ end
31
+
32
+ def clear!(namespace=:current)
33
+ redis(namespace).del REDIS_KEY
34
+ Qtrix::Matrix.clear!(namespace)
35
+ end
36
+
37
+ def overrides_for(*args)
38
+ namespace, hostname, workers = extract_args(2, *args)
39
+ workers.times {redis(namespace).rpush(REDIS_CLAIMS_KEY, hostname)}
40
+ claimed_by(namespace, hostname).map{|override| override.queues}
41
+ end
42
+
43
+ private
44
+ def validate!(processes)
45
+ raise "processes must be positive integer" unless processes > 0
46
+ end
47
+
48
+ def claimed_by(*args)
49
+ namespace, hostname = extract_args(1, *args)
50
+ all(namespace).select{|override| override.host == hostname}
51
+ end
52
+
53
+ def raw_list(ns=:current)
54
+ redis(ns).lrange(REDIS_KEY, 0, -1)
55
+ end
56
+ end
57
+
58
+ attr_reader :queues, :host
59
+
60
+ def hash
61
+ @queues.hash ^ @processes.hash
62
+ end
63
+
64
+ def eql?(other)
65
+ self.class.equal?(other.class) &&
66
+ @processes == other.processes &&
67
+ @queues == other.queues
68
+ end
69
+ alias == eql?
70
+
71
+ private
72
+ def initialize(queues, host=nil)
73
+ @queues = queues
74
+ @host = host
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,77 @@
1
+ require 'bigdecimal'
2
+ require 'set'
3
+
4
+ module Qtrix
5
+ class Queue
6
+ include Qtrix::Namespacing
7
+ REDIS_KEY = :queue_weights
8
+
9
+ class << self
10
+ def map_queue_weights(*args)
11
+ namespace, map = extract_args(1, *args)
12
+ map.each {|queue, weight| validate(queue.to_s, weight.to_f)}
13
+ self.clear!(namespace)
14
+ map.each {|queue, weight| redis(namespace).zadd(REDIS_KEY, weight.to_f, queue.to_s)}
15
+ Qtrix::Matrix.clear!(namespace)
16
+ end
17
+
18
+
19
+ def all_queues(namespace=:current)
20
+ raw = redis(namespace).zrevrange(REDIS_KEY, 0, -1, withscores: true)
21
+ [].tap {|result|
22
+ raw.each_slice(2) do |tuple|
23
+ result << self.new(namespace, tuple[0], tuple[1].to_f)
24
+ end
25
+ }
26
+ end
27
+
28
+ def count(namespace=:current)
29
+ redis(namespace).zcard(REDIS_KEY)
30
+ end
31
+
32
+ def total_weight(ns=:current)
33
+ all_queues(ns).inject(0) {|memo, queue| memo += queue.weight}
34
+ end
35
+
36
+ def clear!(namespace=:current)
37
+ redis(namespace).del REDIS_KEY
38
+ Qtrix::Matrix.clear! namespace
39
+ end
40
+
41
+ private
42
+ def validate(name, weight)
43
+ raise "nil name" if name.nil?
44
+ raise "empty name" if name.empty?
45
+ raise "nil weight" if weight.nil?
46
+ raise "weight of 0 or less" if weight <= 0
47
+ end
48
+
49
+ def high_low
50
+ lambda{|i,j| j.weight <=> i.weight}
51
+ end
52
+ end
53
+ attr_reader :name, :namespace
54
+
55
+ def initialize(ns, name, weight)
56
+ @namespace = ns
57
+ @name = name.to_sym
58
+ @weight = weight.to_f
59
+ end
60
+
61
+ def ==(other)
62
+ name == other.name && weight == other.weight
63
+ end
64
+
65
+ def hash
66
+ name.hash - weight.hash
67
+ end
68
+
69
+ def resource_percentage
70
+ @resource_percentage ||= weight.to_f / self.class.total_weight(namespace)
71
+ end
72
+
73
+ def weight
74
+ @weight ||= redis(namespace).zscore(REDIS_KEY, name).to_f
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,3 @@
1
+ module Qtrix
2
+ VERSION = "0.0.1"
3
+ end
data/qtrix.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'qtrix/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "qtrix"
8
+ gem.version = Qtrix::VERSION
9
+ gem.authors = ["Lance Woodson", "Joshua Flanagan"]
10
+ gem.email = ["jflanagan@peopleadmin.com"]
11
+ gem.description = %q{Central worker queues config}
12
+ gem.summary = gem.description
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ gem.add_dependency "redis-namespace", "~> 1.0.2"
20
+ gem.add_dependency "mixlib-cli", "1.3.0"
21
+ gem.add_development_dependency "rspec-core", "2.11.0"
22
+ gem.add_development_dependency "rspec-prof", "0.0.3"
23
+ gem.add_development_dependency "pry-debugger"
24
+ end
@@ -0,0 +1,94 @@
1
+ require 'qtrix/cli/spec_helper'
2
+
3
+ describe Qtrix::CLI::ConfigSets do
4
+ include_context "cli commands"
5
+ let(:config_sets) {
6
+ Qtrix::CLI::ConfigSets.new(stdout_stream, stderr_stream)
7
+ }
8
+
9
+ describe "listing configuration sets" do
10
+ before(:each) {config_sets.parse_options(["-l"])}
11
+
12
+ it "should display default when no config sets have been added" do
13
+ config_sets.exec
14
+ stdout.should match /default/
15
+ end
16
+
17
+ it "should display all config sets" do
18
+ Qtrix.create_configuration_set :night
19
+ config_sets.exec
20
+ stdout.should match /default/
21
+ stdout.should match /night/
22
+ end
23
+ end
24
+
25
+ describe "current configuration set" do
26
+ before(:each) {config_sets.parse_options(["-c"])}
27
+
28
+ it "should display the current configuration set" do
29
+ Qtrix.create_configuration_set :night
30
+ Qtrix.map_queue_weights :night, A: 10
31
+ Qtrix.activate_configuration_set! :night
32
+ config_sets.exec
33
+ stdout.should match /night/
34
+ end
35
+ end
36
+
37
+ describe "create configuration set" do
38
+ it "should allow creation of a new configuration set" do
39
+ config_sets.parse_options "--create day".split
40
+ config_sets.exec
41
+ stdout.should match /success/
42
+ Qtrix.configuration_sets.include?(:day).should == true
43
+ end
44
+
45
+ it "should not allow creation of duplicate configuration sets" do
46
+ Qtrix.create_configuration_set 'night'
47
+ config_sets.parse_options "--create night".split
48
+ config_sets.exec
49
+ stderr.should match /failure/i
50
+ Qtrix.configuration_sets.select{|cs| cs == :default}.size.should == 1
51
+ end
52
+ end
53
+
54
+ describe "activate configuration set" do
55
+ it "should allow activation of a configuration set" do
56
+ Qtrix.create_configuration_set 'night'
57
+ Qtrix.map_queue_weights 'night', A: 10
58
+ config_sets.parse_options "-a night".split
59
+ config_sets.exec
60
+ stdout.should match /success/i
61
+ Qtrix.current_configuration_set.should == :night
62
+ end
63
+ end
64
+
65
+ describe "remove configuration set" do
66
+ before(:each) do
67
+ Qtrix.create_configuration_set :night
68
+ Qtrix.map_queue_weights :night, A: 10
69
+ end
70
+
71
+ it "should allow removal of a configuration set" do
72
+ config_sets.parse_options "-d night".split
73
+ config_sets.exec
74
+ stdout.should match /success/
75
+ Qtrix.configuration_sets.detect{|cs| cs == :night}.should be_nil
76
+ end
77
+
78
+ it "should not allow removal of the current configuration set" do
79
+ Qtrix.activate_configuration_set! :night
80
+ config_sets.parse_options "-d night".split
81
+ config_sets.exec
82
+ stderr.should match /failure/i
83
+ Qtrix.configuration_sets.select{|cs| cs == :night}.size.should == 1
84
+ end
85
+
86
+ it "should not allow removal of the default configuration set" do
87
+ Qtrix.activate_configuration_set! :night
88
+ config_sets.parse_options "-d default".split
89
+ config_sets.exec
90
+ stderr.should match /failure/i
91
+ Qtrix.configuration_sets.select{|cs| cs == :default}.size.should == 1
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,101 @@
1
+ require 'qtrix/cli/spec_helper'
2
+
3
+ describe Qtrix::CLI::Overrides do
4
+ include_context "cli commands"
5
+ let(:overrides) {
6
+ Qtrix::CLI::Overrides.new(stdout_stream, stderr_stream)
7
+ }
8
+
9
+ describe "listing override" do
10
+ before(:each) {overrides.parse_options(["-l"])}
11
+
12
+ it "should list all overrides" do
13
+ Qtrix.add_override [:A], 1
14
+ Qtrix.add_override [:B], 1
15
+ overrides.exec
16
+ stdout.should match /A/
17
+ stdout.should match /B/
18
+ end
19
+ end
20
+
21
+ describe "adding overrides" do
22
+ it "should add a single override" do
23
+ overrides.parse_options(%w{-a -q A -w 1})
24
+ overrides.exec
25
+ stdout.should match /A/
26
+ Qtrix.overrides.size.should == 1
27
+ end
28
+
29
+ it "should add multiple overrides for multiple workers" do
30
+ overrides.parse_options(%w{-a -q A -w 2})
31
+ overrides.exec
32
+ stdout.should match /A.+A/
33
+ Qtrix.overrides.size.should == 2
34
+ end
35
+
36
+ it "should default to 1 worker" do
37
+ overrides.parse_options(%w{-a -q A})
38
+ overrides.exec
39
+ stdout.should match /A/
40
+ Qtrix.overrides.size.should == 1
41
+ end
42
+
43
+ it "should error if we dont specify any queues" do
44
+ overrides.parse_options(%w{-a -w 2})
45
+ overrides.exec
46
+ stderr.should match /failure/i
47
+ Qtrix.overrides.size.should == 0
48
+ end
49
+ end
50
+
51
+ describe "deleting overrides" do
52
+ before(:each) {Qtrix.add_override(['A'], 2)}
53
+
54
+ it "should allow deletion of a single override" do
55
+ overrides.parse_options(%w{-d -q A})
56
+ overrides.exec
57
+ stdout.should match /A/
58
+ Qtrix.overrides.size.should == 1
59
+ end
60
+
61
+ it "should allow deletion of multiple overrides" do
62
+ overrides.parse_options(%w{-d -q A -w 2})
63
+ overrides.exec
64
+ stdout.should match /A/
65
+ Qtrix.overrides.size.should == 0
66
+ end
67
+
68
+ it "should error if we dont specify any queues" do
69
+ overrides.parse_options(%w{-d -w 2})
70
+ overrides.exec
71
+ stderr.should match /failure/i
72
+ Qtrix.overrides.size.should == 2
73
+ end
74
+ end
75
+
76
+ describe "targetting to config sets" do
77
+ before(:each) do
78
+ Qtrix.create_configuration_set "night"
79
+ end
80
+
81
+ it "should be able to create overrides in the config set" do
82
+ overrides.parse_options(%w{-c night -a -q A})
83
+ overrides.exec
84
+ Qtrix.overrides('night').size.should == 1
85
+ end
86
+
87
+ it "should be able to list overrides in the config set" do
88
+ Qtrix.add_override('night', ['A'], 1)
89
+ overrides.parse_options(%w{-c night -l})
90
+ overrides.exec
91
+ stdout.should match /A/
92
+ end
93
+
94
+ it "should be able to delete overrides from the config set" do
95
+ Qtrix.add_override('night', ['A'], 1)
96
+ overrides.parse_options(%w{-c night -d -q A})
97
+ overrides.exec
98
+ Qtrix.overrides('night').size.should == 0
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,70 @@
1
+ require 'qtrix/cli/spec_helper'
2
+ require 'tmpdir'
3
+
4
+ describe Qtrix::CLI::Queues do
5
+ include_context "cli commands"
6
+ let(:queues) {
7
+ Qtrix::CLI::Queues.new(stdout_stream, stderr_stream)
8
+ }
9
+ describe "queue weights" do
10
+ it "should set the queue weights for the current config set" do
11
+ queues.parse_options(["-w", "A:40,B:30"])
12
+ queues.exec
13
+ stdout.should match /OK/
14
+ Qtrix.desired_distribution.size.should == 2
15
+ end
16
+
17
+ it "should be targettable to a configuration set" do
18
+ Qtrix.create_configuration_set "night"
19
+ queues.parse_options(["-w", "A:100", "-c", "night"])
20
+ queues.exec
21
+ stdout.should match /OK/
22
+ Qtrix.desired_distribution("night").size.should == 1
23
+ end
24
+ end
25
+
26
+ describe "queue weights by yaml" do
27
+ around(:each) do |example|
28
+ yaml = YAML.dump({A: 40, B: 30, C: 20, D: 10})
29
+ Dir.mktmpdir do |dir|
30
+ @path = File.join(dir, "queue_weight.yml")
31
+ File.write(@path, yaml)
32
+ example.run
33
+ end
34
+ end
35
+
36
+ it "should set the queue weights for the current config set" do
37
+ queues.parse_options(["-y", @path])
38
+ queues.exec
39
+ stdout.should match /OK/
40
+ Qtrix.desired_distribution.size.should == 4
41
+ end
42
+
43
+ it "should set the queue weights for a targetted config set" do
44
+ Qtrix.create_configuration_set "night"
45
+ queues.parse_options(["-y", @path, "-c", "night"])
46
+ queues.exec
47
+ stdout.should match /OK/
48
+ Qtrix.desired_distribution("night").size.should == 4
49
+ end
50
+ end
51
+
52
+ describe "list desired distribution" do
53
+ it "should return the list from the current config set" do
54
+ Qtrix.map_queue_weights A: 10
55
+ queues.parse_options(["-l"])
56
+ queues.exec
57
+ stdout.should match /A/
58
+ stdout.should match /10/
59
+ end
60
+
61
+ it "should return the list from a specified config set" do
62
+ Qtrix.create_configuration_set "night"
63
+ Qtrix.map_queue_weights B: 11
64
+ queues.parse_options(["-l", "-c", "night"])
65
+ queues.exec
66
+ stdout.should match /B/
67
+ stdout.should match /11/
68
+ end
69
+ end
70
+ end