lark 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.
- data/README.markdown +22 -0
- data/Rakefile +23 -0
- data/VERSION +1 -0
- data/lib/lark.rb +142 -0
- data/spec/init.rb +17 -0
- data/spec/lark_spec.rb +31 -0
- metadata +88 -0
data/README.markdown
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Lark
|
2
|
+
|
3
|
+
Lark is a simple system to store local cache in a cluster of redis servers, be
|
4
|
+
able to query state about other lark nodes, and notice when one goes offline.
|
5
|
+
|
6
|
+
Lark.on_expire do |id|
|
7
|
+
Log.debug "Lost node #{id}"
|
8
|
+
end
|
9
|
+
|
10
|
+
EM.start do
|
11
|
+
lark = Lark.new "bottom_node.1", :expire => 60
|
12
|
+
lark.set "ip" => "127.0.0.1", "role" => "worker node"
|
13
|
+
EM::PeriodicTimer(5) do
|
14
|
+
lark.set "load" => get_load, "mem_usage" => get_mem_usage
|
15
|
+
end
|
16
|
+
|
17
|
+
on_some_event do
|
18
|
+
lark.find(/^top_node/).each do |id, data|
|
19
|
+
check_on_node(id, data)
|
20
|
+
do
|
21
|
+
end
|
22
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'jeweler'
|
2
|
+
|
3
|
+
Jeweler::Tasks.new do |s|
|
4
|
+
s.name = "lark"
|
5
|
+
s.description = "A system for nodes to communicate state and presence through a redis cluster"
|
6
|
+
s.summary = s.description
|
7
|
+
s.author = "Orion Henry"
|
8
|
+
s.email = "orion@heroku.com"
|
9
|
+
s.homepage = "http://github.com/orionz/lark"
|
10
|
+
s.rubyforge_project = "lark"
|
11
|
+
s.files = FileList["[A-Z]*", "{lib,spec}/**/*"]
|
12
|
+
s.add_dependency "redis", [">= 2.0.5"]
|
13
|
+
end
|
14
|
+
|
15
|
+
Jeweler::RubyforgeTasks.new
|
16
|
+
|
17
|
+
desc 'Run specs'
|
18
|
+
task :spec do
|
19
|
+
sh 'bacon -s spec/*_spec.rb'
|
20
|
+
end
|
21
|
+
|
22
|
+
task :default => :spec
|
23
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/lark.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
class LarkDBOffline < Exception ; end
|
4
|
+
|
5
|
+
class Lark
|
6
|
+
attr_accessor :id
|
7
|
+
|
8
|
+
def self.urls
|
9
|
+
if ENV['LARK_URLS']
|
10
|
+
ENV['LARK_URLS'].split(",")
|
11
|
+
elsif ENV['LARK_URL']
|
12
|
+
[ ENV['LARK_URL'] ]
|
13
|
+
else
|
14
|
+
[ "redis://127.0.0.1:6379/0" ]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.dbs
|
19
|
+
@dbs ||= urls.map do |url|
|
20
|
+
puts "Connecting to #{url}";
|
21
|
+
Redis.connect(:url => url)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
## all the black magic happens here
|
26
|
+
## pool runs the block against all the db's
|
27
|
+
## throws an error if non of them are online
|
28
|
+
## and returns an array of the block results
|
29
|
+
## from the ones who are online
|
30
|
+
|
31
|
+
def self.pool(&blk)
|
32
|
+
dbs.push dbs.shift
|
33
|
+
|
34
|
+
num_errors = 0
|
35
|
+
results = []
|
36
|
+
begin
|
37
|
+
dbs.each do |db|
|
38
|
+
begin
|
39
|
+
results << blk.call(db)
|
40
|
+
rescue Errno::ECONNREFUSED, Timeout::Error => e
|
41
|
+
puts "#{e.class}: #{e.message}"
|
42
|
+
num_errors += 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
ensure
|
46
|
+
raise LarkDBOffline if num_errors == dbs.size
|
47
|
+
end
|
48
|
+
results
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.on_expired(&blk)
|
52
|
+
@expired = blk;
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.expired
|
56
|
+
@expired
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize(_id, options = {})
|
60
|
+
@id = _id
|
61
|
+
@options = options
|
62
|
+
@domain = options[:domain]
|
63
|
+
@expire = options[:expire]
|
64
|
+
end
|
65
|
+
|
66
|
+
def load_data
|
67
|
+
## try each db - return when one works without an error
|
68
|
+
## throw an error if all are down
|
69
|
+
pool do |db|
|
70
|
+
data = db.hgetall(key)
|
71
|
+
return data if not data.empty?
|
72
|
+
end
|
73
|
+
{}
|
74
|
+
end
|
75
|
+
|
76
|
+
def key
|
77
|
+
"#{@domain}:#{@id}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_key
|
81
|
+
"#{@domain}:lark:all"
|
82
|
+
end
|
83
|
+
|
84
|
+
def set(new_data)
|
85
|
+
data.merge!(new_data)
|
86
|
+
save_data
|
87
|
+
end
|
88
|
+
|
89
|
+
def save_data
|
90
|
+
data[:created_on] ||= Time.new.to_i
|
91
|
+
data[:domain] ||= @domain
|
92
|
+
data[:updated_on] = Time.new.to_i
|
93
|
+
pool do |db|
|
94
|
+
db.multi do
|
95
|
+
db.hmset *[ key, data.to_a ].flatten
|
96
|
+
db.expire key, @expire if @expire
|
97
|
+
db.sadd set_key, @id
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def data
|
103
|
+
@data ||= load_data
|
104
|
+
end
|
105
|
+
|
106
|
+
def destroy
|
107
|
+
pool do |db|
|
108
|
+
db.multi do
|
109
|
+
db.del key
|
110
|
+
db.srem set_key, id
|
111
|
+
end
|
112
|
+
end
|
113
|
+
self.class.expired.call(id, @domain);
|
114
|
+
end
|
115
|
+
|
116
|
+
def all_ids
|
117
|
+
pool { |db| db.smembers(set_key) }.flatten.uniq.sort
|
118
|
+
end
|
119
|
+
|
120
|
+
def find_ids(match)
|
121
|
+
all_ids.select { |_id| match.match(_id) }
|
122
|
+
end
|
123
|
+
|
124
|
+
def find_valid(match)
|
125
|
+
all = find_ids(match).map { |_id| Lark.new(_id, @options) }
|
126
|
+
valid = all.reject { |l| l.data.empty? }
|
127
|
+
invalid = all - valid
|
128
|
+
invalid.each { |i| i.destroy }
|
129
|
+
valid
|
130
|
+
end
|
131
|
+
|
132
|
+
def find(match = //)
|
133
|
+
result = {}
|
134
|
+
find_valid(match).each { |v| result[v.id] = v.data }
|
135
|
+
result
|
136
|
+
end
|
137
|
+
|
138
|
+
def pool(&blk)
|
139
|
+
self.class.pool(&blk)
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
data/spec/init.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
require File.dirname(__FILE__) + "/../lib/lark"
|
3
|
+
require "bacon"
|
4
|
+
require "mocha/api"
|
5
|
+
require "mocha/object"
|
6
|
+
|
7
|
+
class Bacon::Context
|
8
|
+
include Mocha::API
|
9
|
+
alias_method :old_it,:it
|
10
|
+
def it description,&block
|
11
|
+
mocha_setup
|
12
|
+
old_it(description,&block)
|
13
|
+
mocha_verify
|
14
|
+
mocha_teardown
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
data/spec/lark_spec.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
require File.dirname(__FILE__) + "/init.rb"
|
3
|
+
|
4
|
+
describe 'Lark' do
|
5
|
+
before do
|
6
|
+
@lark = Lark.new "lark", :domain => "domain"
|
7
|
+
@db1 = mock()
|
8
|
+
@db2 = mock()
|
9
|
+
Lark.stubs(:dbs).returns([@db1, @db2]);
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should locate the data even if the key and data are on different databases' do
|
13
|
+
@db1.expects(:smembers).returns([ "key1" ])
|
14
|
+
@db1.expects(:hgetall).with("domain:key1").returns({})
|
15
|
+
@db2.expects(:smembers).returns([ ])
|
16
|
+
@db2.expects(:hgetall).with("domain:key1").returns({"foo" => "bar"})
|
17
|
+
|
18
|
+
@lark.find.should.equal({ "key1" => {"foo" => "bar"} })
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should get the union of the ids on each server' do
|
22
|
+
@db1.expects(:smembers).returns([ "a", "b" ])
|
23
|
+
@db2.expects(:smembers).returns([ "b", "c" ])
|
24
|
+
@lark.all_ids.should.equal ["a","b","c"]
|
25
|
+
end
|
26
|
+
|
27
|
+
# it 'should run delete on old objects'
|
28
|
+
# it 'should invoke a handler when deleting old objects'
|
29
|
+
# it 'should fetch data properly when one redis is offline'
|
30
|
+
# it 'should raise an exception if all redis are offline'
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lark
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Orion Henry
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-21 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: redis
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 5
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 0
|
33
|
+
- 5
|
34
|
+
version: 2.0.5
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description: A system for nodes to communicate state and presence through a redis cluster
|
38
|
+
email: orion@heroku.com
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files:
|
44
|
+
- README.markdown
|
45
|
+
files:
|
46
|
+
- README.markdown
|
47
|
+
- Rakefile
|
48
|
+
- VERSION
|
49
|
+
- lib/lark.rb
|
50
|
+
- spec/init.rb
|
51
|
+
- spec/lark_spec.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://github.com/orionz/lark
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --charset=UTF-8
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
hash: 3
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 3
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
requirements: []
|
80
|
+
|
81
|
+
rubyforge_project: lark
|
82
|
+
rubygems_version: 1.3.7
|
83
|
+
signing_key:
|
84
|
+
specification_version: 3
|
85
|
+
summary: A system for nodes to communicate state and presence through a redis cluster
|
86
|
+
test_files:
|
87
|
+
- spec/init.rb
|
88
|
+
- spec/lark_spec.rb
|