synapse 0.2.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +3 -0
  2. data/.mailmap +3 -0
  3. data/LICENSE.txt +2 -2
  4. data/Makefile +6 -0
  5. data/README.md +42 -13
  6. data/bin/synapse +29 -21
  7. data/config/hostheader_test.json +71 -0
  8. data/config/svcdir_test.json +46 -0
  9. data/config/synapse.conf.json +26 -32
  10. data/config/synapse_services/service1.json +24 -0
  11. data/config/synapse_services/service2.json +24 -0
  12. data/lib/synapse.rb +39 -24
  13. data/lib/synapse/base.rb +1 -1
  14. data/lib/synapse/haproxy.rb +579 -22
  15. data/lib/synapse/log.rb +24 -0
  16. data/lib/synapse/service_watcher.rb +10 -6
  17. data/lib/synapse/service_watcher/base.rb +33 -11
  18. data/lib/synapse/service_watcher/dns.rb +28 -20
  19. data/lib/synapse/service_watcher/docker.rb +108 -0
  20. data/lib/synapse/service_watcher/ec2tag.rb +1 -1
  21. data/lib/synapse/service_watcher/zookeeper.rb +25 -28
  22. data/lib/synapse/version.rb +1 -1
  23. data/spec/spec_helper.rb +2 -2
  24. data/synapse.gemspec +2 -3
  25. metadata +15 -25
  26. data/Vagrantfile +0 -112
  27. data/chef/converge +0 -4
  28. data/chef/cookbooks/lxc/recipes/default.rb +0 -2
  29. data/chef/cookbooks/synapse/attributes/default.rb +0 -1
  30. data/chef/cookbooks/synapse/recipes/default.rb +0 -6
  31. data/chef/run.json +0 -8
  32. data/chef/run.rb +0 -2
  33. data/client/.RData +0 -0
  34. data/client/.Rhistory +0 -294
  35. data/client/bench_rewrite_config.dat +0 -2013
  36. data/client/benchmark-client.iml +0 -20
  37. data/client/pom.xml +0 -45
  38. data/client/src/main/java/ClientArsch.java +0 -68
  39. data/client/src/main/java/META-INF/MANIFEST.MF +0 -3
  40. data/haproxy.pid +0 -1
  41. data/lib/gen-rb/endpoint_types.rb +0 -65
  42. data/lib/gen-rb/thrift.rb +0 -65
  43. data/test.sh +0 -3
@@ -0,0 +1,24 @@
1
+ module Synapse
2
+ module Logging
3
+
4
+ def log
5
+ @logger ||= Logging.logger_for(self.class.name)
6
+ end
7
+
8
+ # Use a hash class-ivar to cache a unique Logger per class:
9
+ @loggers = {}
10
+
11
+ class << self
12
+ def logger_for(classname)
13
+ @loggers[classname] ||= configure_logger_for(classname)
14
+ end
15
+
16
+ def configure_logger_for(classname)
17
+ logger = Logger.new(STDERR)
18
+ logger.level = Logger::INFO unless ENV['DEBUG']
19
+ logger.progname = classname
20
+ return logger
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,7 +1,8 @@
1
- require_relative "./service_watcher/base"
2
- require_relative "./service_watcher/zookeeper"
3
- require_relative "./service_watcher/ec2tag"
4
- require_relative "./service_watcher/dns"
1
+ require "synapse/service_watcher/base"
2
+ require "synapse/service_watcher/zookeeper"
3
+ require "synapse/service_watcher/ec2tag"
4
+ require "synapse/service_watcher/dns"
5
+ require "synapse/service_watcher/docker"
5
6
 
6
7
  module Synapse
7
8
  class ServiceWatcher
@@ -10,11 +11,14 @@ module Synapse
10
11
  'base'=>BaseWatcher,
11
12
  'zookeeper'=>ZookeeperWatcher,
12
13
  'ec2tag'=>EC2Watcher,
13
- 'dns' => DnsWatcher
14
+ 'dns' => DnsWatcher,
15
+ 'docker' => DockerWatcher
14
16
  }
15
17
 
16
18
  # the method which actually dispatches watcher creation requests
17
- def self.create(opts, synapse)
19
+ def self.create(name, opts, synapse)
20
+ opts['name'] = name
21
+
18
22
  raise ArgumentError, "Missing discovery method when trying to create watcher" \
19
23
  unless opts.has_key?('discovery') && opts['discovery'].has_key?('method')
20
24
 
@@ -1,7 +1,6 @@
1
-
2
1
  module Synapse
3
2
  class BaseWatcher
4
- attr_reader :backends, :name, :listen, :local_port, :server_options
3
+ attr_reader :name, :backends, :haproxy
5
4
 
6
5
  def initialize(opts={}, synapse)
