synapse 0.8.0 → 0.9.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.
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ cache: bundler
3
+ rvm:
4
+ - 1.9.3
5
+
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- synapse (0.8.0)
4
+ synapse (0.9.1)
5
5
  docker-api (~> 1.7.2)
6
6
  zk (~> 1.9.2)
7
7
 
@@ -27,6 +27,7 @@ GEM
27
27
  slop (~> 3.4)
28
28
  pry-nav (0.2.3)
29
29
  pry (~> 0.9.10)
30
+ rake (10.1.1)
30
31
  rspec (2.14.1)
31
32
  rspec-core (~> 2.14.0)
32
33
  rspec-expectations (~> 2.14.0)
@@ -36,10 +37,10 @@ GEM
36
37
  diff-lcs (>= 1.1.3, < 2.0)
37
38
  rspec-mocks (2.14.3)
38
39
  slop (3.4.6)
39
- zk (1.9.2)
40
+ zk (1.9.3)
40
41
  logging (~> 1.7.2)
41
42
  zookeeper (~> 1.4.0)
42
- zookeeper (1.4.7)
43
+ zookeeper (1.4.8)
43
44
 
44
45
  PLATFORMS
45
46
  ruby
@@ -47,5 +48,6 @@ PLATFORMS
47
48
  DEPENDENCIES
48
49
  pry
49
50
  pry-nav
51
+ rake
50
52
  rspec
51
53
  synapse!
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://travis-ci.org/airbnb/synapse.png?branch=master)](https://travis-ci.org/airbnb/synapse)
2
+
1
3
  # Synapse #
2
4
 
3
5
  Synapse is Airbnb's new system for service discovery.
data/Rakefile CHANGED
@@ -1 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :test => :spec
7
+ task :default => :spec
8
+
data/bin/synapse CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'json'
3
+ require 'yaml'
4
4
  require 'optparse'
5
5
 
6
6
  require 'synapse'
@@ -32,15 +32,15 @@ optparse.parse!
32
32
  def parseconfig(filename)
33
33
  # parse synapse config file
34
34
  begin
35
- c = JSON::parse(File.read(filename))
35
+ c = YAML::parse(File.read(filename))
36
36
  rescue Errno::ENOENT => e
37
37
  raise ArgumentError, "config file does not exist:\n#{e.inspect}"
38
38
  rescue Errno::EACCES => e
39
39
  raise ArgumentError, "could not open config file:\n#{e.inspect}"
40
- rescue JSON::ParserError => e
41
- raise "config file #{filename} is not json:\n#{e.inspect}"
40
+ rescue YAML::ParseError => e
41
+ raise "config file #{filename} is not yaml:\n#{e.inspect}"
42
42
  end
43
- return c
43
+ return c.to_ruby
44
44
  end
45
45
 
46
46
  config = parseconfig(options[:config])
@@ -51,8 +51,8 @@ if config.has_key?('service_conf_dir')
51
51
  if ! Dir.exists?(cdir)
52
52
  raise "service conf dir does not exist:#{cdir}"
53
53
  end
54
- cfiles = Dir.glob(File.join(cdir, '*.json'))
55
- cfiles.each { |x| config['services'][File.basename(x[/(.*)\.json$/, 1])] = parseconfig(x) }
54
+ cfiles = Dir.glob(File.join(cdir, '*.{json,yaml}'))
55
+ cfiles.each { |x| config['services'][File.basename(x[/(.*)\.(json|yaml)$/, 1])] = parseconfig(x) }
56
56
  end
57
57
 
58
58
  # run synapse
@@ -1,7 +1,5 @@
1
1
  require 'synapse/log'
2
-
3
2
  require 'socket'
4
- require 'digest'
5
3
 
6
4
  module Synapse
7
5
  class Haproxy
@@ -750,8 +748,12 @@ module Synapse
750
748
 
751
749
  # used to build unique, consistent haproxy names for backends
752
750
  def construct_name(backend)
753
- address_digest = Digest::SHA256.hexdigest(backend['host'])[0..7]
754
- return "#{backend['name']}:#{backend['port']}_#{address_digest}"
751
+ name = "#{backend['host']}:#{backend['port']}"
752
+ if backend['name'] && !backend['name'].empty?
753
+ name = "#{name}_#{backend['name']}"
754
+ end
755
+
756
+ return name
755
757
  end
