srvy 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/.coveralls.yml +1 -0
- data/.gitignore +18 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +50 -0
- data/Rakefile +10 -0
- data/lib/lru_redux/has_key_monkey_patch.rb +10 -0
- data/lib/net/dns/ext/resolver_nameserver_monkey_patch.rb +36 -0
- data/lib/srvy.rb +10 -0
- data/lib/srvy/formatters.rb +23 -0
- data/lib/srvy/formatters/base.rb +17 -0
- data/lib/srvy/formatters/dalli.rb +11 -0
- data/lib/srvy/formatters/host_port.rb +11 -0
- data/lib/srvy/host.rb +18 -0
- data/lib/srvy/resolver.rb +61 -0
- data/lib/srvy/result.rb +61 -0
- data/lib/srvy/version.rb +3 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/srvy/formatters/base_spec.rb +10 -0
- data/spec/srvy/formatters/dalli_spec.rb +21 -0
- data/spec/srvy/formatters/host_port_spec.rb +18 -0
- data/spec/srvy/formatters_spec.rb +40 -0
- data/spec/srvy/resolver_spec.rb +25 -0
- data/spec/srvy/result_spec.rb +178 -0
- data/srvy.gemspec +33 -0
- metadata +218 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 720038b1f82de00846abd452dcacb9e847c5467f
|
4
|
+
data.tar.gz: 4a6b6e85214b477add94f062b7a50c66272922fd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6e498289f089c7ae9fc73cc587db8cf6638d03c6dc1ad739a572fcf3901af34c82b3fe240b0922a6f9504e2139f52465a3d2a40c8a385bd2de14802a3de2f76a
|
7
|
+
data.tar.gz: 2f9579278a60f8b8e115238430516477a6d19ecc585a43a821943a325acbfa026a661d28fa975d9456af41a97971a8c8b9bbcd073f6fd4ba3f4f16b262e5c787
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Scott Fleckenstein
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Srvy
|
2
|
+
[](https://travis-ci.org/nullstyle/srvy)
|
3
|
+
[](https://coveralls.io/r/nullstyle/srvy)
|
4
|
+
[](https://codeclimate.com/github/nullstyle/srvy)
|
5
|
+
|
6
|
+
A rubygem to integrate SRV-based service discovery into your application
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'srvy'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install srvy
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
$srvy = Srvy::Resolver.new # default configuration, using the locally configured resolver
|
26
|
+
|
27
|
+
# picks a single server by randomized with weight in the highest priority group
|
28
|
+
$srvy.get_single("db-slaves.mydomain.com") # => "mysql01.mydomain.com:3306"
|
29
|
+
|
30
|
+
# to get all services in the highest priority group
|
31
|
+
$srvy.get_many("memcache.mydomain.com") # => ["memcache01.mydomain.com:11211", "memcache02.mydomain.com:11211"]
|
32
|
+
|
33
|
+
# get all records, including lower priority (e.g. backup) services
|
34
|
+
$srvy.get_all("db.mydomain.com") # => ["db01.mydomain.com:3306", "db01-failover.mydomain.com:3306"]
|
35
|
+
|
36
|
+
```
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
#TODO
|
41
|
+
|
42
|
+
I havent currently thought of a good api to signal to Srvy that you want to use lower priority services... currently the only way is to use `get_all` and pick the services out yourself.
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
1. Fork it
|
47
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
48
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
49
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
50
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'net/dns'
|
2
|
+
|
3
|
+
Net::DNS::Resolver.class_eval do
|
4
|
+
def nameservers=(arg)
|
5
|
+
@config[:nameservers] = convert_nameservers_arg_to_ips(arg)
|
6
|
+
@logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def convert_nameservers_arg_to_ips(arg)
|
11
|
+
case arg
|
12
|
+
when IPAddr ; [arg]
|
13
|
+
when String ;
|
14
|
+
begin
|
15
|
+
[IPAddr.new(arg)]
|
16
|
+
rescue ArgumentError # arg is in the name form, not IP
|
17
|
+
nameservers_from_name(arg)
|
18
|
+
end
|
19
|
+
when Array ;
|
20
|
+
arg.map{|x| convert_nameservers_arg_to_ips(x) }.flatten
|
21
|
+
else
|
22
|
+
raise ArgumentError, "Wrong argument format, neither String, Array nor IPAddr"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def nameservers_from_name(arg)
|
27
|
+
arr = []
|
28
|
+
arg.split(" ").each do |name|
|
29
|
+
Net::DNS::Resolver.new.search(name).each_address do |ip|
|
30
|
+
arr << ip
|
31
|
+
end
|
32
|
+
end
|
33
|
+
arr
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/srvy.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Srvy
|
2
|
+
module Formatters
|
3
|
+
autoload :Base, "srvy/formatters/base"
|
4
|
+
autoload :HostPort, "srvy/formatters/host_port"
|
5
|
+
autoload :Dalli, "srvy/formatters/dalli"
|
6
|
+
|
7
|
+
def self.from_name(name)
|
8
|
+
case name.to_sym
|
9
|
+
when :host_port ; HostPort.new
|
10
|
+
when :dalli ; Dalli.new
|
11
|
+
else ; raise ArgumentError, "Unknown formatter name: #{name}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.format_single(format_name, result)
|
16
|
+
from_name(format_name).format_single(result)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.format_many(format_name, result)
|
20
|
+
from_name(format_name).format_many(result)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Srvy
|
2
|
+
module Formatters
|
3
|
+
class Base
|
4
|
+
|
5
|
+
def format_single(host)
|
6
|
+
raise NotImplementedError
|
7
|
+
end
|
8
|
+
|
9
|
+
def format_many(hosts)
|
10
|
+
# default to mapping over the collection with the format_single form
|
11
|
+
# by having a separate format_many call, we can build formatters
|
12
|
+
# that aggregate results in some way (such as producing a map of the hosts)
|
13
|
+
hosts.map{|h| format_single h}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/srvy/host.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Srvy
|
2
|
+
class Host
|
3
|
+
|
4
|
+
attr_reader :host
|
5
|
+
attr_reader :port
|
6
|
+
attr_reader :weight
|
7
|
+
attr_reader :priority
|
8
|
+
attr_reader :ttl
|
9
|
+
|
10
|
+
def initialize(host, port, weight, priority, ttl)
|
11
|
+
@host = host
|
12
|
+
@port = port
|
13
|
+
@weight = weight
|
14
|
+
@priority = priority
|
15
|
+
@ttl = ttl
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "net/dns/ext/resolver_nameserver_monkey_patch"
|
2
|
+
require 'lru_redux/has_key_monkey_patch'
|
3
|
+
|
4
|
+
module Srvy
|
5
|
+
class Resolver
|
6
|
+
attr_reader :cache
|
7
|
+
|
8
|
+
def initialize(options={})
|
9
|
+
@dns = Net::DNS::Resolver.new
|
10
|
+
|
11
|
+
if options[:nameserver]
|
12
|
+
@dns.nameservers = options[:nameserver]
|
13
|
+
end
|
14
|
+
|
15
|
+
cache_size = options[:cache_size] || 100
|
16
|
+
@cache = LruRedux::Cache.new(cache_size) #cache of host -> result kv pairs
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
"#<Srvy::Resolver:#{object_id} dns=#{@dns.nameservers.inspect}>"
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_single(host, format=:host_port)
|
24
|
+
result = get(host).get_single
|
25
|
+
|
26
|
+
Srvy::Formatters.format_single(format, result)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_many(host, format=:host_port)
|
30
|
+
result = get(host).get_many
|
31
|
+
Srvy::Formatters.format_many(format, result)
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_all(host, format=:host_port)
|
35
|
+
result = get(host).get_all
|
36
|
+
Srvy::Formatters.format_many(format, result)
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_dns(host)
|
40
|
+
@dns.search(host, Net::DNS::SRV)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Gets the Srvy::Result from the cache or from dns.
|
44
|
+
#
|
45
|
+
# host - The String hostname to query for SRV records.
|
46
|
+
#
|
47
|
+
#
|
48
|
+
# Returns the Srvy::Result for that hostname
|
49
|
+
def get(host)
|
50
|
+
result = @cache[host]
|
51
|
+
|
52
|
+
if !result || result.expired?
|
53
|
+
result = Srvy::Result.from_dns get_dns(host)
|
54
|
+
@cache[host] = result
|
55
|
+
end
|
56
|
+
|
57
|
+
result
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
data/lib/srvy/result.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Srvy
|
2
|
+
class Result
|
3
|
+
EMPTY_RESULT_TTL = 10
|
4
|
+
|
5
|
+
def self.from_dns(dns_result)
|
6
|
+
# for each SRV record
|
7
|
+
srvs = dns_result.answer.select{|rr| rr.is_a?(Net::DNS::RR::SRV) }
|
8
|
+
hosts = srvs.map do |srv|
|
9
|
+
Srvy::Host.new(srv.host, srv.port, srv.weight, srv.priority, srv.ttl)
|
10
|
+
end
|
11
|
+
|
12
|
+
new(Time.now, hosts)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(created_at, hosts)
|
16
|
+
@created_at = created_at
|
17
|
+
@hosts = hosts
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_single
|
21
|
+
return nil if @hosts.empty?
|
22
|
+
|
23
|
+
roll = rand(best_priority_cumulative_weight)
|
24
|
+
acc = 0
|
25
|
+
|
26
|
+
best_priority_hosts_by_weight.each do |host|
|
27
|
+
acc += host.weight
|
28
|
+
return host if roll < acc
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_many
|
33
|
+
best_priority_hosts_by_weight
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_all
|
37
|
+
@hosts
|
38
|
+
end
|
39
|
+
|
40
|
+
def expired?
|
41
|
+
now = Time.now
|
42
|
+
min_ttl = @hosts.map(&:ttl).min
|
43
|
+
min_ttl ||= EMPTY_RESULT_TTL # in case there are no hosts
|
44
|
+
@created_at + min_ttl < now
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def best_priority_hosts_by_weight
|
49
|
+
# sorted weight descending
|
50
|
+
best_priority_hosts_by_weight ||= @hosts.select{|s| s.priority <= best_priority }.sort_by(&:weight).reverse
|
51
|
+
end
|
52
|
+
|
53
|
+
def best_priority_cumulative_weight
|
54
|
+
best_priority_hosts_by_weight.map(&:weight).inject(0, :+)
|
55
|
+
end
|
56
|
+
|
57
|
+
def best_priority
|
58
|
+
@best_priority ||= @hosts.map(&:priority).min
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/srvy/version.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Srvy::Formatters::Base, "format_single" do
|
4
|
+
Given(:host) { Srvy::Host.new("test01.host.com", 11211, 0, 0, 100) }
|
5
|
+
|
6
|
+
When(:result){ subject.format_single(host) }
|
7
|
+
|
8
|
+
Then { expect(result).to have_failed(NotImplementedError) }
|
9
|
+
end
|
10
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Srvy::Formatters::Dalli, "format_single" do
|
4
|
+
Given(:host) { Srvy::Host.new("test01.host.com", 11211, 10, 0, 100) }
|
5
|
+
|
6
|
+
When(:result){ subject.format_single(host) }
|
7
|
+
|
8
|
+
Then{ expect(result).to eq("test01.host.com:11211:10") }
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
describe Srvy::Formatters::Dalli, "format_many" do
|
13
|
+
Given(:hosts) { [
|
14
|
+
Srvy::Host.new("test01.host.com", 11211, 10, 0, 100),
|
15
|
+
Srvy::Host.new("test02.host.com", 11211, 5, 0, 100),
|
16
|
+
] }
|
17
|
+
|
18
|
+
When(:result){ subject.format_many(hosts) }
|
19
|
+
|
20
|
+
Then{ expect(result).to eq(["test01.host.com:11211:10", "test02.host.com:11211:5"]) }
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Srvy::Formatters::HostPort, "format_single" do
|
4
|
+
Given(:host) { Srvy::Host.new("test01.host.com", 11211, 0, 0, 100) }
|
5
|
+
|
6
|
+
When(:result){ subject.format_single(host) }
|
7
|
+
|
8
|
+
Then{ expect(result).to eq("test01.host.com:11211") }
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
describe Srvy::Formatters::HostPort, "format_many" do
|
13
|
+
Given(:hosts) { [Srvy::Host.new("test01.host.com", 11211, 0, 0, 100)] * 2 }
|
14
|
+
|
15
|
+
When(:result){ subject.format_many(hosts) }
|
16
|
+
|
17
|
+
Then{ expect(result).to eq(["test01.host.com:11211", "test01.host.com:11211"]) }
|
18
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Srvy::Formatters, "#from_name" do
|
4
|
+
|
5
|
+
context "with an invalid name" do
|
6
|
+
Given(:name){ "some_formatter" }
|
7
|
+
When(:result) { Srvy::Formatters.from_name(name) }
|
8
|
+
Then { expect(result).to have_failed(ArgumentError, /Unknown/) }
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
context "with host_port" do
|
13
|
+
Given(:name){ "host_port" }
|
14
|
+
When(:result) { Srvy::Formatters.from_name(name) }
|
15
|
+
Then { expect(result).to be_kind_of(Srvy::Formatters::HostPort) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe Srvy::Formatters, "#format_single" do
|
20
|
+
Given(:formatter) { double("Formatter", :format_single => true)}
|
21
|
+
Given(:name) { "host_port" }
|
22
|
+
Given(:host) { Srvy::Host.new("test01.host.com", 11211, 0, 0, 100) }
|
23
|
+
Given { Srvy::Formatters.stub(:from_name){ formatter } }
|
24
|
+
|
25
|
+
When(:result) { Srvy::Formatters.format_single(name, host) }
|
26
|
+
|
27
|
+
Then{ expect(formatter).to have_received(:format_single).with(host) }
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
describe Srvy::Formatters, "#format_many" do
|
32
|
+
Given(:formatter) { double("Formatter", :format_many => true)}
|
33
|
+
Given(:name) { "host_port" }
|
34
|
+
Given(:hosts) { [Srvy::Host.new("test01.host.com", 11211, 0, 0, 100)] }
|
35
|
+
Given { Srvy::Formatters.stub(:from_name){ formatter } }
|
36
|
+
|
37
|
+
When(:result) { Srvy::Formatters.format_many(name, hosts) }
|
38
|
+
|
39
|
+
Then{ expect(formatter).to have_received(:format_many).with(hosts) }
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Srvy::Resolver do
|
4
|
+
describe "cache setting" do
|
5
|
+
Given(:host) { "google.com"}
|
6
|
+
|
7
|
+
When{ subject.get host }
|
8
|
+
|
9
|
+
Then{ expect(subject.cache).to have_key(host) }
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "cache usage" do
|
14
|
+
|
15
|
+
Given(:host) { "google.com"}
|
16
|
+
Given(:srvy_result) { Srvy::Result.new(Time.now, [Srvy::Host.new("test01.host.com", 11211, 10, 0, 100)]) }
|
17
|
+
|
18
|
+
Given { subject.cache[host] = srvy_result }
|
19
|
+
|
20
|
+
When(:result){ subject.get host }
|
21
|
+
|
22
|
+
Then{ expect(result).to eq(srvy_result) }
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Srvy::Result do
|
4
|
+
Given(:now) { Time.at(Time.now.to_i) } # round to nearest second so jruby stops complaining on boundary cases
|
5
|
+
Given(:ttl) { 100 }
|
6
|
+
Given(:call_count) { 10000 }
|
7
|
+
Given(:allowance) { 0.02 }
|
8
|
+
|
9
|
+
# different hosts definitions
|
10
|
+
Given(:hosts_with_equal_weight) { [
|
11
|
+
Srvy::Host.new("test01.host.com", 11211, 10, 0, ttl),
|
12
|
+
Srvy::Host.new("test02.host.com", 11211, 10, 0, ttl),
|
13
|
+
] }
|
14
|
+
|
15
|
+
Given(:hosts_with_unequal_weight) { [
|
16
|
+
Srvy::Host.new("test01.host.com", 11211, 30, 0, ttl),
|
17
|
+
Srvy::Host.new("test02.host.com", 11211, 10, 0, ttl),
|
18
|
+
] }
|
19
|
+
|
20
|
+
Given(:hosts_with_unequal_priorities) { [
|
21
|
+
Srvy::Host.new("test01.host.com", 11211, 10, 0, ttl),
|
22
|
+
Srvy::Host.new("test02.host.com", 11211, 10, 1, ttl),
|
23
|
+
] }
|
24
|
+
Given(:no_hosts) { [ ] }
|
25
|
+
|
26
|
+
|
27
|
+
describe "#expired?" do
|
28
|
+
|
29
|
+
|
30
|
+
context "with data" do
|
31
|
+
subject{ Srvy::Result.new(now, hosts_with_equal_weight) }
|
32
|
+
|
33
|
+
Then{ Timecop.freeze(now) { expect(subject).to_not be_expired } }
|
34
|
+
Then{ Timecop.freeze(now + ttl) { expect(subject).to_not be_expired } }
|
35
|
+
Then{ Timecop.freeze(now + ttl + 1) { expect(subject).to be_expired } }
|
36
|
+
Then{ Timecop.freeze(now - ttl) { expect(subject).to_not be_expired } }
|
37
|
+
Then{ Timecop.freeze(now - (ttl + 1)) { expect(subject).to_not be_expired } }
|
38
|
+
end
|
39
|
+
|
40
|
+
context "with no data" do
|
41
|
+
subject{ Srvy::Result.new(now, []) }
|
42
|
+
|
43
|
+
Then{ Timecop.freeze(now) { expect(subject).to_not be_expired } }
|
44
|
+
Then{ Timecop.freeze(now + 10) { expect(subject).to_not be_expired } }
|
45
|
+
Then{ Timecop.freeze(now + 11) { expect(subject).to be_expired } }
|
46
|
+
Then{ Timecop.freeze(now - 10) { expect(subject).to_not be_expired } }
|
47
|
+
Then{ Timecop.freeze(now - 11) { expect(subject).to_not be_expired } }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#from_dns" do
|
52
|
+
subject{ Srvy::Result.from_dns(dns_result) }
|
53
|
+
|
54
|
+
Given(:dns_result) { Net::DNS::Resolver.new(:nameservers => "8.8.8.8").search("_xmpp-server._tcp.gmail.com", Net::DNS::SRV) }
|
55
|
+
Given(:srvs) { dns_result.answer.select{|rr| rr.is_a?(Net::DNS::RR::SRV) }}
|
56
|
+
Given(:dns_hosts) { srvs.map(&:host).sort }
|
57
|
+
Given(:srvys) { subject.get_all }
|
58
|
+
Given(:srvy_hosts) { srvys.map(&:host).sort }
|
59
|
+
|
60
|
+
Then{ expect(subject).to be_kind_of(Srvy::Result) }
|
61
|
+
Then{ expect(srvy_hosts).to eq(dns_hosts) }
|
62
|
+
|
63
|
+
|
64
|
+
Then do
|
65
|
+
srvys.each do |srvy|
|
66
|
+
dns = srvs.find{|d| d.host == srvy.host}
|
67
|
+
|
68
|
+
expect(dns).to_not be_nil
|
69
|
+
|
70
|
+
expect(srvy.host).to eq(dns.host)
|
71
|
+
expect(srvy.port).to eq(dns.port)
|
72
|
+
expect(srvy.weight).to eq(dns.weight)
|
73
|
+
expect(srvy.priority).to eq(dns.priority)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
describe "#get_many" do
|
80
|
+
subject{ Srvy::Result.new(Time.now, hosts) }
|
81
|
+
|
82
|
+
When(:result){ subject.get_many }
|
83
|
+
|
84
|
+
context "hosts with equal priorities" do
|
85
|
+
Given(:hosts) { hosts_with_equal_weight.sort_by(&:host) }
|
86
|
+
Then{ expect(result.sort_by(&:host)).to eq(hosts) }
|
87
|
+
end
|
88
|
+
|
89
|
+
context "hosts with unequal priorities" do
|
90
|
+
Given(:hosts) { hosts_with_unequal_priorities }
|
91
|
+
# should only have the best (i.e. lowest) priority
|
92
|
+
Then{ expect(result).to eq([hosts[0]]) }
|
93
|
+
end
|
94
|
+
|
95
|
+
context "no hosts" do
|
96
|
+
Given(:hosts) { no_hosts }
|
97
|
+
Then{ expect(result).to eq([]) }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "#get_all" do
|
102
|
+
subject{ Srvy::Result.new(Time.now, hosts) }
|
103
|
+
|
104
|
+
When(:result){ subject.get_all }
|
105
|
+
|
106
|
+
context "with some hosts" do
|
107
|
+
Given(:hosts) { hosts_with_unequal_priorities }
|
108
|
+
Then{ expect(result).to eq(hosts) }
|
109
|
+
end
|
110
|
+
|
111
|
+
context "with no hosts" do
|
112
|
+
Given(:hosts) { [ ] }
|
113
|
+
Then{ expect(result).to eq(hosts) }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "#get_single" do
|
118
|
+
subject{ Srvy::Result.new(Time.now, hosts) }
|
119
|
+
|
120
|
+
When(:result_percentages) do
|
121
|
+
calls = call_count.times.map{ subject.get_single }
|
122
|
+
|
123
|
+
result_counts = calls.each_with_object({}) do |call_result, acc|
|
124
|
+
acc[call_result] ||= 0
|
125
|
+
acc[call_result] += 1
|
126
|
+
end
|
127
|
+
all_hosts_and_results = result_counts.keys | hosts
|
128
|
+
all_hosts_and_results.each_with_object({}) do |host, acc|
|
129
|
+
count = result_counts[host] || 0
|
130
|
+
acc[host] = count / call_count.to_f
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "When the result has hosts with equal weights" do
|
135
|
+
Given(:hosts){ hosts_with_equal_weight }
|
136
|
+
|
137
|
+
Then do
|
138
|
+
result_percentages.values.each do |percentage|
|
139
|
+
expect(percentage).to be_within(allowance).of(0.5)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context "hosts with unequal priority" do
|
145
|
+
Given(:hosts){ hosts_with_unequal_priorities }
|
146
|
+
|
147
|
+
Then do
|
148
|
+
expect(result_percentages[hosts[0]]).to be_within(allowance).of(1.0)
|
149
|
+
expect(result_percentages[hosts[1]]).to be_within(allowance).of(0.0)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
context "hosts with unequal weights" do
|
155
|
+
Given(:hosts){ hosts_with_unequal_weight }
|
156
|
+
|
157
|
+
Then do
|
158
|
+
expect(result_percentages[hosts[0]]).to be_within(allowance).of(0.75)
|
159
|
+
expect(result_percentages[hosts[1]]).to be_within(allowance).of(0.25)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "no hosts" do
|
164
|
+
Given(:hosts){ no_hosts }
|
165
|
+
|
166
|
+
Then do
|
167
|
+
expect(result_percentages[nil]).to be_within(allowance).of(1.0)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
|
178
|
+
|
data/srvy.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'srvy/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "srvy"
|
8
|
+
spec.version = Srvy::VERSION
|
9
|
+
spec.authors = ["Scott Fleckenstein"]
|
10
|
+
spec.email = ["nullstyle@gmail.com"]
|
11
|
+
spec.description = %q{SRV-based service discovery}
|
12
|
+
spec.summary = %q{SRV-based service discovery}
|
13
|
+
spec.homepage = "https://github.com/nullstyle/srvy"
|
14
|
+
spec.license = "MIT"
|
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 "net-dns", "= 0.8.0" # pin this version of net-dns because our monkey patch only applies to it.
|
22
|
+
spec.add_dependency "lru_redux", "~> 0.8.1"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "pry"
|
27
|
+
|
28
|
+
spec.add_development_dependency "rspec", ">= 2.14.1"
|
29
|
+
spec.add_development_dependency "rspec-given", ">= 3.5.0"
|
30
|
+
spec.add_development_dependency "coveralls", "~> 0.7.0"
|
31
|
+
spec.add_development_dependency "timecop", "~> 0.7.1"
|
32
|
+
spec.add_development_dependency "activesupport", ">= 4.0.0"
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: srvy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Scott Fleckenstein
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: net-dns
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.8.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.8.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: lru_redux
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.8.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.8.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.14.1
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.14.1
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec-given
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 3.5.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 3.5.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: coveralls
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.7.0
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.7.0
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: timecop
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ~>
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.7.1
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ~>
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.7.1
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: activesupport
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 4.0.0
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - '>='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 4.0.0
|
153
|
+
description: SRV-based service discovery
|
154
|
+
email:
|
155
|
+
- nullstyle@gmail.com
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files: []
|
159
|
+
files:
|
160
|
+
- .coveralls.yml
|
161
|
+
- .gitignore
|
162
|
+
- .travis.yml
|
163
|
+
- Gemfile
|
164
|
+
- LICENSE.txt
|
165
|
+
- README.md
|
166
|
+
- Rakefile
|
167
|
+
- lib/lru_redux/has_key_monkey_patch.rb
|
168
|
+
- lib/net/dns/ext/resolver_nameserver_monkey_patch.rb
|
169
|
+
- lib/srvy.rb
|
170
|
+
- lib/srvy/formatters.rb
|
171
|
+
- lib/srvy/formatters/base.rb
|
172
|
+
- lib/srvy/formatters/dalli.rb
|
173
|
+
- lib/srvy/formatters/host_port.rb
|
174
|
+
- lib/srvy/host.rb
|
175
|
+
- lib/srvy/resolver.rb
|
176
|
+
- lib/srvy/result.rb
|
177
|
+
- lib/srvy/version.rb
|
178
|
+
- spec/spec_helper.rb
|
179
|
+
- spec/srvy/formatters/base_spec.rb
|
180
|
+
- spec/srvy/formatters/dalli_spec.rb
|
181
|
+
- spec/srvy/formatters/host_port_spec.rb
|
182
|
+
- spec/srvy/formatters_spec.rb
|
183
|
+
- spec/srvy/resolver_spec.rb
|
184
|
+
- spec/srvy/result_spec.rb
|
185
|
+
- srvy.gemspec
|
186
|
+
homepage: https://github.com/nullstyle/srvy
|
187
|
+
licenses:
|
188
|
+
- MIT
|
189
|
+
metadata: {}
|
190
|
+
post_install_message:
|
191
|
+
rdoc_options: []
|
192
|
+
require_paths:
|
193
|
+
- lib
|
194
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
195
|
+
requirements:
|
196
|
+
- - '>='
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: '0'
|
199
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
200
|
+
requirements:
|
201
|
+
- - '>='
|
202
|
+
- !ruby/object:Gem::Version
|
203
|
+
version: '0'
|
204
|
+
requirements: []
|
205
|
+
rubyforge_project:
|
206
|
+
rubygems_version: 2.0.3
|
207
|
+
signing_key:
|
208
|
+
specification_version: 4
|
209
|
+
summary: SRV-based service discovery
|
210
|
+
test_files:
|
211
|
+
- spec/spec_helper.rb
|
212
|
+
- spec/srvy/formatters/base_spec.rb
|
213
|
+
- spec/srvy/formatters/dalli_spec.rb
|
214
|
+
- spec/srvy/formatters/host_port_spec.rb
|
215
|
+
- spec/srvy/formatters_spec.rb
|
216
|
+
- spec/srvy/resolver_spec.rb
|
217
|
+
- spec/srvy/result_spec.rb
|
218
|
+
has_rdoc:
|