Skip to content

Commit

Permalink
Merge pull request #130 from Yelp/merge_the_forks
Browse files Browse the repository at this point in the history
Merge the forks
  • Loading branch information
jnb committed Jul 23, 2015
2 parents 142b257 + 6f11a82 commit 3e36e88
Show file tree
Hide file tree
Showing 13 changed files with 384 additions and 201 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ GEM
slyphon-zookeeper_jar (3.3.5-java)
spoon (0.0.4)
ffi
zk (1.9.4)
zk (1.9.5)
logging (~> 1.8.2)
zookeeper (~> 1.4.0)
zookeeper (1.4.10)
Expand Down
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,19 @@ If you do not list any default servers, no proxy will be created. The
`default_servers` will also be used in addition to discovered servers if the
`keep_default_servers` option is set.

If you do not list any `default_servers`, and all backends for a service
disappear then the previous known backends will be used. Disable this behavior
by unsetting `use_previous_backends`.

#### The `file_output` Section ####

This section controls whether or not synapse will write out service state
to the filesystem in json format. This can be used for services that want to
use discovery information but not go through HAProxy.

* `output_directory`: the path to a directory on disk that service registrations
should be written to.

#### The `haproxy` Section ####

This section is its own hash, which should contain the following keys:
Expand All @@ -218,11 +231,20 @@ The `haproxy` section of the config file has the following options:
* `config_file_path`: where Synapse will write the HAProxy config file
* `do_writes`: whether or not the config file will be written (default to `true`)
* `do_reloads`: whether or not Synapse will reload HAProxy (default to `true`)
* `do_socket`: whether or not Synapse will use the HAProxy socket commands to prevent reloads (default to `true`)
* `global`: options listed here will be written into the `global` section of the HAProxy config
* `defaults`: options listed here will be written into the `defaults` section of the HAProxy config
* `extra_sections`: additional, manually-configured `frontend`, `backend`, or `listen` stanzas
* `bind_address`: force HAProxy to listen on this address (default is localhost)
* `shared_frontend`: (OPTIONAL) additional lines passed to the HAProxy config used to configure a shared HTTP frontend (see below)
* `restart_interval`: number of seconds to wait between restarts of haproxy (default: 2)
* `restart_jitter`: percentage, expressed as a float, of jitter to multiply the `restart_interval` by when determining the next
restart time. Use this to help prevent healthcheck storms when HAProxy restarts. (default: 0.0)
* `state_file_path`: full path on disk (e.g. /tmp/synapse/state.json) for caching haproxy state between reloads.
If provided, synapse will store recently seen backends at this location and can "remember" backends across both synapse and
HAProxy restarts. Any backends that are "down" in the reporter but listed in the cache will be put into HAProxy disabled (default: nil)
* `state_file_ttl`: the number of seconds that backends should be kept in the state file cache.
This only applies if `state_file_path` is provided (default: 86400)

Note that a non-default `bind_address` can be dangerous.
If you configure an `address:port` combination that is already in use on the system, haproxy will fail to start.
Expand Down Expand Up @@ -335,5 +357,5 @@ end
3. Implement the `start` and `validate_discovery_opts` methods
4. Implement whatever additional methods your discovery requires

When your watcher detects a list of new backends, they should be written to `@backends`.
You should then call `@synapse.configure` to force synapse to update the HAProxy config.
When your watcher detects a list of new backends, you should call `set_backends` to
store the new backends and update the HAProxy config.
26 changes: 20 additions & 6 deletions lib/synapse.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "synapse/version"
require "synapse/service_watcher/base"
require "synapse/haproxy"
require "synapse/file_output"
require "synapse/service_watcher"
require "synapse/log"

Expand All @@ -17,9 +18,17 @@ def initialize(opts={})
raise "specify a list of services to connect in the config" unless opts.has_key?('services')
@service_watchers = create_service_watchers(opts['services'])

# create the haproxy object
# create objects that need to be notified of service changes
@config_generators = []
# create the haproxy config generator, this is mandatory
raise "haproxy config section is missing" unless opts.has_key?('haproxy')
@haproxy = Haproxy.new(opts['haproxy'])
@config_generators << Haproxy.new(opts['haproxy'])

# possibly create a file manifestation for services that do not
# want to communicate via haproxy, e.g. cassandra
if opts.has_key?('file_output')
@config_generators << FileOutput.new(opts['file_output'])
end

# configuration is initially enabled to configure on first loop
@config_updated = true
Expand Down Expand Up @@ -47,10 +56,15 @@ def run

if @config_updated
@config_updated = false
log.info "synapse: regenerating haproxy config"
@haproxy.update_config(@service_watchers)
else
sleep 1
@config_generators.each do |config_generator|
log.info "synapse: configuring #{config_generator.name}"
config_generator.update_config(@service_watchers)
end
end

sleep 1
@config_generators.each do |config_generator|
config_generator.tick(@service_watchers)
end

loops += 1
Expand Down
57 changes: 57 additions & 0 deletions lib/synapse/file_output.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require 'synapse/log'
require 'fileutils'
require 'tempfile'

module Synapse
class FileOutput
include Logging
attr_reader :opts, :name

def initialize(opts)
unless opts.has_key?("output_directory")
raise ArgumentError, "flat file generation requires an output_directory key"
end

begin
FileUtils.mkdir_p(opts['output_directory'])
rescue SystemCallError => err
raise ArgumentError, "provided output directory #{opts['output_directory']} is not present or creatable"
end

@opts = opts
@name = 'file_output'
end

def tick(watchers)
end

def update_config(watchers)
watchers.each do |watcher|
write_backends_to_file(watcher.name, watcher.backends)
end
end

def write_backends_to_file(service_name, new_backends)
data_path = File.join(@opts['output_directory'], "#{service_name}.json")
begin
old_backends = JSON.load(File.read(data_path))
rescue Errno::ENOENT
old_backends = nil
end

if old_backends == new_backends
# Prevent modifying the file unless something has actually changed
# This way clients can set watches on this file and update their
# internal state only when the smartstack state has actually changed
return false
else
# Atomically write new sevice configuration file
temp_path = File.join(@opts['output_directory'],
".#{service_name}.json.tmp")
File.open(temp_path, 'w', 0644) {|f| f.write(new_backends.to_json)}
FileUtils.mv(temp_path, data_path)
return true
end
end
end
end
Loading

0 comments on commit 3e36e88

Please sign in to comment.