756
758
  end
757
759
  end
@@ -1,6 +1,12 @@
1
+ require 'synapse/log'
2
+
1
3
  module Synapse
2
4
  class BaseWatcher
3
- attr_reader :name, :backends, :haproxy
5
+ include Logging
6
+
7
+ LEADER_WARN_INTERVAL = 30
8
+
9
+ attr_reader :name, :haproxy
4
10
 
5
11
  def initialize(opts={}, synapse)
6
12
  super()
@@ -15,6 +21,9 @@ module Synapse
15
21
  @name = opts['name']
16
22
  @discovery = opts['discovery']
17
23
 
24
+ @leader_election = opts['leader_election'] || false
25
+ @leader_last_warn = Time.now - LEADER_WARN_INTERVAL
26
+
18
27
  # the haproxy config
19
28
  @haproxy = opts['haproxy']
20
29
  @haproxy['server_options'] ||= ""
@@ -55,6 +64,26 @@ module Synapse
55
64
  true
56
65
  end
57
66
 
67
+ def backends
68
+ if @leader_election
69
+ if @backends.all?{|b| b.key?('id') && b['id']}
70
+ smallest = @backends.sort_by{ |b| b['id']}.first
71
+ log.debug "synapse: leader election chose one of #{@backends.count} backends " \
72
+ "(#{smallest['host']}:#{smallest['port']} with id #{smallest['id']})"
73
+
74
+ return [smallest]
75
+ elsif (Time.now - @leader_last_warn) > LEADER_WARN_INTERVAL
76
+ log.warn "synapse: service #{@name}: leader election failed; not all backends include an id"
77
+ @leader_last_warn = Time.now
78
+ end
79
+
80
+ # if leader election fails, return no backends
81
+ return []
82
+ end
83
+
84
+ return @backends
85
+ end
86
+
58
87
  private
59
88
  def validate_discovery_opts
60
89
  raise ArgumentError, "invalid discovery method '#{@discovery['method']}' for base watcher" \
@@ -1,12 +1,10 @@
1
1
  require "synapse/service_watcher/base"
2
- require "synapse/log"
3
2
 
4
3
  require 'thread'
5
4
  require 'resolv'
6
5
 
7
6
  module Synapse
8
7
  class DnsWatcher < BaseWatcher
9
- include Logging
10
8
  def start
11
9
  @check_interval = @discovery['check_interval'] || 30.0
12
10
  @nameserver = @discovery['nameserver']
@@ -80,7 +78,6 @@ module Synapse
80
78
  new_backends = servers.flat_map do |(server, addresses)|
81
79
  addresses.map do |address|
82
80
  {
83
- 'name' => server['name'],
84
81
  'host' => address,
85
82
  'port' => server['port']
86
83
  }
@@ -34,7 +34,7 @@ module Synapse
34
34
  end
35
35
 
36
36
  sleep_until_next_check(start)
37
- rescue => e
37
+ rescue Exception => e
38
38
  log.warn "synapse: error in watcher thread: #{e.inspect}"
39
39
  log.warn e.backtrace
40
40
  end
@@ -50,6 +50,24 @@ module Synapse
50
50
  end
51
51
  end
52
52
 
53
+ def rewrite_container_ports(ports)
54
+ pairs = []
55
+ if ports.is_a?(String)
56
+ # "Ports" comes through (as of 0.6.5) as a string like "0.0.0.0:49153->6379/tcp, 0.0.0.0:49153->6379/tcp"
57
+ # Convert string to a map of container port to host port: {"7000"->"49158", "6379": "49159"}
58
+ pairs = ports.split(", ").collect do |v|
59
+ pair = v.split('->')
60
+ [ pair[1].rpartition("/").first, pair[0].rpartition(":").last ]
61
+ end
62
+ elsif ports.is_a?(Array)
63
+ # New style API, ports is an array of hashes, with numeric values (or nil if no ports forwarded)
64
+ pairs = ports.collect do |v|
65
+ [v['PrivatePort'].to_s, v['PublicPort'].to_s]
66
+ end
67
+ end
68
+ Hash[pairs]
69
+ end
70
+
53
71
  def containers
54
72
  backends = @discovery['servers'].map do |server|
55
73
  Docker.url = "http://#{server['host']}:#{server['port'] || 4243}"
