discover 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.
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