7
6
  super()
@@ -9,34 +8,57 @@ module Synapse
9
8
  @synapse = synapse
10
9
 
11
10
  # set required service parameters
12
- %w{name discovery local_port}.each do |req|
11
+ %w{name discovery haproxy}.each do |req|
13
12
  raise ArgumentError, "missing required option #{req}" unless opts[req]
14
13
  end
15
14
 
16
15
  @name = opts['name']
17
16
  @discovery = opts['discovery']
18
- @local_port = opts['local_port']
19
17
 
20
- # optional service parameters
21
- @listen = opts['listen'] || []
22
- @server_options = opts['server_options'] || ""
23
- @default_servers = opts['default_servers'] || []
24
- @server_port_override = opts['server_port_override']
18
+ # the haproxy config
19
+ @haproxy = opts['haproxy']
20
+ @haproxy['server_options'] ||= ""
21
+ @haproxy['server_port_override'] ||= nil
22
+ %w{backend frontend listen}.each do |sec|
23
+ @haproxy[sec] ||= []
24
+ end
25
25
 
26
- # set initial backends to default servers
26
+ unless @haproxy.include?('port')
27
+ log.warn "synapse: service #{name}: haproxy config does not include a port; only backend sections for the service will be created; you must move traffic there manually using configuration in `extra_sections`"
28
+ end
29
+
30
+ # set initial backends to default servers, if any
31
+ @default_servers = opts['default_servers'] || []
27
32
  @backends = @default_servers
28
33
 
34
+ # set a flag used to tell the watchers to exit
35
+ # this is not used in every watcher
36
+ @should_exit = false
37
+
29
38
  validate_discovery_opts
30
39
  end
31
40
 
41
+ # this should be overridden to actually start your watcher
32
42
  def start
33
43
  log.info "synapse: starting stub watcher; this means doing nothing at all!"
34
44
  end
35
45
 
46
+ # this should be overridden to actually stop your watcher if necessary
47
+ # if you are running a thread, your loop should run `until @should_exit`
48
+ def stop
49
+ log.info "synapse: stopping watcher #{self.name} using default stop handler"
50
+ @should_exit = true
51
+ end
52
+
53
+ # this should be overridden to do a health check of the watcher
54
+ def ping?
55
+ true
56
+ end
57
+
36
58
  private
37
59
  def validate_discovery_opts
38
60
  raise ArgumentError, "invalid discovery method '#{@discovery['method']}' for base watcher" \
39
- unless @discovery['method'] == 'base'
61
+ unless @discovery['method'] == 'base'
40
62
 
41
63
  log.warn "synapse: warning: a stub watcher with no default servers is pretty useless" if @default_servers.empty?
42
64
  end
@@ -1,15 +1,23 @@
1
- require_relative "./base"
1
+ require "synapse/service_watcher/base"
2
+ require "synapse/log"
2
3
 
3
4
  require 'thread'
4
5
  require 'resolv'
5
6
 
6
7
  module Synapse
7
8
  class DnsWatcher < BaseWatcher
9
+ include Logging
8
10
  def start
9
11
  @check_interval = @discovery['check_interval'] || 30.0
10
12
  @nameserver = @discovery['nameserver']
11
13
 
12
- watch
14
+ @watcher = Thread.new do
15
+ watch
16
+ end
17
+ end
18
+
19
+ def ping?
20
+ !(resolver.getaddresses('airbnb.com').empty?)
13
21
  end
14
22
 
15
23
  private
@@ -21,25 +29,25 @@ module Synapse
21
29
  end
22
30
 
23
31
  def watch
24
- @watcher = Thread.new do
25
- last_resolution = resolve_servers
26
- configure_backends(last_resolution)
27
- while true
28
- begin
29
- start = Time.now
30
- current_resolution = resolve_servers
31
- unless last_resolution == current_resolution
32
- last_resolution = current_resolution
33
- configure_backends(last_resolution)
34
- end
35
-
36
- sleep_until_next_check(start)
37
- rescue => e
38
- log.warn "Error in watcher thread: #{e.inspect}"
39
- log.warn e.backtrace
32
+ last_resolution = resolve_servers
33
+ configure_backends(last_resolution)
34
+ until @should_exit
35
+ begin
36
+ start = Time.now
37
+ current_resolution = resolve_servers
38
+ unless last_resolution == current_resolution
39
+ last_resolution = current_resolution
40
+ configure_backends(last_resolution)
40
41
  end
42
+
43
+ sleep_until_next_check(start)
44
+ rescue => e
45
+ log.warn "Error in watcher thread: #{e.inspect}"
46
+ log.warn e.backtrace
41
47
  end
42
48
  end
49
+
50
+ log.info "synapse: dns watcher exited successfully"
43
51
  end