@@ -59,14 +77,8 @@ module Synapse
59
77
  log.warn "synapse: error polling docker host #{Docker.url}: #{e.inspect}"
60
78
  next []
61
79
  end
62
- # "Ports" comes through (as of 0.6.5) as a string like "0.0.0.0:49153->6379/tcp, 0.0.0.0:49153->6379/tcp"
63
- # Convert string to a map of container port to host port: {"7000"->"49158", "6379": "49159"}
64
80
  cnts.each do |cnt|
65
- pairs = cnt["Ports"].split(", ").collect do |v|
66
- pair = v.split('->')
67
- [ pair[1].rpartition("/").first, pair[0].rpartition(":").last ]
68
- end
69
- cnt["Ports"] = Hash[pairs]
81
+ cnt['Ports'] = rewrite_container_ports cnt['Ports']
70
82
  end
71
83
  # Discover containers that match the image/port we're interested in
72
84
  cnts = cnts.find_all do |cnt|
@@ -1,11 +1,11 @@
1
1
  require "synapse/service_watcher/base"
2
- require "synapse/log"
3
2
 
4
3
  require 'zk'
5
4
 
6
5
  module Synapse
7
6
  class ZookeeperWatcher < BaseWatcher
8
- include Logging
7
+ NUMBERS_RE = /^\d+$/
8
+
9
9
  def start
10
10
  zk_hosts = @discovery['hosts'].shuffle.join(',')
11
11
 
@@ -55,18 +55,22 @@ module Synapse
55
55
 
56
56
  new_backends = []
57
57
  begin
58
- @zk.children(@discovery['path'], :watch => true).map do |name|
59
- node = @zk.get("#{@discovery['path']}/#{name}")
58
+ @zk.children(@discovery['path'], :watch => true).each do |id|
59
+ node = @zk.get("#{@discovery['path']}/#{id}")
60
60
 
61
61
  begin
62
- host, port = deserialize_service_instance(node.first)
63
- rescue
64
- log.error "synapse: invalid data in ZK node #{name} at #{@discovery['path']}"
62
+ host, port, name = deserialize_service_instance(node.first)
63
+ rescue StandardError => e
64
+ log.error "synapse: invalid data in ZK node #{id} at #{@discovery['path']}: #{e}"
65
65
  else
66
66
  server_port = @server_port_override ? @server_port_override : port
67
67
 
68
+ # find the numberic id in the node name; used for leader elections if enabled
69
+ numeric_id = id.split('_').last
70
+ numeric_id = NUMBERS_RE =~ numeric_id ? numeric_id.to_i : nil
71
+
68
72
  log.debug "synapse: discovered backend #{name} at #{host}:#{server_port} for service #{@name}"
69
- new_backends << { 'name' => name, 'host' => host, 'port' => server_port}
73
+ new_backends << { 'name' => name, 'host' => host, 'port' => server_port, 'id' => numeric_id}
70
74
  end
71
75
  end
72
76
  rescue ZK::Exceptions::NoNode
@@ -108,28 +112,16 @@ module Synapse
108
112
  end
109
113
  end
110
114
 
111
- # tries to extract host/port from a json hash
112
- def parse_json(data)
113
- begin
114
- json = JSON.parse data
115
- rescue Object => o
116
- return false
117
- end
118
- raise 'instance json data does not have host key' unless json.has_key?('host')
119
- raise 'instance json data does not have port key' unless json.has_key?('port')
120
- return json['host'], json['port']
121
- end
122
-
123
115
  # decode the data at a zookeeper endpoint
124
116
  def deserialize_service_instance(data)
125
117
  log.debug "synapse: deserializing process data"
118
+ decoded = JSON.parse(data)
126
119
 
127
- # if that does not work, try json
128
- host, port = parse_json(data)
129
- return host, port if host
120
+ host = decoded['host'] || (raise ValueError, 'instance json data does not have host key')
121
+ port = decoded['port'] || (raise ValueError, 'instance json data does not have port key')
122
+ name = decoded['name'] || nil
130
123
 
131
- # if we got this far, then we have a problem
132
- raise "could not decode this data:\n#{data}"
124
+ return host, port, name
133
125
  end
134
126
  end
135
127
  end
@@ -1,3 +1,3 @@
1
1
  module Synapse
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.1"
3
3
  end
