kestrel-client 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ pkg/*
@@ -0,0 +1,16 @@
1
+ # kestrel-client: Talk to Kestrel queue server from Ruby
2
+
3
+ Copyright 2010 Twitter, Inc.
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+
@@ -0,0 +1,30 @@
1
+ ROOT_DIR = File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'rubygems' rescue nil
4
+ require 'rake'
5
+ require 'spec/rake/spectask'
6
+
7
+ task :default => :spec
8
+
9
+ desc "Run all specs in spec directory."
10
+ Spec::Rake::SpecTask.new(:spec) do |t|
11
+ t.spec_opts = ['--options', "\"#{ROOT_DIR}/spec/spec.opts\""]
12
+ t.spec_files = FileList['spec/**/*_spec.rb']
13
+ end
14
+
15
+ # gemification with jeweler
16
+ begin
17
+ require 'jeweler'
18
+ Jeweler::Tasks.new do |gemspec|
19
+ gemspec.name = "kestrel-client"
20
+ gemspec.summary = "Ruby Kestrel client"
21
+ gemspec.description = "Ruby client for the Kestrel queue server"
22
+ gemspec.email = "rael@twitter.com"
23
+ gemspec.homepage = "http://github.com/freels/kestrel-client"
24
+ gemspec.authors = ["Matt Freels", "Rael Dornfest"]
25
+ gemspec.add_dependency 'memcached'
26
+ end
27
+ Jeweler::GemcutterTasks.new
28
+ rescue LoadError
29
+ puts "Jeweler not available. Install it with: gem install jeweler"
30
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,71 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{kestrel-client}
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Matt Freels", "Rael Dornfest"]
12
+ s.date = %q{2010-02-11}
13
+ s.description = %q{Ruby client for the Kestrel queue server}
14
+ s.email = %q{rael@twitter.com}
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "README.md",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "kestrel-client.gemspec",
24
+ "lib/kestrel-client.rb",
25
+ "lib/kestrel.rb",
26
+ "lib/kestrel/client.rb",
27
+ "lib/kestrel/client/blocking.rb",
28
+ "lib/kestrel/client/envelope.rb",
29
+ "lib/kestrel/client/json.rb",
30
+ "lib/kestrel/client/proxy.rb",
31
+ "lib/kestrel/client/unmarshal.rb",
32
+ "lib/kestrel/config.rb",
33
+ "spec/kestrel/client/blocking_spec.rb",
34
+ "spec/kestrel/client/envelope_spec.rb",
35
+ "spec/kestrel/client/json_spec.rb",
36
+ "spec/kestrel/client/unmarshal_spec.rb",
37
+ "spec/kestrel/client_spec.rb",
38
+ "spec/kestrel/config/kestrel.yml",
39
+ "spec/kestrel/config_spec.rb",
40
+ "spec/spec.opts",
41
+ "spec/spec_helper.rb"
42
+ ]
43
+ s.homepage = %q{http://github.com/freels/kestrel-client}
44
+ s.rdoc_options = ["--charset=UTF-8"]
45
+ s.require_paths = ["lib"]
46
+ s.rubygems_version = %q{1.3.5}
47
+ s.summary = %q{Ruby Kestrel client}
48
+ s.test_files = [
49
+ "spec/kestrel/client/blocking_spec.rb",
50
+ "spec/kestrel/client/envelope_spec.rb",
51
+ "spec/kestrel/client/json_spec.rb",
52
+ "spec/kestrel/client/unmarshal_spec.rb",
53
+ "spec/kestrel/client_spec.rb",
54
+ "spec/kestrel/config_spec.rb",
55
+ "spec/spec_helper.rb"
56
+ ]
57
+
58
+ if s.respond_to? :specification_version then
59
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
60
+ s.specification_version = 3
61
+
62
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
63
+ s.add_runtime_dependency(%q<memcached>, [">= 0"])
64
+ else
65
+ s.add_dependency(%q<memcached>, [">= 0"])
66
+ end
67
+ else
68
+ s.add_dependency(%q<memcached>, [">= 0"])
69
+ end
70
+ end
71
+
@@ -0,0 +1 @@
1
+ require 'kestrel'
@@ -0,0 +1,10 @@
1
+ require 'yaml'
2
+ require 'socket'
3
+ require 'memcached'
4
+
5
+ require 'kestrel/config'
6
+ require 'kestrel/client'
7
+ require 'kestrel/client/proxy'
8
+ require 'kestrel/client/envelope'
9
+ require 'kestrel/client/blocking'
10
+ require 'kestrel/client/unmarshal'
@@ -0,0 +1,99 @@
1
+ module Kestrel
2
+ class Client < Memcached::Rails
3
+ class Timeout < Timeout::Error; end
4
+
5
+ QUEUE_STAT_NAMES = %w{items bytes total_items logsize expired_items mem_items mem_bytes age discarded}
6
+
7
+ def flush(queue)
8
+ count = 0
9
+ while sizeof(queue) > 0
10
+ while get(queue, true)
11
+ count += 1
12
+ end
13
+ end
14
+ count
15
+ end
16
+
17
+ def peek(queue)
18
+ val = get(queue)
19
+ set(queue, val)
20
+ val
21
+ end
22
+
23
+ def sizeof(queue)
24
+ stat_info = stat(queue)
25
+ stat_info ? stat_info['items'] : 0
26
+ end
27
+
28
+ def available_queues
29
+ stats['queues'].keys.sort
30
+ end
31
+
32
+ def stats
33
+ merge_stats(servers.map { |server| stats_for_server(server) })
34
+ end
35
+
36
+ def stat(queue)
37
+ stats['queues'][queue]
38
+ end
39
+
40
+ private
41
+
42
+ def stats_for_server(server)
43
+ server_name, port = server.split(/:/)
44
+ socket = TCPSocket.new(server_name, port)
45
+ socket.puts "STATS"
46
+
47
+ stats = Hash.new
48
+ stats['queues'] = Hash.new
49
+ while line = socket.readline
50
+ if line =~ /^STAT queue_(\S+?)_(#{QUEUE_STAT_NAMES.join("|")}) (\S+)/
51
+ queue_name, queue_stat_name, queue_stat_value = $1, $2, deserialize_stat_value($3)
52
+ stats['queues'][queue_name] ||= Hash.new
53
+ stats['queues'][queue_name][queue_stat_name] = queue_stat_value
54
+ elsif line =~ /^STAT (\w+) (\S+)/
55
+ stat_name, stat_value = $1, deserialize_stat_value($2)
56
+ stats[stat_name] = stat_value
57
+ elsif line =~ /^END/
58
+ socket.close
59
+ break
60
+ elsif defined?(RAILS_DEFAULT_LOGGER)
61
+ RAILS_DEFAULT_LOGGER.debug("KestrelClient#stats_for_server: Ignoring #{line}")
62
+ end
63
+ end
64
+
65
+ stats
66
+ end
67
+
68
+ def merge_stats(all_stats)
69
+ result = Hash.new
70
+
71
+ all_stats.each do |stats|
72
+ stats.each do |stat_name, stat_value|
73
+ if result.has_key?(stat_name)
74
+ if stat_value.kind_of?(Hash)
75
+ result[stat_name] = merge_stats([result[stat_name], stat_value])
76
+ else
77
+ result[stat_name] += stat_value
78
+ end
79
+ else
80
+ result[stat_name] = stat_value
81
+ end
82
+ end
83
+ end
84
+
85
+ result
86
+ end
87
+
88
+ def deserialize_stat_value(value)
89
+ case value
90
+ when /^\d+\.\d+$/:
91
+ value.to_f
92
+ when /^\d+$/:
93
+ value.to_i
94
+ else
95
+ value
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,31 @@
1
+ module Kestrel
2
+ class Client
3
+ class Blocking < Proxy
4
+ DEFAULT_EXPIRY = 0
5
+ WAIT_TIME_BEFORE_RETRY = 0.25
6
+
7
+ def get(*args)
8
+ while !(response = client.get(*args))
9
+ sleep WAIT_TIME_BEFORE_RETRY
10
+ end
11
+ response
12
+ end
13
+
14
+ def get_without_blocking(*args)
15
+ client.get(*args)
16
+ end
17
+
18
+ def set(key, value, expiry = DEFAULT_EXPIRY, raw = false)
19
+ @retried = false
20
+ begin
21
+ client.set(key, value, expiry, raw)
22
+ rescue Memcached::Error => e
23
+ raise if @retried
24
+ sleep(WAIT_TIME_BEFORE_RETRY)
25
+ @retried = true
26
+ retry
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ module Kestrel
2
+ class Client
3
+ class Envelope < Proxy
4
+ attr_accessor :envelope_class
5
+
6
+ def initialize(envelope_class, client)
7
+ @envelope_class = envelope_class
8
+ super(client)
9
+ end
10
+
11
+ def get(*args)
12
+ response = client.get(*args)
13
+ if response.respond_to?(:unwrap)
14
+ response.unwrap
15
+ else
16
+ response
17
+ end
18
+ end
19
+
20
+ def set(key, value, *args)
21
+ client.set(key, envelope_class.new(value), *args)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ require 'json'
2
+
3
+ module Kestrel
4
+ class Client
5
+ class Json < Proxy
6
+ def get(*args)
7
+ response = client.get(*args)
8
+ if response.is_a?(String)
9
+ HashWithIndifferentAccess.new(JSON.parse(response)) rescue response
10
+ else
11
+ response
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ module Kestrel
2
+ class Client
3
+ class Proxy
4
+ attr_reader :client
5
+
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def method_missing(method, *args, &block)
11
+ client.send(method, *args, &block)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ module Kestrel
2
+ class Client
3
+ class Unmarshal < Proxy
4
+ def get(keys, raw = false)
5
+ response = client.get(keys, true)
6
+ return response if raw
7
+ if is_marshaled?(response)
8
+ Marshal.load_with_constantize(response, loaded_constants = [])
9
+ else
10
+ response
11
+ end
12
+ end
13
+
14
+ def is_marshaled?(object)
15
+ object.to_s[0] == Marshal::MAJOR_VERSION && object.to_s[1] == Marshal::MINOR_VERSION
16
+ rescue Exception
17
+ false
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,48 @@
1
+ require 'yaml'
2
+
3
+ module Kestrel
4
+ module Config
5
+ class ConfigNotLoaded < StandardError; end
6
+
7
+ extend self
8
+
9
+ attr_accessor :environment, :config
10
+
11
+ def load(config_file)
12
+ self.config = YAML.load_file(config_file)
13
+ end
14
+
15
+ def environment
16
+ @environment ||= 'development'
17
+ end
18
+
19
+ def config
20
+ @config or raise ConfigNotLoaded
21
+ end
22
+
23
+ def namespace(namespace)
24
+ client_args_from config[namespace.to_s][environment.to_s]
25
+ end
26
+
27
+ def default
28
+ client_args_from config[environment.to_s]
29
+ end
30
+
31
+ def new_client(space = nil)
32
+ Client.new *(space ? namespace(space) : default)
33
+ end
34
+
35
+ alias method_missing namespace
36
+
37
+ private
38
+
39
+ def client_args_from(config)
40
+ sanitized = config.inject({}) do |sanitized, (key, val)|
41
+ sanitized[key.to_sym] = val; sanitized
42
+ end
43
+ servers = sanitized.delete(:servers)
44
+
45
+ [servers, sanitized]
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,72 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "Kestrel::Client::Blocking" do
4
+ describe "Instance Methods" do
5
+ before do
6
+ Kestrel::Config.load TEST_CONFIG_FILE
7
+ @raw_kestrel_client = Kestrel::Client.new(*Kestrel::Config.default)
8
+ @kestrel = Kestrel::Client::Blocking.new(@raw_kestrel_client)
9
+ end
10
+
11
+ describe "#set" do
12
+ before do
13
+ @queue = "some_queue"
14
+ @value = "some_value"
15
+ end
16
+
17
+ it "blocks on a set until the set works" do
18
+ @queue = "some_queue"
19
+ @value = "some_value"
20
+ mock(@raw_kestrel_client)\
21
+ .set(@queue, @value, anything, anything) { raise Memcached::Error }.then\
22
+ .set(@queue, @value, anything, anything) { :mcguffin }
23
+ mock(@kestrel).sleep(Kestrel::Client::Blocking::WAIT_TIME_BEFORE_RETRY).once
24
+ @kestrel.set(@queue, @value).should == :mcguffin
25
+ end
26
+
27
+ it "raises if two sets in a row fail" do
28
+ mock(@raw_kestrel_client)\
29
+ .set(@queue, @value, anything, anything) { raise Memcached::Error }.then\
30
+ .set(@queue, @value, anything, anything) { raise Memcached::Error }
31
+ mock(@kestrel).sleep(Kestrel::Client::Blocking::WAIT_TIME_BEFORE_RETRY).once
32
+ lambda { @kestrel.set(@queue, @value) }.should raise_error(Memcached::Error)
33
+ end
34
+
35
+ it "passes along the default expiry time if none is given" do
36
+ @queue = "some_queue"
37
+ @value = "some_value"
38
+ mock(@raw_kestrel_client).set(@queue, @value, Kestrel::Client::Blocking::DEFAULT_EXPIRY, anything)
39
+ @kestrel.set(@queue, @value)
40
+ end
41
+
42
+ it "passes along the given expiry time if one is passed in" do
43
+ @queue = "some_queue"
44
+ @value = "some_value"
45
+ mock(@raw_kestrel_client).set(@queue, @value, 60, anything)
46
+ @kestrel.set(@queue, @value, 60)
47
+ end
48
+ end
49
+
50
+ describe "#get" do
51
+ before do
52
+ @queue = "some_queue"
53
+ end
54
+
55
+ it "blocks on a get until the get works" do
56
+ mock(@raw_kestrel_client)\
57
+ .get(@queue) { nil }.then\
58
+ .get(@queue) { :mcguffin }
59
+ mock(@kestrel).sleep(Kestrel::Client::Blocking::WAIT_TIME_BEFORE_RETRY).once
60
+ @kestrel.get(@queue).should == :mcguffin
61
+ end
62
+
63
+ describe "#get_without_blocking" do
64
+ it "does not block" do
65
+ mock(@raw_kestrel_client).get(@queue) { nil }
66
+ mock(@kestrel).sleep(Kestrel::Client::Blocking::WAIT_TIME_BEFORE_RETRY).never
67
+ @kestrel.get_without_blocking(@queue).should be_nil
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,34 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ class Envelope; end
4
+
5
+ describe Kestrel::Client::Envelope do
6
+ describe "Instance Methods" do
7
+ before do
8
+ Kestrel::Config.load TEST_CONFIG_FILE
9
+ @raw_kestrel_client = Kestrel::Client.new(*Kestrel::Config.default)
10
+ @kestrel = Kestrel::Client::Envelope.new(Envelope, @raw_kestrel_client)
11
+ end
12
+
13
+ describe "#get and #set" do
14
+ describe "envelopes" do
15
+ it "creates an envelope on a set" do
16
+ mock(Envelope).new(:mcguffin)
17
+ @kestrel.set('a_queue', :mcguffin)
18
+ end
19
+
20
+ it "unwraps an envelope on a get" do
21
+ envelope = Envelope.new
22
+ mock(@raw_kestrel_client).get('a_queue') { envelope }
23
+ mock(envelope).unwrap { :mcguffin }
24
+ @kestrel.get('a_queue').should == :mcguffin
25
+ end
26
+
27
+ it "does not unwrap a nil get" do
28
+ mock(@raw_kestrel_client).get('a_queue') { nil }
29
+ @kestrel.get('a_queue').should be_nil
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,42 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ require 'kestrel/client/json'
4
+
5
+ describe Kestrel::Client::Json do
6
+ describe "Instance Methods" do
7
+ before do
8
+ Kestrel::Config.load TEST_CONFIG_FILE
9
+ @raw_kestrel_client = Kestrel::Client.new(*Kestrel::Config.default)
10
+ @kestrel = Kestrel::Client::Json.new(@raw_kestrel_client)
11
+ end
12
+
13
+ describe "#get" do
14
+ it "parses json" do
15
+ mock(@raw_kestrel_client).get('a_queue') { '{"a": 1, "b": [{"c": 2}]}' }
16
+ @kestrel.get('a_queue').should == {"a" => 1, "b" => ["c" => 2]}
17
+ end
18
+
19
+ it "uses a HashWithIndifferentAccess" do
20
+ mock(@raw_kestrel_client).get('a_queue') { '{"a": 1, "b": [{"c": 2}]}' }
21
+ @kestrel.get('a_queue').class.should == HashWithIndifferentAccess
22
+ end
23
+
24
+ it "passes through non-strings" do
25
+ mock(@raw_kestrel_client).get('a_queue') { {:key => "value"} }
26
+ @kestrel.get('a_queue').should == {:key => "value"}
27
+ end
28
+
29
+ it "passes through strings that are not json" do
30
+ mock(@raw_kestrel_client).get('a_queue') { "I am not JSON" }
31
+ @kestrel.get('a_queue').should == "I am not JSON"
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ class HashWithIndifferentAccess < Hash
38
+ def initialize(hash = {})
39
+ super()
40
+ merge!(hash)
41
+ end
42
+ end
@@ -0,0 +1,61 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Kestrel::Client::Unmarshal do
4
+ describe "Instance Methods" do
5
+ before do
6
+ Kestrel::Config.load TEST_CONFIG_FILE
7
+ @raw_kestrel_client = Kestrel::Client.new(*Kestrel::Config.default)
8
+ @kestrel = Kestrel::Client::Unmarshal.new(@raw_kestrel_client)
9
+ end
10
+
11
+ describe "#get" do
12
+ it "unmarshals marshaled objects" do
13
+ test_object = {:a => 1, :b => [1, 2, 3]}
14
+ mock(@raw_kestrel_client).get('a_queue', true) { Marshal.dump(test_object) }
15
+ @kestrel.get('a_queue').should == test_object
16
+ end
17
+
18
+ it "does not unmarshal when raw is true" do
19
+ test_object = {:a => 1, :b => [1, 2, 3]}
20
+ mock(@raw_kestrel_client).get('a_queue', true) { Marshal.dump(test_object) }
21
+ @kestrel.get('a_queue', true).should == Marshal.dump(test_object)
22
+ end
23
+
24
+ it "pasess through objects" do
25
+ test_object = Object.new
26
+ mock(@raw_kestrel_client).get('a_queue', true) { test_object }
27
+ @kestrel.get('a_queue').should == test_object
28
+ end
29
+
30
+ it "passes through strings" do
31
+ mock(@raw_kestrel_client).get('a_queue', true) { "I am not marshaled" }
32
+ @kestrel.get('a_queue').should == "I am not marshaled"
33
+ end
34
+ end
35
+
36
+ describe "#isMarshaled" do
37
+ it "should foo" do
38
+ @kestrel.is_marshaled?("foo").should be_false
39
+ @kestrel.is_marshaled?(Marshal.dump("foo")).should be_true
40
+
41
+ @kestrel.is_marshaled?({}).should be_false
42
+ @kestrel.is_marshaled?(Marshal.dump({})).should be_true
43
+
44
+ @kestrel.is_marshaled?(BadObject.new).should be_false
45
+ @kestrel.is_marshaled?(Marshal.dump(BadObject.new)).should be_true
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ class BadObject
52
+ def to_s
53
+ raise Exception
54
+ end
55
+ end
56
+
57
+ module Marshal
58
+ def self.load_with_constantize(source, loaded_constants = [])
59
+ self.load(source)
60
+ end
61
+ end
@@ -0,0 +1,107 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Kestrel::Client do
4
+ describe "Instance Methods" do
5
+ before do
6
+ Kestrel::Config.load TEST_CONFIG_FILE
7
+ @kestrel = Kestrel::Client.new(*Kestrel::Config.default)
8
+ stub(@kestrel).with_timing(anything) { |_, block| block.call }
9
+ end
10
+
11
+ describe "#get and #set" do
12
+ it "basic operation" do
13
+ @kestrel.flush(queue = "test_queue")
14
+ @kestrel.set(queue, value = "russell's reserve")
15
+ @kestrel.get(queue).should == value
16
+ end
17
+ end
18
+
19
+ describe "#flush" do
20
+ before do
21
+ @queue = "some_random_queue_#{Time.now.to_i}_#{rand(10000)}"
22
+ end
23
+
24
+ it "counts the number of items flushed and passes each of them to a given block" do
25
+ %w{A B C}.each { |item| @kestrel.set(@queue, item) }
26
+ @kestrel.flush(@queue).should == 3
27
+ end
28
+
29
+ it "does not attempt to Marshal load the data being flushed" do
30
+ @kestrel.set(@queue, "some_stuff", 0, true)
31
+ mock(Marshal).load.never
32
+ @kestrel.flush(@queue).should == 1
33
+ end
34
+ end
35
+
36
+ describe "#peek" do
37
+ it "should return first item from the queue and reenqueue" do
38
+ @queue = "some_random_queue_#{Time.now.to_i}_#{rand(10000)}"
39
+ @kestrel.set(@queue, "item_1")
40
+ @kestrel.set(@queue, "item_2")
41
+ @kestrel.peek(@queue).should == "item_1"
42
+ @kestrel.sizeof(@queue).should == 2
43
+ end
44
+ end
45
+
46
+ describe "#stats" do
47
+ it "retrieves stats" do
48
+ stats = @kestrel.stats
49
+ %w{uptime time version curr_items total_items bytes curr_connections total_connections
50
+ cmd_get cmd_set get_hits get_misses bytes_read bytes_written queues}.each do |stat|
51
+ stats[stat].should_not be_nil
52
+ end
53
+
54
+ @kestrel.set("test-queue-name", 97)
55
+ stats['queues']["test-queue-name"].should_not be_nil
56
+ Kestrel::Client::QUEUE_STAT_NAMES.each do |queue_stat|
57
+ stats['queues']['test-queue-name'][queue_stat].should_not be_nil
58
+ end
59
+ end
60
+
61
+ it "merge in stats from all the servers" do
62
+ server = @kestrel.servers.first
63
+ stub(@kestrel).servers { [server] }
64
+ stats_for_one_server = @kestrel.stats
65
+
66
+ server = @kestrel.servers.first
67
+ stub(@kestrel).servers { [server] * 2 }
68
+ stats_for_two_servers = @kestrel.stats
69
+
70
+ stats_for_two_servers['bytes'].should == 2*stats_for_one_server['bytes']
71
+ end
72
+ end
73
+
74
+ describe "#stat" do
75
+ it "get stats for single queue" do
76
+ @kestrel.set(queue = "test-queue-name", 97)
77
+ all_stats = @kestrel.stats
78
+ @kestrel.stat(queue).should == all_stats['queues'][queue]
79
+ end
80
+ end
81
+
82
+ describe "#sizeof" do
83
+ before do
84
+ @queue = "some_random_queue_#{Time.now.to_i}_#{rand(10000)}"
85
+ end
86
+
87
+ it "reports the size of the queue" do
88
+ 100.times { @kestrel.set(@queue, true) }
89
+ @kestrel.sizeof(@queue).should == 100
90
+ end
91
+
92
+ it "reports the size of a non-existant queue as 0" do
93
+ queue = "some_random_queue_#{Time.now.to_i}_#{rand(10000)}"
94
+ @kestrel.sizeof(queue).should == 0
95
+ end
96
+ end
97
+
98
+ describe "#available_queues" do
99
+ it "returns all the queue names" do
100
+ @kestrel.set("test-queue-name1", 97)
101
+ @kestrel.set("test-queue-name2", 155)
102
+ @kestrel.available_queues.should include('test-queue-name1')
103
+ @kestrel.available_queues.should include('test-queue-name2')
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,42 @@
1
+ defaults: &defaults
2
+ distribution: :random
3
+ timeout: 10
4
+ connect_timeout: 2
5
+ server_failure_limit: 10
6
+ auto_eject_hosts: false
7
+
8
+ production: &production
9
+ <<: *defaults
10
+ auto_eject_hosts: true
11
+ server_failure_limit: 4
12
+ servers:
13
+ - localhost:22133
14
+
15
+ staging:
16
+ <<: *production
17
+ servers:
18
+ - localhost:22133
19
+
20
+ development: &development
21
+ <<: *defaults
22
+ show_backtraces: true
23
+ servers:
24
+ - localhost:22133
25
+
26
+ benchmark:
27
+ <<: *development
28
+
29
+ test:
30
+ <<: *development
31
+
32
+ replication_lag:
33
+ <<: *development
34
+
35
+ selenium:
36
+ <<: *development
37
+
38
+ foo_space:
39
+ production:
40
+ <<: *production
41
+ development:
42
+ <<: *development
@@ -0,0 +1,75 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Kestrel::Config do
4
+ before do
5
+ Kestrel::Config.environment = nil
6
+ Kestrel::Config.load TEST_CONFIG_FILE
7
+
8
+ # to sniff namespace foo_space
9
+ Kestrel::Config.config['foo_space']['development']['connect_timeout'] = 8
10
+ end
11
+
12
+ describe "load" do
13
+ it "loads a yaml file" do
14
+ Kestrel::Config.config = nil
15
+ lambda { Kestrel::Config.default }.should raise_error(Kestrel::Config::ConfigNotLoaded)
16
+
17
+ Kestrel::Config.load TEST_CONFIG_FILE
18
+ lambda { Kestrel::Config.default }.should_not raise_error(Kestrel::Config::ConfigNotLoaded)
19
+ end
20
+ end
21
+
22
+ shared_examples_for "config getters" do
23
+ it "returns a tuple of [servers, options]" do
24
+ config = @configurer.call
25
+ config.should be_a(Array)
26
+
27
+ [String, Array].should include(config.first.class)
28
+ config.last.should be_a(Hash)
29
+
30
+ config.last.keys.map{|k| k.class }.uniq.should == [Symbol]
31
+ end
32
+
33
+ it "defaults to development enviroment" do
34
+ @configurer.call.last[:server_failure_limit].should == 10 # development options should pull 10 from defaults
35
+ end
36
+
37
+ it "returns config for the specified environment" do
38
+ Kestrel::Config.environment = :production
39
+ @configurer.call.last[:server_failure_limit].should == 4 # production is set to 4
40
+ end
41
+ end
42
+
43
+ describe "namespace" do
44
+ before { @configurer = lambda { Kestrel::Config.namespace(:foo_space) } }
45
+
46
+ it_should_behave_like "config getters"
47
+
48
+ it "returns args for Kestrel::Client.new for the appropriate namespace" do
49
+ Kestrel::Config.foo_space.last[:connect_timeout].should == 8
50
+ end
51
+
52
+ it "is aliased to method_missing" do
53
+ Kestrel::Config.foo_space.should == Kestrel::Config.namespace(:foo_space)
54
+ end
55
+ end
56
+
57
+ describe "default" do
58
+ before { @configurer = lambda { Kestrel::Config.default } }
59
+
60
+ it_should_behave_like "config getters"
61
+ end
62
+
63
+ describe "new_client" do
64
+ it "returns a Kestrel::Client instance" do
65
+ client = Kestrel::Config.new_client
66
+ client.should be_a(Kestrel::Client)
67
+ end
68
+
69
+ it "can take a namespace" do
70
+ client = Kestrel::Config.new_client(:foo_space)
71
+ client.should be_a(Kestrel::Client)
72
+ client.options[:connect_timeout].should == 8
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,7 @@
1
+ --colour
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
5
+ --timeout 20
6
+ --diff
7
+ --backtrace
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ spec_dir = File.dirname(__FILE__)
5
+
6
+ # make sure we load local libs rather than gems first
7
+ $: << File.expand_path("#{spec_dir}/../lib")
8
+
9
+ require 'kestrel'
10
+
11
+ TEST_CONFIG_FILE = File.expand_path("#{spec_dir}/kestrel/config/kestrel.yml")
12
+
13
+ Spec::Runner.configure do |config|
14
+ config.mock_with :rr
15
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kestrel-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Matt Freels
8
+ - Rael Dornfest
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2010-02-11 00:00:00 -08:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: memcached
18
+ type: :runtime
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ version:
26
+ description: Ruby client for the Kestrel queue server
27
+ email: rael@twitter.com
28
+ executables: []
29
+
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - README.md
34
+ files:
35
+ - .gitignore
36
+ - README.md
37
+ - Rakefile
38
+ - VERSION
39
+ - kestrel-client.gemspec
40
+ - lib/kestrel-client.rb
41
+ - lib/kestrel.rb
42
+ - lib/kestrel/client.rb
43
+ - lib/kestrel/client/blocking.rb
44
+ - lib/kestrel/client/envelope.rb
45
+ - lib/kestrel/client/json.rb
46
+ - lib/kestrel/client/proxy.rb
47
+ - lib/kestrel/client/unmarshal.rb
48
+ - lib/kestrel/config.rb
49
+ - spec/kestrel/client/blocking_spec.rb
50
+ - spec/kestrel/client/envelope_spec.rb
51
+ - spec/kestrel/client/json_spec.rb
52
+ - spec/kestrel/client/unmarshal_spec.rb
53
+ - spec/kestrel/client_spec.rb
54
+ - spec/kestrel/config/kestrel.yml
55
+ - spec/kestrel/config_spec.rb
56
+ - spec/spec.opts
57
+ - spec/spec_helper.rb
58
+ has_rdoc: true
59
+ homepage: http://github.com/freels/kestrel-client
60
+ licenses: []
61
+
62
+ post_install_message:
63
+ rdoc_options:
64
+ - --charset=UTF-8
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ requirements: []
80
+
81
+ rubyforge_project:
82
+ rubygems_version: 1.3.5
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: Ruby Kestrel client
86
+ test_files:
87
+ - spec/kestrel/client/blocking_spec.rb
88
+ - spec/kestrel/client/envelope_spec.rb
89
+ - spec/kestrel/client/json_spec.rb
90
+ - spec/kestrel/client/unmarshal_spec.rb
91
+ - spec/kestrel/client_spec.rb
92
+ - spec/kestrel/config_spec.rb
93
+ - spec/spec_helper.rb