daikon 0.0.0 → 0.1.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.
- data/Gemfile +15 -0
- data/Gemfile.lock +59 -0
- data/{LICENSE.txt → MIT-LICENSE} +0 -0
- data/Rakefile +0 -9
- data/daikon.gemspec +29 -19
- data/lib/daikon.rb +5 -1
- data/lib/daikon/client.rb +82 -52
- data/lib/daikon/configuration.rb +6 -2
- data/lib/daikon/daemon.rb +10 -9
- data/lib/daikon/namespace_tools.rb +1 -1
- data/spec/client_spec.rb +207 -1
- data/spec/configuration_spec.rb +19 -1
- data/spec/spec_helper.rb +16 -1
- metadata +98 -56
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem "daemons", "~> 1.1.0"
|
4
|
+
gem "json_pure", "~> 1.4.6"
|
5
|
+
gem "net-http-persistent", "~> 1.4.1"
|
6
|
+
gem "redis", "~> 2.1.1"
|
7
|
+
gem "SystemTimer", "~> 1.2.1"
|
8
|
+
|
9
|
+
group :development do
|
10
|
+
gem "rspec"
|
11
|
+
gem "cucumber"
|
12
|
+
gem "jeweler"
|
13
|
+
gem "bourne"
|
14
|
+
gem "webmock"
|
15
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
SystemTimer (1.2.1)
|
5
|
+
addressable (2.2.2)
|
6
|
+
bourne (1.0)
|
7
|
+
mocha (= 0.9.8)
|
8
|
+
builder (2.1.2)
|
9
|
+
crack (0.1.8)
|
10
|
+
cucumber (0.9.4)
|
11
|
+
builder (~> 2.1.2)
|
12
|
+
diff-lcs (~> 1.1.2)
|
13
|
+
gherkin (~> 2.2.9)
|
14
|
+
json (~> 1.4.6)
|
15
|
+
term-ansicolor (~> 1.0.5)
|
16
|
+
daemons (1.1.0)
|
17
|
+
diff-lcs (1.1.2)
|
18
|
+
gherkin (2.2.9)
|
19
|
+
json (~> 1.4.6)
|
20
|
+
term-ansicolor (~> 1.0.5)
|
21
|
+
git (1.2.5)
|
22
|
+
jeweler (1.5.1)
|
23
|
+
bundler (~> 1.0.0)
|
24
|
+
git (>= 1.2.5)
|
25
|
+
rake
|
26
|
+
json (1.4.6)
|
27
|
+
json_pure (1.4.6)
|
28
|
+
mocha (0.9.8)
|
29
|
+
rake
|
30
|
+
net-http-persistent (1.4.1)
|
31
|
+
rake (0.8.7)
|
32
|
+
redis (2.1.1)
|
33
|
+
rspec (2.1.0)
|
34
|
+
rspec-core (~> 2.1.0)
|
35
|
+
rspec-expectations (~> 2.1.0)
|
36
|
+
rspec-mocks (~> 2.1.0)
|
37
|
+
rspec-core (2.1.0)
|
38
|
+
rspec-expectations (2.1.0)
|
39
|
+
diff-lcs (~> 1.1.2)
|
40
|
+
rspec-mocks (2.1.0)
|
41
|
+
term-ansicolor (1.0.5)
|
42
|
+
webmock (1.6.1)
|
43
|
+
addressable (>= 2.2.2)
|
44
|
+
crack (>= 0.1.7)
|
45
|
+
|
46
|
+
PLATFORMS
|
47
|
+
ruby
|
48
|
+
|
49
|
+
DEPENDENCIES
|
50
|
+
SystemTimer (~> 1.2.1)
|
51
|
+
bourne
|
52
|
+
cucumber
|
53
|
+
daemons (~> 1.1.0)
|
54
|
+
jeweler
|
55
|
+
json_pure (~> 1.4.6)
|
56
|
+
net-http-persistent (~> 1.4.1)
|
57
|
+
redis (~> 2.1.1)
|
58
|
+
rspec
|
59
|
+
webmock
|
data/{LICENSE.txt → MIT-LICENSE}
RENAMED
File without changes
|
data/Rakefile
CHANGED
@@ -12,15 +12,6 @@ Jeweler::Tasks.new do |gem|
|
|
12
12
|
gem.summary = gem.description = %Q{daikon, a radishapp.com client}
|
13
13
|
gem.email = "nick@quaran.to"
|
14
14
|
gem.authors = ["Nick Quaranto"]
|
15
|
-
|
16
|
-
gem.add_runtime_dependency "daemons", "~> 1.0.0"
|
17
|
-
gem.add_runtime_dependency "redis", "~> 2.1.1"
|
18
|
-
gem.add_runtime_dependency "system_timer", "= 1.0"
|
19
|
-
|
20
|
-
gem.add_development_dependency "rspec", "~> 2.1.0"
|
21
|
-
gem.add_development_dependency "cucumber", ">= 0"
|
22
|
-
gem.add_development_dependency "bundler", "~> 1.0.0"
|
23
|
-
gem.add_development_dependency "jeweler", "~> 1.5.1"
|
24
15
|
end
|
25
16
|
Jeweler::RubygemsDotOrgTasks.new
|
26
17
|
|
data/daikon.gemspec
CHANGED
@@ -5,23 +5,24 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{daikon}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.1.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Nick Quaranto"]
|
12
|
-
s.date = %q{2010-11-
|
12
|
+
s.date = %q{2010-11-21}
|
13
13
|
s.default_executable = %q{daikon}
|
14
14
|
s.description = %q{daikon, a radishapp.com client}
|
15
15
|
s.email = %q{nick@quaran.to}
|
16
16
|
s.executables = ["daikon"]
|
17
17
|
s.extra_rdoc_files = [
|
18
|
-
"LICENSE.txt",
|
19
18
|
"README.rdoc"
|
20
19
|
]
|
21
20
|
s.files = [
|
22
21
|
".document",
|
23
22
|
".rspec",
|
24
|
-
"
|
23
|
+
"Gemfile",
|
24
|
+
"Gemfile.lock",
|
25
|
+
"MIT-LICENSE",
|
25
26
|
"README.rdoc",
|
26
27
|
"Rakefile",
|
27
28
|
"VERSION",
|
@@ -57,30 +58,39 @@ Gem::Specification.new do |s|
|
|
57
58
|
s.specification_version = 3
|
58
59
|
|
59
60
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
60
|
-
s.add_runtime_dependency(%q<daemons>, ["~> 1.
|
61
|
+
s.add_runtime_dependency(%q<daemons>, ["~> 1.1.0"])
|
62
|
+
s.add_runtime_dependency(%q<json_pure>, ["~> 1.4.6"])
|
63
|
+
s.add_runtime_dependency(%q<net-http-persistent>, ["~> 1.4.1"])
|
61
64
|
s.add_runtime_dependency(%q<redis>, ["~> 2.1.1"])
|
62
|
-
s.add_runtime_dependency(%q<
|
63
|
-
s.add_development_dependency(%q<rspec>, ["
|
65
|
+
s.add_runtime_dependency(%q<SystemTimer>, ["~> 1.2.1"])
|
66
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
64
67
|
s.add_development_dependency(%q<cucumber>, [">= 0"])
|
65
|
-
s.add_development_dependency(%q<
|
66
|
-
s.add_development_dependency(%q<
|
68
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
69
|
+
s.add_development_dependency(%q<bourne>, [">= 0"])
|
70
|
+
s.add_development_dependency(%q<webmock>, [">= 0"])
|
67
71
|
else
|
68
|
-
s.add_dependency(%q<daemons>, ["~> 1.
|
72
|
+
s.add_dependency(%q<daemons>, ["~> 1.1.0"])
|
73
|
+
s.add_dependency(%q<json_pure>, ["~> 1.4.6"])
|
74
|
+
s.add_dependency(%q<net-http-persistent>, ["~> 1.4.1"])
|
69
75
|
s.add_dependency(%q<redis>, ["~> 2.1.1"])
|
70
|
-
s.add_dependency(%q<
|
71
|
-
s.add_dependency(%q<rspec>, ["
|
76
|
+
s.add_dependency(%q<SystemTimer>, ["~> 1.2.1"])
|
77
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
72
78
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
73
|
-
s.add_dependency(%q<
|
74
|
-
s.add_dependency(%q<
|
79
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
80
|
+
s.add_dependency(%q<bourne>, [">= 0"])
|
81
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
75
82
|
end
|
76
83
|
else
|
77
|
-
s.add_dependency(%q<daemons>, ["~> 1.
|
84
|
+
s.add_dependency(%q<daemons>, ["~> 1.1.0"])
|
85
|
+
s.add_dependency(%q<json_pure>, ["~> 1.4.6"])
|
86
|
+
s.add_dependency(%q<net-http-persistent>, ["~> 1.4.1"])
|
78
87
|
s.add_dependency(%q<redis>, ["~> 2.1.1"])
|
79
|
-
s.add_dependency(%q<
|
80
|
-
s.add_dependency(%q<rspec>, ["
|
88
|
+
s.add_dependency(%q<SystemTimer>, ["~> 1.2.1"])
|
89
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
81
90
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
82
|
-
s.add_dependency(%q<
|
83
|
-
s.add_dependency(%q<
|
91
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
92
|
+
s.add_dependency(%q<bourne>, [">= 0"])
|
93
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
84
94
|
end
|
85
95
|
end
|
86
96
|
|
data/lib/daikon.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
require 'stringio'
|
2
3
|
require 'logger'
|
3
4
|
require 'shellwords'
|
4
5
|
require 'socket'
|
5
6
|
|
7
|
+
require 'system_timer'
|
6
8
|
require 'daemons'
|
9
|
+
require 'json'
|
10
|
+
require 'net/http/persistent'
|
7
11
|
require 'redis'
|
8
12
|
|
9
13
|
__DIR__ = File.dirname(__FILE__)
|
@@ -18,5 +22,5 @@ require 'daikon/client'
|
|
18
22
|
require 'daikon/daemon'
|
19
23
|
|
20
24
|
module Daikon
|
21
|
-
VERSION = "0.
|
25
|
+
VERSION = "0.1.1"
|
22
26
|
end
|
data/lib/daikon/client.rb
CHANGED
@@ -2,26 +2,90 @@ module Daikon
|
|
2
2
|
class Client
|
3
3
|
include NamespaceTools
|
4
4
|
|
5
|
-
|
5
|
+
EXCEPTIONS = [Timeout::Error,
|
6
|
+
Errno::EINVAL,
|
7
|
+
Errno::ECONNRESET,
|
8
|
+
EOFError,
|
9
|
+
Net::HTTPBadResponse,
|
10
|
+
Net::HTTPHeaderSyntaxError,
|
11
|
+
Net::ProtocolError,
|
12
|
+
Net::HTTP::Persistent::Error,
|
13
|
+
JSON::ParserError]
|
14
|
+
|
15
|
+
attr_accessor :redis, :logger, :config, :http, :monitor
|
16
|
+
|
17
|
+
def setup(config, logger = nil)
|
18
|
+
self.config = config
|
19
|
+
self.logger = logger
|
20
|
+
self.redis = Redis.new(:port => config.redis_port)
|
21
|
+
self.http = Net::HTTP::Persistent.new
|
22
|
+
http.headers['Authorization'] = config.api_key
|
23
|
+
|
24
|
+
log "Started Daikon v#{VERSION}"
|
25
|
+
end
|
6
26
|
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
27
|
+
def start_monitor
|
28
|
+
self.monitor = StringIO.new
|
29
|
+
Thread.new do
|
30
|
+
Redis.new(:port => config.redis_port).monitor do |line|
|
31
|
+
monitor.puts line
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
11
35
|
|
12
|
-
|
36
|
+
def log(message)
|
37
|
+
logger.info message if logger
|
38
|
+
end
|
39
|
+
|
40
|
+
def http_request(method, url)
|
41
|
+
request_uri = URI.parse("#{config.server_prefix}/#{url}")
|
42
|
+
request_method = Net::HTTP.const_get method.to_s.capitalize
|
43
|
+
request = request_method.new request_uri.path
|
44
|
+
|
45
|
+
yield request if block_given?
|
46
|
+
|
47
|
+
log "#{method.to_s.upcase} #{request_uri}"
|
48
|
+
http.request request_uri, request
|
13
49
|
end
|
14
50
|
|
15
51
|
def fetch_commands
|
16
|
-
|
52
|
+
raw_commands = http_request(:get, "api/v1/commands.json")
|
53
|
+
commands = JSON.parse(raw_commands.body)
|
54
|
+
|
55
|
+
commands.each do |id, command|
|
56
|
+
result = evaluate_redis(command)
|
57
|
+
|
58
|
+
http_request(:put, "api/v1/commands/#{id}.json") do |request|
|
59
|
+
request.body = result.to_json
|
60
|
+
request.add_field "Content-Length", request.body.size.to_s
|
61
|
+
request.add_field "Content-Type", "application/json"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
rescue *EXCEPTIONS => ex
|
65
|
+
log ex.to_s
|
17
66
|
end
|
18
67
|
|
19
|
-
def
|
20
|
-
|
68
|
+
def report_info
|
69
|
+
http_request(:post, "api/v1/info.json") do |request|
|
70
|
+
request.body = redis.info.to_json
|
71
|
+
request.add_field "Content-Length", request.body.size.to_s
|
72
|
+
request.add_field "Content-Type", "application/json"
|
73
|
+
end
|
74
|
+
rescue *EXCEPTIONS => ex
|
75
|
+
log ex.to_s
|
21
76
|
end
|
22
77
|
|
23
78
|
def rotate_monitor
|
24
|
-
|
79
|
+
monitor_data = monitor.string
|
80
|
+
monitor.reopen(StringIO.new)
|
81
|
+
|
82
|
+
http_request(:post, "api/v1/monitor") do |request|
|
83
|
+
request.body = Gem.gzip(monitor_data)
|
84
|
+
request.add_field "Content-Length", request.body.size.to_s
|
85
|
+
request.add_field "Content-Type", "application/x-gzip"
|
86
|
+
end
|
87
|
+
rescue *EXCEPTIONS => ex
|
88
|
+
log ex.to_s
|
25
89
|
end
|
26
90
|
|
27
91
|
def evaluate_redis(command)
|
@@ -32,16 +96,16 @@ module Daikon
|
|
32
96
|
rescue Exception => e
|
33
97
|
STDERR.puts e.message
|
34
98
|
e.backtrace.each {|bt| STDERR.puts bt}
|
35
|
-
return { "
|
99
|
+
return { "response" => e.message }
|
36
100
|
end
|
37
|
-
return { "
|
101
|
+
return { "response" => "No command received." } unless argv[0]
|
38
102
|
|
39
103
|
begin
|
40
104
|
{ "response" => execute_redis(argv) }
|
41
105
|
rescue Exception => e
|
42
106
|
STDERR.puts e.message
|
43
107
|
e.backtrace.each {|bt| STDERR.puts bt}
|
44
|
-
{ "
|
108
|
+
{ "response" => e.message }
|
45
109
|
end
|
46
110
|
end
|
47
111
|
|
@@ -53,47 +117,13 @@ module Daikon
|
|
53
117
|
# Apply the current namespace to any fields that need it.
|
54
118
|
argv = namespace_input(namespace, *argv)
|
55
119
|
|
56
|
-
|
57
|
-
raise "I'm sorry, I don't recognize that command. #{help}" unless argv.kind_of? Array
|
58
|
-
|
59
|
-
if result = bypass(argv)
|
60
|
-
result
|
61
|
-
else
|
62
|
-
# Send the command to Redis.
|
63
|
-
result = redis.send(*argv)
|
64
|
-
|
65
|
-
# Remove the namespace from any commands that return a key.
|
66
|
-
denamespace_output namespace, argv.first, result
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def bypass(argv)
|
71
|
-
queue = "transactions-#{namespace}"
|
120
|
+
raise "Not a Redis command." unless argv.kind_of? Array
|
72
121
|
|
73
|
-
|
74
|
-
|
75
|
-
redis.rpush queue, argv.to_json
|
76
|
-
return "OK"
|
77
|
-
elsif redis.llen(queue).to_i >= 1
|
78
|
-
redis.rpush queue, argv.to_json
|
122
|
+
# Send the command to Redis.
|
123
|
+
result = redis.send(*argv)
|
79
124
|
|
80
|
-
|
81
|
-
|
82
|
-
redis.del queue
|
83
|
-
|
84
|
-
return commands.map do |c|
|
85
|
-
cmd = JSON.parse(c)
|
86
|
-
|
87
|
-
# Send the command to Redis.
|
88
|
-
result = redis.send(*cmd)
|
89
|
-
|
90
|
-
# Remove the namespace from any commands that return a key.
|
91
|
-
denamespace_output namespace, cmd.first, result
|
92
|
-
end.last
|
93
|
-
end
|
94
|
-
|
95
|
-
return "QUEUED"
|
96
|
-
end
|
125
|
+
# Remove the namespace from any commands that return a key.
|
126
|
+
denamespace_output namespace, argv.first, result
|
97
127
|
end
|
98
128
|
end
|
99
129
|
end
|
data/lib/daikon/configuration.rb
CHANGED
@@ -2,11 +2,11 @@ module Daikon
|
|
2
2
|
class Configuration
|
3
3
|
FLAGS = %w[-p -k -f -s]
|
4
4
|
OPTIONS = %w[redis_port api_key field_id server_prefix]
|
5
|
-
DEFAULTS = %w[6379 1234567890 1 radishapp.com]
|
5
|
+
DEFAULTS = %w[6379 1234567890 1 https://radishapp.com]
|
6
6
|
|
7
7
|
attr_accessor *OPTIONS
|
8
8
|
|
9
|
-
def initialize(argv)
|
9
|
+
def initialize(argv = [])
|
10
10
|
FLAGS.each_with_index do |flag, flag_index|
|
11
11
|
argv_index = argv.index(flag)
|
12
12
|
value = if argv_index
|
@@ -17,6 +17,10 @@ module Daikon
|
|
17
17
|
|
18
18
|
send "#{OPTIONS[flag_index]}=", value
|
19
19
|
end
|
20
|
+
|
21
|
+
if api_key == DEFAULTS[1] && argv.any? { |arg| arg =~ /start|run/ }
|
22
|
+
abort "Must supply an api key to start the daemon.\nExample: daikon start #{FLAGS[1]} #{DEFAULTS[1]}"
|
23
|
+
end
|
20
24
|
end
|
21
25
|
end
|
22
26
|
end
|
data/lib/daikon/daemon.rb
CHANGED
@@ -1,26 +1,27 @@
|
|
1
1
|
module Daikon
|
2
2
|
class Daemon
|
3
3
|
def self.start
|
4
|
-
|
4
|
+
config = Daikon::Configuration.new(ARGV)
|
5
|
+
|
6
|
+
Daemons.run_proc("daikon", :log_output => true, :backtrace => true) do
|
5
7
|
if ARGV.include?("run")
|
6
8
|
logger = Logger.new(STDOUT)
|
7
9
|
else
|
8
10
|
logger = Logger.new("/tmp/radish.log")
|
9
11
|
end
|
10
12
|
|
11
|
-
config = Daikon::Configuration.new(ARGV)
|
12
|
-
client = Daikon::Client.new(config, logger)
|
13
13
|
count = 0
|
14
|
-
|
15
|
-
|
14
|
+
client = Daikon::Client.new
|
15
|
+
client.setup(config, logger)
|
16
|
+
client.start_monitor
|
16
17
|
|
17
18
|
loop do
|
18
|
-
|
19
|
-
|
20
|
-
if count % 5 == 4
|
21
|
-
client.send_info
|
19
|
+
if count % 5 == 0
|
20
|
+
client.report_info
|
22
21
|
end
|
23
22
|
|
23
|
+
client.fetch_commands
|
24
|
+
|
24
25
|
if count % 10 == 9
|
25
26
|
client.rotate_monitor
|
26
27
|
end
|
data/spec/client_spec.rb
CHANGED
@@ -1,5 +1,211 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Daikon::Client do
|
3
|
+
describe Daikon::Client, "setup" do
|
4
|
+
subject { Daikon::Client.new }
|
5
|
+
let(:logger) { Logger.new(nil) }
|
6
|
+
let(:redis) { 'redis instance' }
|
4
7
|
|
8
|
+
before do
|
9
|
+
Redis.stubs(:new => redis)
|
10
|
+
subject.stubs(:redis=)
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with defaults" do
|
14
|
+
let(:config) { Daikon::Configuration.new(%w[-p 1234]) }
|
15
|
+
|
16
|
+
before do
|
17
|
+
subject.setup(config, logger)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "sets redis to listen on the given port" do
|
21
|
+
Redis.should have_received(:new).with(:port => "1234")
|
22
|
+
subject.should have_received(:redis=).with(redis)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "with overrides" do
|
27
|
+
let(:config) { Daikon::Configuration.new([]) }
|
28
|
+
|
29
|
+
before do
|
30
|
+
subject.setup(config, logger)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "sets redis to listen on the given port" do
|
34
|
+
Redis.should have_received(:new).with(:port => "6379")
|
35
|
+
subject.should have_received(:redis=).with(redis)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
shared_examples_for "a command api consumer" do
|
41
|
+
it "sends a request for commands" do
|
42
|
+
WebMock.should have_requested(:get, "#{server}/api/v1/commands.json").
|
43
|
+
with(:headers => {'Authorization' => api_key})
|
44
|
+
end
|
45
|
+
|
46
|
+
it "processes each command" do
|
47
|
+
subject.should have_received(:evaluate_redis).with("INCR foo")
|
48
|
+
subject.should have_received(:evaluate_redis).with("DECR foo")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "shoots the results back to radish" do
|
52
|
+
results = {"response" => "9999"}.to_json
|
53
|
+
|
54
|
+
headers = {
|
55
|
+
"Authorization" => api_key,
|
56
|
+
"Content-Length" => results.size.to_s,
|
57
|
+
"Content-Type" => "application/json"
|
58
|
+
}
|
59
|
+
|
60
|
+
WebMock.should have_requested(:put, "#{server}/api/v1/commands/42.json").
|
61
|
+
with(:body => results, :headers => headers)
|
62
|
+
|
63
|
+
WebMock.should have_requested(:put, "#{server}/api/v1/commands/43.json").
|
64
|
+
with(:body => results, :headers => headers)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe Daikon::Client, "fetching commands" do
|
69
|
+
subject { Daikon::Client.new }
|
70
|
+
let(:body) { {"42" => "INCR foo", "43" => "DECR foo"}.to_json }
|
71
|
+
|
72
|
+
before do
|
73
|
+
subject.stubs(:evaluate_redis => {"response" => "9999"})
|
74
|
+
stub_request(:get, "#{server}/api/v1/commands.json").to_return(:body => body)
|
75
|
+
stub_request(:put, %r{#{server}/api/v1/commands/\d+\.json})
|
76
|
+
|
77
|
+
subject.setup(config)
|
78
|
+
subject.fetch_commands
|
79
|
+
end
|
80
|
+
|
81
|
+
context "with default configuration" do
|
82
|
+
let(:api_key) { config.api_key }
|
83
|
+
let(:server) { "https://radishapp.com" }
|
84
|
+
let(:config) { Daikon::Configuration.new([]) }
|
85
|
+
|
86
|
+
it_should_behave_like "a command api consumer"
|
87
|
+
end
|
88
|
+
|
89
|
+
context "with custom settings" do
|
90
|
+
let(:api_key) { "0987654321" }
|
91
|
+
let(:server) { "http://localhost:9999" }
|
92
|
+
let(:config) { Daikon::Configuration.new(["-k", api_key, "-s", "http://localhost:9999"]) }
|
93
|
+
|
94
|
+
it_should_behave_like "a command api consumer"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe Daikon::Client, "when server is down" do
|
99
|
+
subject { Daikon::Client.new }
|
100
|
+
before do
|
101
|
+
subject.setup(Daikon::Configuration.new)
|
102
|
+
WebMock.stub_request(:any, /#{subject.config.server_prefix}.*/).to_raise(Timeout::Error)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "does not commit suicide" do
|
106
|
+
lambda {
|
107
|
+
subject.fetch_commands
|
108
|
+
}.should_not raise_error
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe Daikon::Client, "when it returns bad json" do
|
113
|
+
subject { Daikon::Client.new }
|
114
|
+
before do
|
115
|
+
subject.setup(Daikon::Configuration.new)
|
116
|
+
WebMock.stub_request(:any, /#{subject.config.server_prefix}.*/).to_return(:body => "{'bad':'json}")
|
117
|
+
end
|
118
|
+
|
119
|
+
it "does not commit suicide" do
|
120
|
+
lambda {
|
121
|
+
subject.fetch_commands
|
122
|
+
}.should_not raise_error
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
shared_examples_for "a info api consumer" do
|
127
|
+
it "shoots the results back to radish" do
|
128
|
+
|
129
|
+
headers = {
|
130
|
+
"Authorization" => api_key,
|
131
|
+
"Content-Length" => results.to_json.size.to_s,
|
132
|
+
"Content-Type" => "application/json"
|
133
|
+
}
|
134
|
+
|
135
|
+
WebMock.should have_requested(:post, "#{server}/api/v1/info.json").
|
136
|
+
with(:body => results.to_json, :headers => headers)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe Daikon::Client, "report info" do
|
141
|
+
subject { Daikon::Client.new }
|
142
|
+
let(:results) { {"connected_clients"=>"1", "used_cpu_sys_childrens"=>"0.00"}.to_json }
|
143
|
+
let(:redis) { stub("redis instance", :info => results) }
|
144
|
+
|
145
|
+
before do
|
146
|
+
stub_request(:post, "#{server}/api/v1/info.json")
|
147
|
+
subject.stubs(:redis => redis)
|
148
|
+
subject.setup(config)
|
149
|
+
subject.report_info
|
150
|
+
end
|
151
|
+
|
152
|
+
context "with default configuration" do
|
153
|
+
let(:api_key) { config.api_key }
|
154
|
+
let(:server) { "https://radishapp.com" }
|
155
|
+
let(:config) { Daikon::Configuration.new }
|
156
|
+
|
157
|
+
it_should_behave_like "a info api consumer"
|
158
|
+
end
|
159
|
+
|
160
|
+
context "with custom settings" do
|
161
|
+
let(:api_key) { "0987654321" }
|
162
|
+
let(:server) { "http://localhost:9999" }
|
163
|
+
let(:config) { Daikon::Configuration.new(["-k", api_key, "-s", "http://localhost:9999"]) }
|
164
|
+
|
165
|
+
it_should_behave_like "a info api consumer"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
shared_examples_for "a monitor api consumer" do
|
170
|
+
it "shoots the results back to radish" do
|
171
|
+
zipped_data = Gem.gzip(results)
|
172
|
+
|
173
|
+
headers = {
|
174
|
+
"Authorization" => api_key,
|
175
|
+
"Content-Length" => zipped_data.size,
|
176
|
+
"Content-Type" => "application/x-gzip"
|
177
|
+
}
|
178
|
+
|
179
|
+
WebMock.should have_requested(:post, "#{server}/api/v1/monitor").
|
180
|
+
with(:body => zipped_data, :headers => headers)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe Daikon::Client, "rotate monitor" do
|
185
|
+
subject { Daikon::Client.new }
|
186
|
+
let(:results) { %{1290289048.96581 "info"\n1290289053.568815 "info"} }
|
187
|
+
let(:redis) { stub("redis instance", :info => results) }
|
188
|
+
|
189
|
+
before do
|
190
|
+
stub_request(:post, "#{server}/api/v1/monitor")
|
191
|
+
subject.monitor = StringIO.new(results)
|
192
|
+
subject.setup(config)
|
193
|
+
subject.rotate_monitor
|
194
|
+
end
|
195
|
+
|
196
|
+
context "with default configuration" do
|
197
|
+
let(:api_key) { config.api_key }
|
198
|
+
let(:server) { "https://radishapp.com" }
|
199
|
+
let(:config) { Daikon::Configuration.new }
|
200
|
+
|
201
|
+
it_should_behave_like "a monitor api consumer"
|
202
|
+
end
|
203
|
+
|
204
|
+
context "with custom settings" do
|
205
|
+
let(:api_key) { "0987654321" }
|
206
|
+
let(:server) { "http://localhost:9999" }
|
207
|
+
let(:config) { Daikon::Configuration.new(["-k", api_key, "-s", "http://localhost:9999"]) }
|
208
|
+
|
209
|
+
it_should_behave_like "a monitor api consumer"
|
210
|
+
end
|
5
211
|
end
|
data/spec/configuration_spec.rb
CHANGED
@@ -13,7 +13,7 @@ describe Daikon::Configuration do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
describe Daikon::Configuration do
|
16
|
-
subject { Daikon::Configuration.new([]) }
|
16
|
+
subject { Daikon::Configuration.new(%w[-k 1234567890]) }
|
17
17
|
|
18
18
|
it "uses the default keys" do
|
19
19
|
subject.redis_port.should == "6379"
|
@@ -23,6 +23,24 @@ describe Daikon::Configuration do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
describe Daikon::Configuration do
|
27
|
+
%w[start run restart].each do |command|
|
28
|
+
it "raises an error if no api key provided when booting daemon with #{command}" do
|
29
|
+
capture do
|
30
|
+
lambda {
|
31
|
+
Daikon::Configuration.new([command])
|
32
|
+
}.should raise_error(SystemExit)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "raises no errors on other commands" do
|
38
|
+
lambda {
|
39
|
+
Daikon::Configuration.new(["stop"])
|
40
|
+
}.should_not raise_error
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
26
44
|
describe Daikon::Configuration do
|
27
45
|
subject { Daikon::Configuration.new(flags) }
|
28
46
|
let(:flags) { %w[-p 9001 -k deadbeef] }
|
data/spec/spec_helper.rb
CHANGED
@@ -7,6 +7,21 @@ require 'daikon'
|
|
7
7
|
# in ./support/ and its subdirectories.
|
8
8
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
9
|
|
10
|
+
require 'bourne'
|
11
|
+
|
12
|
+
require 'webmock/rspec'
|
13
|
+
WebMock.disable_net_connect!
|
14
|
+
|
10
15
|
RSpec.configure do |config|
|
11
|
-
|
16
|
+
config.mock_with :mocha
|
17
|
+
end
|
18
|
+
|
19
|
+
# http://pivotallabs.com/users/alex/blog/articles/853-capturing-standard-out-in-unit-tests
|
20
|
+
def capture
|
21
|
+
output = StringIO.new
|
22
|
+
$stderr = output
|
23
|
+
yield
|
24
|
+
output.string
|
25
|
+
ensure
|
26
|
+
$stderr = STDERR
|
12
27
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: daikon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Nick Quaranto
|
@@ -15,76 +15,94 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-11-
|
18
|
+
date: 2010-11-21 00:00:00 -05:00
|
19
19
|
default_executable: daikon
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
name: daemons
|
23
22
|
prerelease: false
|
24
|
-
|
23
|
+
type: :runtime
|
24
|
+
name: daemons
|
25
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
25
26
|
none: false
|
26
27
|
requirements:
|
27
28
|
- - ~>
|
28
29
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
30
|
+
hash: 19
|
30
31
|
segments:
|
31
32
|
- 1
|
33
|
+
- 1
|
32
34
|
- 0
|
33
|
-
|
34
|
-
|
35
|
+
version: 1.1.0
|
36
|
+
requirement: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
prerelease: false
|
35
39
|
type: :runtime
|
36
|
-
|
40
|
+
name: json_pure
|
41
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 11
|
47
|
+
segments:
|
48
|
+
- 1
|
49
|
+
- 4
|
50
|
+
- 6
|
51
|
+
version: 1.4.6
|
52
|
+
requirement: *id002
|
37
53
|
- !ruby/object:Gem::Dependency
|
38
|
-
name: redis
|
39
54
|
prerelease: false
|
40
|
-
|
55
|
+
type: :runtime
|
56
|
+
name: net-http-persistent
|
57
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
41
58
|
none: false
|
42
59
|
requirements:
|
43
60
|
- - ~>
|
44
61
|
- !ruby/object:Gem::Version
|
45
|
-
hash:
|
62
|
+
hash: 5
|
46
63
|
segments:
|
47
|
-
- 2
|
48
64
|
- 1
|
65
|
+
- 4
|
49
66
|
- 1
|
50
|
-
version:
|
51
|
-
|
52
|
-
version_requirements: *id002
|
67
|
+
version: 1.4.1
|
68
|
+
requirement: *id003
|
53
69
|
- !ruby/object:Gem::Dependency
|
54
|
-
name: system_timer
|
55
70
|
prerelease: false
|
56
|
-
|
71
|
+
type: :runtime
|
72
|
+
name: redis
|
73
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
57
74
|
none: false
|
58
75
|
requirements:
|
59
|
-
- -
|
76
|
+
- - ~>
|
60
77
|
- !ruby/object:Gem::Version
|
61
|
-
hash:
|
78
|
+
hash: 9
|
62
79
|
segments:
|
80
|
+
- 2
|
63
81
|
- 1
|
64
|
-
-
|
65
|
-
version:
|
66
|
-
|
67
|
-
version_requirements: *id003
|
82
|
+
- 1
|
83
|
+
version: 2.1.1
|
84
|
+
requirement: *id004
|
68
85
|
- !ruby/object:Gem::Dependency
|
69
|
-
name: rspec
|
70
86
|
prerelease: false
|
71
|
-
|
87
|
+
type: :runtime
|
88
|
+
name: SystemTimer
|
89
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
72
90
|
none: false
|
73
91
|
requirements:
|
74
92
|
- - ~>
|
75
93
|
- !ruby/object:Gem::Version
|
76
|
-
hash:
|
94
|
+
hash: 29
|
77
95
|
segments:
|
96
|
+
- 1
|
78
97
|
- 2
|
79
98
|
- 1
|
80
|
-
|
81
|
-
|
82
|
-
type: :development
|
83
|
-
version_requirements: *id004
|
99
|
+
version: 1.2.1
|
100
|
+
requirement: *id005
|
84
101
|
- !ruby/object:Gem::Dependency
|
85
|
-
name: cucumber
|
86
102
|
prerelease: false
|
87
|
-
|
103
|
+
type: :development
|
104
|
+
name: rspec
|
105
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
88
106
|
none: false
|
89
107
|
requirements:
|
90
108
|
- - ">="
|
@@ -93,40 +111,63 @@ dependencies:
|
|
93
111
|
segments:
|
94
112
|
- 0
|
95
113
|
version: "0"
|
96
|
-
|
97
|
-
version_requirements: *id005
|
114
|
+
requirement: *id006
|
98
115
|
- !ruby/object:Gem::Dependency
|
99
|
-
name: bundler
|
100
116
|
prerelease: false
|
101
|
-
|
117
|
+
type: :development
|
118
|
+
name: cucumber
|
119
|
+
version_requirements: &id007 !ruby/object:Gem::Requirement
|
102
120
|
none: false
|
103
121
|
requirements:
|
104
|
-
- -
|
122
|
+
- - ">="
|
105
123
|
- !ruby/object:Gem::Version
|
106
|
-
hash:
|
124
|
+
hash: 3
|
107
125
|
segments:
|
108
|
-
- 1
|
109
|
-
- 0
|
110
126
|
- 0
|
111
|
-
version:
|
112
|
-
|
113
|
-
version_requirements: *id006
|
127
|
+
version: "0"
|
128
|
+
requirement: *id007
|
114
129
|
- !ruby/object:Gem::Dependency
|
130
|
+
prerelease: false
|
131
|
+
type: :development
|
115
132
|
name: jeweler
|
133
|
+
version_requirements: &id008 !ruby/object:Gem::Requirement
|
134
|
+
none: false
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
hash: 3
|
139
|
+
segments:
|
140
|
+
- 0
|
141
|
+
version: "0"
|
142
|
+
requirement: *id008
|
143
|
+
- !ruby/object:Gem::Dependency
|
116
144
|
prerelease: false
|
117
|
-
|
145
|
+
type: :development
|
146
|
+
name: bourne
|
147
|
+
version_requirements: &id009 !ruby/object:Gem::Requirement
|
118
148
|
none: false
|
119
149
|
requirements:
|
120
|
-
- -
|
150
|
+
- - ">="
|
121
151
|
- !ruby/object:Gem::Version
|
122
|
-
hash:
|
152
|
+
hash: 3
|
123
153
|
segments:
|
124
|
-
-
|
125
|
-
|
126
|
-
|
127
|
-
|
154
|
+
- 0
|
155
|
+
version: "0"
|
156
|
+
requirement: *id009
|
157
|
+
- !ruby/object:Gem::Dependency
|
158
|
+
prerelease: false
|
128
159
|
type: :development
|
129
|
-
|
160
|
+
name: webmock
|
161
|
+
version_requirements: &id010 !ruby/object:Gem::Requirement
|
162
|
+
none: false
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
hash: 3
|
167
|
+
segments:
|
168
|
+
- 0
|
169
|
+
version: "0"
|
170
|
+
requirement: *id010
|
130
171
|
description: daikon, a radishapp.com client
|
131
172
|
email: nick@quaran.to
|
132
173
|
executables:
|
@@ -134,12 +175,13 @@ executables:
|
|
134
175
|
extensions: []
|
135
176
|
|
136
177
|
extra_rdoc_files:
|
137
|
-
- LICENSE.txt
|
138
178
|
- README.rdoc
|
139
179
|
files:
|
140
180
|
- .document
|
141
181
|
- .rspec
|
142
|
-
-
|
182
|
+
- Gemfile
|
183
|
+
- Gemfile.lock
|
184
|
+
- MIT-LICENSE
|
143
185
|
- README.rdoc
|
144
186
|
- Rakefile
|
145
187
|
- VERSION
|