@@ -6,8 +6,7 @@ describe Synapse::Haproxy do
6
6
  subject { Synapse::Haproxy.new(config['haproxy']) }
7
7
 
8
8
  it 'updating the config' do
9
- mockWatcher = mock(Synapse::ServiceWatcher)
10
- binding.pry
9
+ mockWatcher = double(Synapse::ServiceWatcher)
11
10
  subject.should_receive(:generate_config)
12
11
  subject.update_config([mockWatcher])
13
12
  end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ class Synapse::BaseWatcher
4
+ attr_reader :should_exit, :default_servers
5
+ end
6
+
7
+ describe Synapse::BaseWatcher do
8
+ let(:mocksynapse) { double() }
9
+ subject { Synapse::BaseWatcher.new(args, mocksynapse) }
10
+ let(:testargs) { { 'name' => 'foo', 'discovery' => { 'method' => 'base' }, 'haproxy' => {} }}
11
+
12
+ def remove_arg(name)
13
+ args = testargs.clone
14
+ args.delete name
15
+ args
16
+ end
17
+
18
+ context "can construct normally" do
19
+ let(:args) { testargs }
20
+ it('can at least construct') { expect { subject }.not_to raise_error }
21
+ end
22
+
23
+ ['name', 'discovery', 'haproxy'].each do |to_remove|
24
+ context "without #{to_remove} argument" do
25
+ let(:args) { remove_arg to_remove }
26
+ it('gots bang') { expect { subject }.to raise_error(ArgumentError, "missing required option #{to_remove}") }
27
+ end
28
+ end
29
+
30
+ context "normal tests" do
31
+ let(:args) { testargs }
32
+ it('is running') { expect(subject.should_exit).to equal(false) }
33
+ it('can ping') { expect(subject.ping?).to equal(true) }
34
+ it('can be stopped') do
35
+ subject.stop
36
+ expect(subject.should_exit).to equal(true)
37
+ end
38
+ end
39
+
40
+ context "with default_servers" do
41
+ default_servers = ['server1', 'server2']
42
+ let(:args) { testargs.merge({'default_servers' => default_servers}) }
43
+ it('sets default backends to default_servers') { expect(subject.backends).to equal(default_servers) }
44
+ end
45
+ end
46
+
@@ -0,0 +1,152 @@
1
+ require 'spec_helper'
2
+
3
+ class Synapse::DockerWatcher
4
+ attr_reader :check_interval, :watcher, :synapse
5
+ attr_accessor :default_servers
6
+ end
7
+
8
+ describe Synapse::DockerWatcher do
9
+ let(:mocksynapse) { double() }
10
+ subject { Synapse::DockerWatcher.new(testargs, mocksynapse) }
11
+ let(:testargs) { { 'name' => 'foo', 'discovery' => { 'method' => 'docker', 'servers' => [{'host' => 'server1.local', 'name' => 'mainserver'}], 'image_name' => 'mycool/image', 'container_port' => 6379 }, 'haproxy' => {} }}
12
+ before(:each) do
13
+ allow(subject.log).to receive(:warn)
14
+ allow(subject.log).to receive(:info)
15
+ end
16
+
17
+ def add_arg(name, value)
18
+ args = testargs.clone
19
+ args['discovery'][name] = value
20
+ args
21
+ end
22
+
23
+ context "can construct normally" do
24
+ it('can at least construct') { expect { subject }.not_to raise_error }
25
+ end
26
+
27
+ context "normal tests" do
28
+ it('starts a watcher thread') do
29
+ watcher_mock = double()
30
+ expect(Thread).to receive(:new).and_return(watcher_mock)
31
+ subject.start
32
+ expect(subject.watcher).to equal(watcher_mock)
33
+ end
34
+ it('sets default check interval') do
35
+ expect(Thread).to receive(:new).and_return(double)
36
+ subject.start
37
+ expect(subject.check_interval).to eq(15.0)
38
+ end
39
+ end
40
+
41
+ context "watch tests" do
42
+ before(:each) do
43
+ expect(subject).to receive(:sleep_until_next_check) do |arg|
44
+ subject.instance_variable_set('@should_exit', true)
45
+ end
46
+ end
47
+ it('has a happy first run path, configuring backends') do
48
+ expect(subject).to receive(:containers).and_return(['container1'])
49
+ expect(subject).to receive(:configure_backends).with(['container1'])
50
+ subject.send(:watch)
51
+ end
52
+ it('does not call configure_backends if there is no change') do
53
+ expect(subject).to receive(:containers).and_return([])
54
+ expect(subject).to_not receive(:configure_backends)
55
+ subject.send(:watch)
56
+ end
57
+ end
58
+ context "watch eats exceptions" do
59
+ it "blows up when finding containers" do
60
+ expect(subject).to receive(:containers) do |arg|
61
+ subject.instance_variable_set('@should_exit', true)
62
+ raise('throw exception inside watch')
63
+ end
64
+ expect { subject.send(:watch) }.not_to raise_error
65
+ end
66
+ end
67
+
68
+ context "configure_backends tests" do
69
+ before(:each) do
70
+ expect(subject.synapse).to receive(:'reconfigure!').at_least(:once)
71
+ end
72
+ it 'runs' do
73
+ expect { subject.send(:configure_backends, []) }.not_to raise_error
74
+ end
75
+ it 'sets backends right' do
76
+ subject.send(:configure_backends, ['foo'])
77
+ expect(subject.backends).to eq(['foo'])
78
+ end
79
+ it 'resets to default backends if no container found' do
80
+ subject.default_servers = ['fallback1']
81
+ subject.send(:configure_backends, ['foo'])
82
+ expect(subject.backends).to eq(['foo'])
83
+ subject.send(:configure_backends, [])
84
+ expect(subject.backends).to eq(['fallback1'])
85
+ end
86
+ it 'does not reset to default backends if there are no default backends' do
87
+ subject.default_servers = []
88
+ subject.send(:configure_backends, ['foo'])
89
+ expect(subject.backends).to eq(['foo'])
90
+ subject.send(:configure_backends, [])
91
+ expect(subject.backends).to eq(['foo'])
92
+ end
93
+ end
94
+
95
+ context "rewrite_container_ports tests" do
96
+ it 'doesnt break if Ports => nil' do
97
+ subject.send(:rewrite_container_ports, nil)
98
+ end
99
+ it 'works for old style port mappings' do
100
+ expect(subject.send(:rewrite_container_ports, "0.0.0.0:49153->6379/tcp, 0.0.0.0:49154->6390/tcp")).to \
101
+ eql({'6379' => '49153', '6390' => '49154'})
102
+ end
103
+ it 'works for new style port mappings' do
104
+ expect(subject.send(:rewrite_container_ports, [{'PrivatePort' => 6379, 'PublicPort' => 49153}, {'PublicPort' => 49154, 'PrivatePort' => 6390}])).to \
105
+ eql({'6379' => '49153', '6390' => '49154'})
106
+ end
107
+ end
108
+
109
+ context "container discovery tests" do
110
+ before(:each) do
111
+ getter = double()
112
+ expect(getter).to receive(:get)
113
+ expect(Docker).to receive(:connection).and_return(getter)
114
+ end
115
+
116
+ it('has a sane uri') { subject.send(:containers); expect(Docker.url).to eql('http://server1.local:4243') }
117
+
118
+ context 'old style port mappings' do
119
+ context 'works for one container' do
120
+ let(:docker_data) { [{"Ports" => "0.0.0.0:49153->6379/tcp, 0.0.0.0:49154->6390/tcp", "Image" => "mycool/image:tagname"}] }
121
+ it do
122
+ expect(Docker::Util).to receive(:parse_json).and_return(docker_data)
123
+ expect(subject.send(:containers)).to eql([{"name"=>"mainserver", "host"=>"server1.local", "port"=>"49153"}])
124
+ end
125
+ end
126
+ context 'works for multiple containers' do
127
+ let(:docker_data) { [{"Ports" => "0.0.0.0:49153->6379/tcp, 0.0.0.0:49154->6390/tcp", "Image" => "mycool/image:tagname"}, {"Ports" => "0.0.0.0:49155->6379/tcp", "Image" => "mycool/image:tagname"}] }
128
+ it do
129
+ expect(Docker::Util).to receive(:parse_json).and_return(docker_data)
130
+ expect(subject.send(:containers)).to eql([{"name"=>"mainserver", "host"=>"server1.local", "port"=>"49153"},{"name"=>"mainserver", "host"=>"server1.local", "port"=>"49155"}])
131
+ end
132
+ end
133
+ end
134
+
135
+ context 'new style port mappings' do
136
+ let(:docker_data) { [{"Ports" => [{'PrivatePort' => 6379, 'PublicPort' => 49153}, {'PublicPort' => 49154, 'PrivatePort' => 6390}], "Image" => "mycool/image:tagname"}] }
137
+ it do
138
+ expect(Docker::Util).to receive(:parse_json).and_return(docker_data)
139
+ expect(subject.send(:containers)).to eql([{"name"=>"mainserver", "host"=>"server1.local", "port"=>"49153"}])
140
+ end
141
+ end
142
+
143
+ context 'filters out wrong images' do
144
+ let(:docker_data) { [{"Ports" => "0.0.0.0:49153->6379/tcp, 0.0.0.0:49154->6390/tcp", "Image" => "mycool/image:tagname"}, {"Ports" => "0.0.0.0:49155->6379/tcp", "Image" => "wrong/image:tagname"}] }
145
+ it do
146
+ expect(Docker::Util).to receive(:parse_json).and_return(docker_data)
147
+ expect(subject.send(:containers)).to eql([{"name"=>"mainserver", "host"=>"server1.local", "port"=>"49153"}])
148
+ end
149
+ end
150
+ end
151
+ end
152
+
data/spec/spec_helper.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  # loaded once.
5
5
  #
