discover 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6e952beaea5ffcb83ae577c9b95a19c05c595eba
4
+ data.tar.gz: dd8c6d2cc4c03a078e84024f14ff50695f3138e7
5
+ SHA512:
6
+ metadata.gz: 3f74fd998d606f0ea0aebecb4220cb9f05d7f138e47d34bb3bc88af5b23c37410f1330ef8e5c63d8660b852e01c50baa030ebbec1c7ffe96ff75b4b367fcdbcd
7
+ data.tar.gz: 601a492a7bc6bf57ada768f1b0c614d26c3616cfa94113ef758a840b962e062931d82c4635bdbfc5bc978e6c530735e2113df9febd8a0e173279c01f4cc50846
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ Dockerfile
data/.travis.yml ADDED
@@ -0,0 +1,33 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1.0
7
+ - rbx
8
+
9
+ before_install:
10
+ - export GOPATH=$HOME/gopath
11
+ - export PATH=$HOME/gopath/bin:$PATH
12
+ - go get github.com/kr/godep
13
+ - wget https://github.com/coreos/etcd/releases/download/v0.3.0/etcd-v0.3.0-linux-amd64.tar.gz
14
+ - tar xzf etcd-v0.3.0-linux-amd64.tar.gz
15
+ - mv etcd-v0.3.0-linux-amd64/etcd $GOPATH/bin
16
+ - git clone -b master https://github.com/flynn/discoverd.git $GOPATH/src/github.com/flynn/discoverd
17
+ - pushd $GOPATH/src/github.com/flynn/discoverd
18
+ - godep go install
19
+ - popd
20
+
21
+ env:
22
+ - "HAS_DEPENDENCIES=true"
23
+
24
+ notifications:
25
+ irc:
26
+ channels:
27
+ - "chat.freenode.net#flynn"
28
+ use_notice: true
29
+ skip_join: true
30
+ on_success: change
31
+ on_failure: always
32
+ template:
33
+ - "%{repository}/%{branch} - %{commit}: %{message} %{build_url}"
data/CONTRIBUTING.md ADDED
@@ -0,0 +1 @@
1
+ See the [Flynn contributing guide](https://flynn.io/docs/contributing)
data/Dockerfile.base ADDED
@@ -0,0 +1,48 @@
1
+ FROM ubuntu:precise
2
+
3
+ # Set DEBIAN_FRONTEND to non-interactive to avoid useless warnings
4
+ ENV DEBIAN_FRONTEND noninteractive
5
+
6
+ # Update apt sources
7
+ RUN apt-get update
8
+
9
+ # Support adding PPAs
10
+ RUN apt-get install -y python-software-properties
11
+
12
+ # Install git
13
+ RUN add-apt-repository -y ppa:git-core/ppa && \
14
+ apt-get update && \
15
+ apt-get install -y git
16
+
17
+ # Install Go
18
+ RUN add-apt-repository -y ppa:tsuru/golang && \
19
+ apt-get update && \
20
+ apt-get install -y golang
21
+
22
+ # Install Ruby
23
+ RUN apt-get install -y ruby1.9.3
24
+
25
+ # Set the GOPATH
26
+ ENV GOPATH /go
27
+
28
+ # Install etcd v0.3.0
29
+ RUN apt-get install -y wget && \
30
+ wget -O - -q "https://github.com/coreos/etcd/releases/download/v0.3.0/etcd-v0.3.0-linux-amd64.tar.gz" | \
31
+ tar xz --strip-components=1 -C /usr/local/bin
32
+
33
+ # Install godep (requires mercurial)
34
+ RUN apt-get install -y mercurial && \
35
+ go get github.com/kr/godep
36
+
37
+ # Clone and build discoverd
38
+ RUN git clone -b master https://github.com/flynn/discoverd.git /go/src/github.com/flynn/discoverd && \
39
+ cd /go/src/github.com/flynn/discoverd && \
40
+ /go/bin/godep go install
41
+
42
+ # Add ruby-discoverd
43
+ ADD . /ruby-discoverd
44
+
45
+ # Bundle the gems (needs build tools)
46
+ RUN apt-get install -y build-essential && \
47
+ gem install --no-ri --no-rdoc bundler && \
48
+ bundle install --gemfile /ruby-discoverd/Gemfile
data/Dockerfile.test ADDED
@@ -0,0 +1,7 @@
1
+ FROM ruby-discoverd-base
2
+
3
+ # Add ruby-discoverd
4
+ ADD . /ruby-discoverd
5
+
6
+ # Run etcd, discoverd then the integration tests
7
+ CMD bash -c "cd /ruby-discoverd && PATH=\${PATH}:/go/bin bundle exec rake test:integration"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in discover.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ Flynn is a trademark of Apollic Software, LLC.
2
+
3
+ Copyright (c) 2013-2014 Apollic Software, LLC. All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are
7
+ met:
8
+
9
+ * Redistributions of source code must retain the above copyright
10
+ notice, this list of conditions and the following disclaimer.
11
+ * Redistributions in binary form must reproduce the above
12
+ copyright notice, this list of conditions and the following disclaimer
13
+ in the documentation and/or other materials provided with the
14
+ distribution.
15
+ * Neither the name of Apollic Software, LLC nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/MAINTAINERS ADDED
@@ -0,0 +1,2 @@
1
+ Jonathan Rudenberg <jonathan@titanous.com> (github: titanous)
2
+ Lewis Marshall <lewis@lmars.net> (github: lmars)
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Discover
2
+
3
+ [![Build Status](https://travis-ci.org/flynn/ruby-discoverd.png?branch=master)](https://travis-ci.org/flynn/ruby-discoverd)
4
+
5
+ A [discoverd](https://github.com/flynn/discoverd) client for Ruby.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'discover'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install discover
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( http://github.com/<my-github-username>/discover/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,79 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc "Run all tests by default"
4
+ task :default => "test:integration"
5
+
6
+ namespace :test do
7
+ # If HAS_DEPENDENCIES is set then we just run the tests, otherwise we spin up a
8
+ # Docker container with all the dependencies installed and run the tests inside that
9
+ if ENV["HAS_DEPENDENCIES"]
10
+ require "rake/testtask"
11
+
12
+ Rake::TestTask.new(:integration) do |t|
13
+ t.libs << "test"
14
+ t.pattern = "test/integration/test_*.rb"
15
+ end
16
+ else
17
+ desc "Run the integration tests in a Docker container"
18
+ task :integration => :build_image do
19
+ command = []
20
+ command << "docker run -i -t"
21
+ command << "-e HAS_DEPENDENCIES=true"
22
+
23
+ # Pass through any env vars beginning with TEST
24
+ ENV.select { |k,v| k =~ /^TEST/ }.each_pair do |key, val|
25
+ command << %{-e "#{key}"="#{val}"}
26
+ end
27
+
28
+ command << "ruby-discoverd-test"
29
+
30
+ exec command.join(" ")
31
+ end
32
+
33
+ desc "Build the Docker image for running tests"
34
+ task :build_image => [:docker, "tmp/.build_base_image", :build_test_image]
35
+
36
+ # This task builds the base Docker image which has etcd, discoverd & gem dependencies.
37
+ #
38
+ # We use a file task as we only want to rebuild if the base dependencies change (so we
39
+ # don't have to install those dependencies every time we run the tests)
40
+ BASE_IMAGE_FILE_DEPENDENCIES = %w(
41
+ Dockerfile.base
42
+ Gemfile
43
+ Gemfile.lock
44
+ discover.gemspec
45
+ )
46
+ file "tmp/.build_base_image" => BASE_IMAGE_FILE_DEPENDENCIES do
47
+ FileUtils.ln_sf "Dockerfile.base", "Dockerfile"
48
+
49
+ unless system "docker build -rm=true -t ruby-discoverd-base ."
50
+ fail "failed to build the test Docker image, exiting"
51
+ end
52
+
53
+ FileUtils.touch "tmp/.build_base_image"
54
+ end
55
+
56
+ # The test image gets rebuilt every time we run the tests to ensure
57
+ # we are testing the correct code
58
+ task :build_test_image do
59
+ FileUtils.ln_sf "Dockerfile.test", "Dockerfile"
60
+
61
+ unless system "docker build -rm=true -t ruby-discoverd-test ."
62
+ fail "failed to build the test Docker image, exiting"
63
+ end
64
+ end
65
+
66
+ task :docker do
67
+ unless system("docker version >/dev/null")
68
+ fail "Docker is required to run the integration tests, but it is not available. Exiting"
69
+ end
70
+ end
71
+
72
+ def fail(msg)
73
+ $stderr.puts "*** ERROR ***"
74
+ $stderr.puts msg
75
+ $stderr.puts "*************"
76
+ exit 1
77
+ end
78
+ end
79
+ end
data/discover.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'discover/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "discover"
8
+ spec.version = Discover::VERSION
9
+ spec.authors = ["Jonathan Rudenberg", "Lewis Marshall"]
10
+ spec.email = ["jonathan@titanous.com", "lewis@lmars.net"]
11
+ spec.summary = %q{A discoverd client for Ruby}
12
+ spec.description = %q{A discoverd client for Ruby}
13
+ spec.homepage = ""
14
+ spec.license = "BSD"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'celluloid'
22
+ spec.add_dependency 'rpcplus'
23
+ spec.add_development_dependency "bundler"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "minitest", ">= 5.2.3"
26
+ end
data/example.rb ADDED
@@ -0,0 +1,11 @@
1
+ client = Discover::Client.new
2
+
3
+ client.register("name", 1111, "127.0.0.1")
4
+
5
+ service = client.service("foo")
6
+ service.online
7
+ service.addrs
8
+
9
+ service.each_event do |update|
10
+
11
+ end
@@ -0,0 +1,3 @@
1
+ module Discover
2
+ VERSION = "0.0.1"
3
+ end
data/lib/discover.rb ADDED
@@ -0,0 +1,260 @@
1
+ require 'discover/version'
2
+ require "rpcplus"
3
+
4
+ module Discover
5
+ class Client
6
+ include Celluloid
7
+
8
+ def initialize(host='127.0.0.1', port=1111)
9
+ @client = RPCPlus::Client.new(host, port)
10
+ @registrations = []
11
+ end
12
+
13
+ def request(*args, &block)
14
+ @client.request(*args, &block)
15
+ end
16
+
17
+ def service(name, filters={})
18
+ Service.new(self, name, filters)
19
+ end
20
+
21
+ def register(name, port=nil, ip=nil, attributes={})
22
+ _register(name, port, ip, attributes, false)
23
+ end
24
+
25
+ def register_and_standby(name, port=nil, ip=nil, attributes={})
26
+ _register(name, port, ip, attributes, true)
27
+ end
28
+
29
+ def remove_registration(reg)
30
+ @registrations.delete(reg)
31
+ end
32
+
33
+ def unregister_all
34
+ @registrations.each(&:unregister)
35
+ end
36
+
37
+ private
38
+ def _register(name, port=nil, ip=nil, attributes={}, standby=false)
39
+ reg = Registration.new(self, name, "#{ip}:#{port}", attributes, standby)
40
+ @registrations << reg
41
+ reg.register
42
+ reg
43
+ end
44
+ end
45
+
46
+ class Registration
47
+ include Celluloid
48
+
49
+ HEARTBEAT_INTERVAL = 5
50
+
51
+ def initialize(client, name, address, attributes = {}, standby = false)
52
+ @client = client
53
+ @name = name
54
+ @address = address
55
+ @attributes = attributes
56
+ @standby = standby
57
+ end
58
+
59
+ def register
60
+ send_register_request
61
+ start_heartbeat
62
+ wait_for_election if @standby
63
+ end
64
+
65
+ def unregister
66
+ stop_heartbeat
67
+ send_unregister_request
68
+ @client.remove_registration(self)
69
+ end
70
+
71
+ def send_register_request
72
+ args = {
73
+ "Name" => @name,
74
+ "Addr" => @address,
75
+ "Attrs" => @attributes
76
+ }
77
+
78
+ @client.request("Agent.Register", args).value
79
+ end
80
+
81
+ def send_unregister_request
82
+ args = {
83
+ "Name" => @name,
84
+ "Addr" => @address
85
+ }
86
+
87
+ @client.request("Agent.Unregister", args).value
88
+ end
89
+
90
+ def start_heartbeat
91
+ @heartbeat = every(HEARTBEAT_INTERVAL) do
92
+ @client.request(
93
+ "Agent.Heartbeat",
94
+ "Name" => @name,
95
+ "Addr" => @address
96
+ )
97
+ end
98
+ end
99
+
100
+ def stop_heartbeat
101
+ @heartbeat.cancel
102
+ end
103
+
104
+ def wait_for_election
105
+ async.watch_leaders
106
+ wait :elected
107
+ end
108
+
109
+ def watch_leaders
110
+ @client.service(@name).each_leader do |leader|
111
+ if leader.address == @address
112
+ signal :elected
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ class Service
119
+ include Celluloid
120
+
121
+ class Update < Struct.new(:address, :attributes, :created, :name, :online)
122
+ def self.from_hash(hash)
123
+ new *hash.values_at("Addr", "Attrs", "Created", "Name", "Online")
124
+ end
125
+
126
+ def attributes
127
+ super || {}
128
+ end
129
+
130
+ def online?
131
+ online == true
132
+ end
133
+
134
+ def offline?
135
+ !online?
136
+ end
137
+
138
+ # The sentinel update marks the end of existing updates from discoverd
139
+ def sentinel?
140
+ address.empty? && name.empty?
141
+ end
142
+ end
143
+
144
+ class Watcher
145
+ include Celluloid
146
+
147
+ def initialize(block)
148
+ @block = block
149
+ @condition = Condition.new
150
+ end
151
+
152
+ def notify(update)
153
+ @block.call update
154
+ end
155
+
156
+ def done
157
+ @condition.broadcast
158
+ end
159
+
160
+ def wait
161
+ @condition.wait
162
+ end
163
+ end
164
+
165
+ def initialize(client, name, filters={})
166
+ @client = client
167
+ @name = name
168
+ @filters = filters
169
+ @current = Condition.new
170
+ @instances = {}
171
+ @watchers = []
172
+ async.process_updates
173
+ end
174
+
175
+ def online
176
+ @current.wait if @current
177
+ @instances.values
178
+ end
179
+
180
+ def leader
181
+ online.sort_by(&:created).first
182
+ end
183
+
184
+ def each_leader(&block)
185
+ leader = self.leader
186
+ block.call leader if leader
187
+
188
+ each_update(false) do |update|
189
+ if leader.nil? || (update.offline? && leader && update.address == leader.address)
190
+ leader = self.leader
191
+ block.call leader if leader
192
+ end
193
+ end
194
+ end
195
+
196
+ def each_update(include_current = true, &block)
197
+ # Since updates are coming from a Proc being called in a different
198
+ # Actor (the RPCClient), we need to suspend update notifications
199
+ # here to avoid race conditions where we could potentially miss
200
+ # updates between initializing the Watcher and adding it to @watchers
201
+ watcher = pause_updates do
202
+ watcher = Watcher.new(block)
203
+
204
+ if include_current
205
+ online.each { |u| watcher.notify u }
206
+ end
207
+
208
+ @watchers << watcher
209
+
210
+ watcher
211
+ end
212
+
213
+ watcher.wait
214
+ end
215
+
216
+ private
217
+
218
+ def process_updates
219
+ @client.request('Agent.Subscribe', {'Name' => @name}) do |update|
220
+ update = Update.from_hash(update)
221
+
222
+ if @current && update.sentinel?
223
+ c, @current = @current, nil
224
+ c.broadcast
225
+ next
226
+ end
227
+
228
+ if matches_filters?(update)
229
+ if update.online?
230
+ @instances[update.address] = update
231
+ else
232
+ @instances.delete(update.address)
233
+ end
234
+
235
+ @pause_updates.wait if @pause_updates
236
+ @watchers.each { |w| w.notify update }
237
+ end
238
+ end
239
+ @watchers.each(&:done)
240
+ # TODO: handle disconnect
241
+ end
242
+
243
+ def matches_filters?(update)
244
+ @filters.all? do |key, val|
245
+ update.attributes[key] == val
246
+ end
247
+ end
248
+
249
+ def pause_updates(&block)
250
+ @pause_updates = Condition.new
251
+
252
+ result = block.call
253
+
254
+ c, @pause_updates = @pause_updates, nil
255
+ c.broadcast
256
+
257
+ result
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,128 @@
1
+ require "test_helper"
2
+
3
+ class TestRegistration < DiscoverIntegrationTest
4
+ class TestRegisterStandby
5
+ include Celluloid
6
+
7
+ def initialize(client, name, port, ip)
8
+ @client = client
9
+ @name = name
10
+ @port = port
11
+ @ip = ip
12
+ @elected = false
13
+
14
+ async.register_and_standby
15
+ end
16
+
17
+ def elected?
18
+ @elected
19
+ end
20
+
21
+ def register_and_standby
22
+ @client.register_and_standby(@name, @port, @ip)
23
+
24
+ @elected = true
25
+ end
26
+ end
27
+
28
+ def test_service_is_online_after_registration
29
+ name = "foo"
30
+ port = 1111
31
+ ip = "127.0.0.1"
32
+ attributes = { "foo" => "bar" }
33
+
34
+ @client.register name, port, ip, attributes
35
+
36
+ service = @client.service(name)
37
+ assert_equal 1, service.online.size
38
+
39
+ instance = service.online.first
40
+ assert_equal name, instance.name
41
+ assert_equal "#{ip}:#{port}", instance.address
42
+ assert_equal attributes, instance.attributes
43
+ assert instance.online?
44
+
45
+ sleep(11)
46
+ assert_equal 1, service.online.size
47
+ end
48
+
49
+ def test_service_is_offline_after_unregister
50
+ name = "foo"
51
+ port = 1111
52
+ ip = "127.0.0.1"
53
+
54
+ registration = @client.register name, port, ip
55
+
56
+ service = @client.service(name)
57
+ assert_equal 1, service.online.size
58
+
59
+ registration.unregister
60
+
61
+ service = @client.service(name)
62
+ assert_equal 0, service.online.size
63
+ end
64
+
65
+ def test_changing_service_attributes
66
+ name = "foo"
67
+ port = 1111
68
+ ip = "127.0.0.1"
69
+ attributes = { "foo" => "bar" }
70
+
71
+ @client.register name, port, ip, attributes
72
+
73
+ service = @client.service(name)
74
+ assert_equal 1, service.online.size
75
+
76
+ instance = service.online.first
77
+ assert_equal attributes, instance.attributes
78
+
79
+ new_attributes = { "foo" => "baz" }
80
+ @client.register name, port, ip, new_attributes
81
+
82
+ service = @client.service(name)
83
+ assert_equal 1, service.online.size
84
+
85
+ instance = service.online.first
86
+ assert_equal new_attributes, instance.attributes
87
+ end
88
+
89
+ def test_service_with_filters
90
+ name = "foo"
91
+ ip = "127.0.0.1"
92
+
93
+ matching_attributes = { "foo" => "bar", "baz" => "qux" }
94
+ non_matching_attributes = { "foo" => "baz", "baz" => "qux" }
95
+
96
+ @client.register name, 1111, ip, matching_attributes
97
+ @client.register name, 2222, ip, non_matching_attributes
98
+
99
+ service = @client.service(name)
100
+ assert_equal 2, service.online.size
101
+
102
+ filtered_service = @client.service(name, "foo" => "bar")
103
+ assert_equal 1, filtered_service.online.size
104
+
105
+ instance = filtered_service.online.first
106
+ assert_equal matching_attributes, instance.attributes
107
+ end
108
+
109
+ def test_register_and_standby
110
+ name = "foo"
111
+ ip = "127.0.0.1"
112
+
113
+ registrations = []
114
+ registrations << @client.register(name, 1111, ip)
115
+
116
+ standby = TestRegisterStandby.new @client, name, 2222, ip
117
+ sleep(0.2)
118
+ assert !standby.elected?
119
+
120
+ registrations << @client.register(name, 3333, ip)
121
+ sleep(0.2)
122
+ assert !standby.elected?
123
+
124
+ registrations.each(&:unregister)
125
+ sleep(0.2)
126
+ assert standby.elected?
127
+ end
128
+ end
@@ -0,0 +1,78 @@
1
+ require "test_helper"
2
+
3
+ class TestServiceLeader < DiscoverIntegrationTest
4
+ class TestLeaderWatcher
5
+ include Celluloid
6
+
7
+ attr_reader :leader_updates
8
+
9
+ def initialize(service)
10
+ @service = service
11
+ @leader_updates = []
12
+
13
+ async.watch
14
+ end
15
+
16
+ def watch
17
+ @service.each_leader do |leader|
18
+ @leader_updates.push leader
19
+ end
20
+ end
21
+ end
22
+
23
+ def test_leader_is_oldest_online_service
24
+ name = "foo"
25
+ ip = "127.0.0.1"
26
+
27
+ service = @client.service(name)
28
+ assert_nil service.leader
29
+
30
+ registrations = []
31
+ registrations << @client.register(name, 1111, ip)
32
+ assert_equal "#{ip}:1111", service.leader.address
33
+
34
+ registrations << @client.register(name, 2222, ip)
35
+ registrations << @client.register(name, 3333, ip)
36
+ sleep(0.2)
37
+ assert_equal "#{ip}:1111", service.leader.address
38
+
39
+ registrations.shift.unregister
40
+ sleep(0.2)
41
+ assert_equal "#{ip}:2222", service.leader.address
42
+
43
+ registrations.each(&:unregister)
44
+ sleep(0.2)
45
+ assert_nil service.leader
46
+ end
47
+
48
+ def test_leader_changes
49
+ name = "foo"
50
+ ip = "127.0.0.1"
51
+
52
+ service = @client.service(name)
53
+ watcher = TestLeaderWatcher.new(service)
54
+ assert_equal 0, watcher.leader_updates.size
55
+
56
+ registrations = []
57
+ registrations << @client.register(name, 1111, ip)
58
+ sleep(0.2)
59
+ assert_equal 1, watcher.leader_updates.size
60
+ assert_equal "#{ip}:1111", watcher.leader_updates.last.address
61
+
62
+ registrations << @client.register(name, 2222, ip)
63
+ registrations << @client.register(name, 3333, ip)
64
+ sleep(0.2)
65
+ assert_equal 1, watcher.leader_updates.size
66
+ assert_equal "#{ip}:1111", watcher.leader_updates.last.address
67
+
68
+ registrations.shift.unregister
69
+ sleep(0.2)
70
+ assert_equal 2, watcher.leader_updates.size
71
+ assert_equal "#{ip}:2222", watcher.leader_updates.last.address
72
+
73
+ registrations.each(&:unregister)
74
+ sleep(0.2)
75
+ assert_equal 3, watcher.leader_updates.size
76
+ assert_equal "#{ip}:3333", watcher.leader_updates.last.address
77
+ end
78
+ end
@@ -0,0 +1,44 @@
1
+ require "test_helper"
2
+
3
+ class TestServiceUpdates < DiscoverIntegrationTest
4
+ class TestServiceWatcher
5
+ include Celluloid
6
+
7
+ attr_reader :updates
8
+
9
+ def initialize(service)
10
+ @service = service
11
+ @updates = []
12
+ async.watch
13
+ end
14
+
15
+ def watch
16
+ @service.each_update do |update|
17
+ @updates.push update
18
+ end
19
+ end
20
+ end
21
+
22
+ def test_registration_triggers_updates
23
+ name = "foo"
24
+ ip = "127.0.0.1"
25
+
26
+ service = @client.service(name)
27
+ watcher = TestServiceWatcher.new(service)
28
+
29
+ @client.register name, 1111, ip
30
+ sleep(0.2)
31
+ assert_equal 1, watcher.updates.size
32
+ assert_equal "#{ip}:1111", watcher.updates.last.address
33
+
34
+ @client.register name, 2222, ip
35
+ sleep(0.2)
36
+ assert_equal 2, watcher.updates.size
37
+ assert_equal "#{ip}:2222", watcher.updates.last.address
38
+
39
+ @client.register name, 1111, ip, { "foo" => "bar" }
40
+ sleep(0.2)
41
+ assert_equal 3, watcher.updates.size
42
+ assert_equal "#{ip}:1111", watcher.updates.last.address
43
+ end
44
+ end
@@ -0,0 +1,22 @@
1
+ require "securerandom"
2
+
3
+ class DiscoverIntegrationTest < Minitest::Test
4
+ def setup
5
+ etcd_name = SecureRandom.hex
6
+ @etcd_pid = spawn("etcd -name #{etcd_name}", [:out, :err] => "/dev/null")
7
+ sleep(0.2)
8
+
9
+ @discoverd_pid = spawn("discoverd", [:out, :err] => "/dev/null")
10
+ sleep(0.2)
11
+
12
+ @client = Discover::Client.new
13
+ end
14
+
15
+ def teardown
16
+ @client.unregister_all
17
+ sleep(0.2)
18
+ Process.kill("TERM", @discoverd_pid)
19
+ Process.kill("TERM", @etcd_pid)
20
+ Process.waitall
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ # Since both Celluloid and Minitest use at_exit hooks, we need to ensure
2
+ # that the Minitest hook runs before the Celluloid one (so Celluloid has
3
+ # not shutdown when the tests run), so we need to require Minitest *after*
4
+ # Celluloid (as at_exit hooks are called in reverse order)
5
+ require "discover"
6
+ require "minitest/autorun"
7
+
8
+ # Only log Celluloid errors
9
+ Celluloid.logger.level = Logger::ERROR
10
+
11
+ require "support/discover_integration_test"
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: discover
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Rudenberg
8
+ - Lewis Marshall
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-02-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: celluloid
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - '>='
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rpcplus
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: bundler
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rake
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: minitest
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '>='
75
+ - !ruby/object:Gem::Version
76
+ version: 5.2.3
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '>='
82
+ - !ruby/object:Gem::Version
83
+ version: 5.2.3
84
+ description: A discoverd client for Ruby
85
+ email:
86
+ - jonathan@titanous.com
87
+ - lewis@lmars.net
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - .gitignore
93
+ - .travis.yml
94
+ - CONTRIBUTING.md
95
+ - Dockerfile.base
96
+ - Dockerfile.test
97
+ - Gemfile
98
+ - LICENSE
99
+ - MAINTAINERS
100
+ - README.md
101
+ - Rakefile
102
+ - discover.gemspec
103
+ - example.rb
104
+ - lib/discover.rb
105
+ - lib/discover/version.rb
106
+ - test/integration/test_registration.rb
107
+ - test/integration/test_service_leader.rb
108
+ - test/integration/test_service_updates.rb
109
+ - test/support/discover_integration_test.rb
110
+ - test/test_helper.rb
111
+ homepage: ''
112
+ licenses:
113
+ - BSD
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - '>='
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.0.14
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: A discoverd client for Ruby
135
+ test_files:
136
+ - test/integration/test_registration.rb
137
+ - test/integration/test_service_leader.rb
138
+ - test/integration/test_service_updates.rb
139
+ - test/support/discover_integration_test.rb
140
+ - test/test_helper.rb