kestrel-client 0.2.0

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