6
6
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
- require '../lib/synapse'
7
+ require "#{File.dirname(__FILE__)}/../lib/synapse"
8
8
  require 'pry'
9
9
  require 'support/config'
10
10
 
data/synapse.gemspec CHANGED
@@ -19,6 +19,7 @@ Gem::Specification.new do |gem|
19
19
  gem.add_runtime_dependency "zk", "~> 1.9.2"
20
20
  gem.add_runtime_dependency "docker-api", "~> 1.7.2"
21
21
 
22
+ gem.add_development_dependency "rake"
22
23
  gem.add_development_dependency "rspec"
23
24
  gem.add_development_dependency "pry"
24
25
  gem.add_development_dependency "pry-nav"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: synapse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-01-27 00:00:00.000000000 Z
12
+ date: 2014-02-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: zk
@@ -43,6 +43,22 @@ dependencies:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
45
  version: 1.7.2
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
46
62
  - !ruby/object:Gem::Dependency
47
63
  name: rspec
48
64
  requirement: !ruby/object:Gem::Requirement
@@ -102,6 +118,7 @@ files:
102
118
  - .gitignore
103
119
  - .mailmap
104
120
  - .rspec
121
+ - .travis.yml
105
122
  - Gemfile
106
123
  - Gemfile.lock
107
124
  - LICENSE.txt