44
52
 
45
53
  def sleep_until_next_check(start_time)
@@ -72,7 +80,7 @@ module Synapse
72
80
  new_backends = servers.flat_map do |(server, addresses)|
73
81
  addresses.map do |address|
74
82
  {
75
- 'name' => "#{server['name']}-#{[address, server['port']].hash}",
83
+ 'name' => server['name'],
76
84
  'host' => address,
77
85
  'port' => server['port']
78
86
  }
@@ -92,7 +100,7 @@ module Synapse
92
100
  log.info "synapse: discovered #{new_backends.length} backends for service #{@name}"
93
101
  @backends = new_backends
94
102
  end
95
- @synapse.configure
103
+ @synapse.reconfigure!
96
104
  end
97
105
  end
98
106
  end
@@ -0,0 +1,108 @@
1
+ require "synapse/service_watcher/base"
2
+ require 'docker'
3
+
4
+ module Synapse
5
+ class DockerWatcher < BaseWatcher
6
+ def start
7
+ @check_interval = @discovery['check_interval'] || 15.0
8
+ @watcher = Thread.new do
9
+ watch
10
+ end
11
+ end
12
+
13
+ private
14
+ def validate_discovery_opts
15
+ raise ArgumentError, "invalid discovery method #{@discovery['method']}" \
16
+ unless @discovery['method'] == 'docker'
17
+ raise ArgumentError, "a non-empty list of servers is required" \
18
+ if @discovery['servers'].nil? or @discovery['servers'].empty?
19
+ raise ArgumentError, "non-empty image_name required" \
20
+ if @discovery['image_name'].nil? or @discovery['image_name'].empty?
21
+ raise ArgumentError, "container_port required" \
22
+ if @discovery['container_port'].nil?
23
+ end
24
+
25
+ def watch
26
+ last_containers = []
27
+ until @should_exit
28
+ begin
29
+ start = Time.now
30
+ current_containers = containers
31
+ unless last_containers == current_containers
32
+ last_containers = current_containers
33
+ configure_backends(last_containers)
34
+ end
35
+
36
+ sleep_until_next_check(start)
37
+ rescue => e
38
+ log.warn "synapse: error in watcher thread: #{e.inspect}"
39
+ log.warn e.backtrace
40
+ end
41
+ end
42
+
43
+ log.info "synapse: docker watcher exited successfully"
44
+ end
45
+
46
+ def sleep_until_next_check(start_time)
47
+ sleep_time = @check_interval - (Time.now - start_time)
48
+ if sleep_time > 0.0
49
+ sleep(sleep_time)
50
+ end
51
+ end
52
+
53
+ def containers
54
+ backends = @discovery['servers'].map do |server|
55
+ Docker.url = "http://#{server['host']}:#{server['port'] || 4243}"
56
+ begin
57
+ cnts = Docker::Util.parse_json(Docker.connection.get('/containers/json', {}))
58
+ rescue => e
59
+ log.warn "synapse: error polling docker host #{Docker.url}: #{e.inspect}"
60
+ next []
61
+ 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
+ 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]
70
+ end
71
+ # Discover containers that match the image/port we're interested in
72
+ cnts = cnts.find_all do |cnt|
73
+ cnt["Image"].rpartition(":").first == @discovery["image_name"] \
74
+ and cnt["Ports"].has_key?(@discovery["container_port"].to_s())
75
+ end
76
+ cnts.map do |cnt|
77
+ {
78
+ 'name' => server['name'],
79
+ 'host' => server['host'],
80
+ 'port' => cnt["Ports"][@discovery["container_port"].to_s()]
81
+ }
82
+ end
83
+ end
84
+ backends.flatten
85
+ rescue => e
86
+ log.warn "synapse: error while polling for containers: #{e.inspect}"
87
+ []
88
+ end
89
+
90
+ def configure_backends(new_backends)
91
+ if new_backends.empty?
92
+ if @default_servers.empty?
93
+ log.warn "synapse: no backends and no default servers for service #{@name};" \
94
+ " using previous backends: #{@backends.inspect}"
95
+ else
96
+ log.warn "synapse: no backends for service #{@name};" \
97
+ " using default servers: #{@default_servers.inspect}"
98
+ @backends = @default_servers
99
+ end
100
+ else
101
+ log.info "synapse: discovered #{new_backends.length} backends for service #{@name}"
102
+ @backends = new_backends
103
+ end
104
+ @synapse.reconfigure!
105
+ end
106
+
107
+ end
108
+ end
@@ -1,4 +1,4 @@
1
- require_relative "./base"
1
+ require "synapse/service_watcher/base"
2
2
 
3
3
  module Synapse
4
4
  class EC2Watcher < BaseWatcher
