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 +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +33 -0
- data/CONTRIBUTING.md +1 -0
- data/Dockerfile.base +48 -0
- data/Dockerfile.test +7 -0
- data/Gemfile +4 -0
- data/LICENSE +29 -0
- data/MAINTAINERS +2 -0
- data/README.md +31 -0
- data/Rakefile +79 -0
- data/discover.gemspec +26 -0
- data/example.rb +11 -0
- data/lib/discover/version.rb +3 -0
- data/lib/discover.rb +260 -0
- data/test/integration/test_registration.rb +128 -0
- data/test/integration/test_service_leader.rb +78 -0
- data/test/integration/test_service_updates.rb +44 -0
- data/test/support/discover_integration_test.rb +22 -0
- data/test/test_helper.rb +11 -0
- metadata +140 -0
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
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
data/Gemfile
ADDED
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
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Discover
|
2
|
+
|
3
|
+
[](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
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
|
data/test/test_helper.rb
ADDED
@@ -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
|