@@ -126,6 +143,8 @@ files:
126
143
  - lib/synapse/service_watcher/zookeeper.rb
127
144
  - lib/synapse/version.rb
128
145
  - spec/lib/synapse/haproxy_spec.rb
146
+ - spec/lib/synapse/service_watcher_base_spec.rb
147
+ - spec/lib/synapse/service_watcher_docker_spec.rb
129
148
  - spec/spec_helper.rb
130
149
  - spec/support/config.rb
131
150
  - spec/support/minimum.conf.yaml
@@ -142,12 +161,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
142
161
  - - ! '>='
143
162
  - !ruby/object:Gem::Version
144
163
  version: '0'
164
+ segments:
165
+ - 0
166
+ hash: 575568743231626432
145
167
  required_rubygems_version: !ruby/object:Gem::Requirement
146
168
  none: false
147
169
  requirements:
148
170
  - - ! '>='
149
171
  - !ruby/object:Gem::Version
150
172
  version: '0'
173
+ segments:
174
+ - 0
175
+ hash: 575568743231626432
151
176
  requirements: []
152
177
  rubyforge_project:
153
178
  rubygems_version: 1.8.23
@@ -156,6 +181,8 @@ specification_version: 3
156
181
  summary: ': Write a gem summary'
157
182
  test_files:
158
183
  - spec/lib/synapse/haproxy_spec.rb
184
+ - spec/lib/synapse/service_watcher_base_spec.rb
185
+ - spec/lib/synapse/service_watcher_docker_spec.rb
159
186
  - spec/spec_helper.rb
160
187
  - spec/support/config.rb
161
188
  - spec/support/minimum.conf.yaml