@@ -1,21 +1,34 @@
1
- require_relative "./base"
1
+ require "synapse/service_watcher/base"
2
+ require "synapse/log"
2
3
 
3
- require_relative "../../gen-rb/endpoint_types"
4
- require_relative "../../gen-rb/thrift"
5
4
  require 'zk'
6
5
 
7
6
  module Synapse
8
7
  class ZookeeperWatcher < BaseWatcher
8
+ include Logging
9
9
  def start
10
10
  zk_hosts = @discovery['hosts'].shuffle.join(',')
11
11
 
12
12
  log.info "synapse: starting ZK watcher #{@name} @ hosts: #{zk_hosts}, path: #{@discovery['path']}"
13
+ @should_exit = false
13
14
  @zk = ZK.new(zk_hosts)
14
15
 
15
- @deserializer = Thrift::Deserializer.new
16
+ # call the callback to bootstrap the process
17
+ watcher_callback.call
18
+ end
19
+
20
+ def stop
21
+ log.warn "synapse: zookeeper watcher exiting"
22
+
23
+ @should_exit = true
24
+ @watcher.unsubscribe if defined? @watcher
25
+ @zk.close! if defined? @zk
16
26
 
17
- watch
18
- discover
27
+ log.info "synapse: zookeeper watcher cleaned up successfully"
28
+ end
29
+
30
+ def ping?
31
+ @zk.ping?
19
32
  end
20
33
 
21
34
  private
@@ -51,10 +64,9 @@ module Synapse
51
64
  log.error "synapse: invalid data in ZK node #{name} at #{@discovery['path']}"
52
65
  else
53
66
  server_port = @server_port_override ? @server_port_override : port
54
- backend_name = "#{name}-#{[host, server_port].hash}"
55
67
 
56
- log.debug "synapse: discovered backend #{backend_name} at #{host}:#{server_port} for service #{@name}"
57
- new_backends << { 'name' => backend_name, 'host' => host, 'port' => server_port}
68
+ log.debug "synapse: discovered backend #{name} at #{host}:#{server_port} for service #{@name}"
69
+ new_backends << { 'name' => name, 'host' => host, 'port' => server_port}
58
70
  end
59
71
  end
60
72
  rescue ZK::Exceptions::NoNode
@@ -78,19 +90,21 @@ module Synapse
78
90
 
79
91
  # sets up zookeeper callbacks if the data at the discovery path changes
80
92
  def watch
93
+ return if @should_exit
94
+
81
95
  @watcher.unsubscribe if defined? @watcher
82
96
  @watcher = @zk.register(@discovery['path'], &watcher_callback)
83
97
  end
84
98
 
85
99
  # handles the event that a watched path has changed in zookeeper
86
100
  def watcher_callback
87
- Proc.new do |event|
101
+ @callback ||= Proc.new do |event|
88
102
  # Set new watcher
89
103
  watch
90
104
  # Rediscover
91
105
  discover
92
106
  # send a message to calling class to reconfigure
93
- @synapse.configure
107
+ @synapse.reconfigure!
94
108
  end
95
109
  end
96
110
 
@@ -106,27 +120,10 @@ module Synapse
106
120
  return json['host'], json['port']
107
121
  end
108
122
 
109
- # tries to extract a host/port from twitter thrift data
110
- def parse_thrift(data)
111
- begin
112
- service = Twitter::Thrift::ServiceInstance.new
113
- @deserializer.deserialize(service, data)
114
- rescue Object => o
115
- return false
116
- end
117
- raise "instance thrift data does not have host" if service.serviceEndpoint.host.nil?
118
- raise "instance thrift data does not have port" if service.serviceEndpoint.port.nil?
119
- return service.serviceEndpoint.host, service.serviceEndpoint.port
120
- end
121
-
122
123
  # decode the data at a zookeeper endpoint
123
124
  def deserialize_service_instance(data)
124
125
  log.debug "synapse: deserializing process data"
125
126
 
126
- # first, lets try parsing this as thrift
127
- host, port = parse_thrift(data)
128
- return host, port if host
129
-
130
127
  # if that does not work, try json
131
128
  host, port = parse_json(data)
132
129
  return host, port if host
@@ -1,3 +1,3 @@
1
1
  module Synapse
2
- VERSION = "0.2.1"
2
+ VERSION = "0.8.0"
3
3
  end
data/spec/spec_helper.rb CHANGED
@@ -4,9 +4,9 @@
4
4
  # loaded once.
5
5
  #
6
6
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
- require_relative '../lib/synapse'
7
+ require '../lib/synapse'
8
8
  require 'pry'
9
- require_relative 'support/config'
9
+ require 'support/config'
10
10
 
11
11
  RSpec.configure do |config|
12
12
  config.treat_symbols_as_metadata_keys_with_true_values = true