lark 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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