Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion lib/synapse/haproxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,16 @@ def generate_backend_stanza(watcher, config)
# setting the enabled state.
watcher.backends.each do |backend|
backend_name = construct_name(backend)
# If we have information in the state file that allows us to detect
# server option changes, use that to potentially force a restart
if backends.has_key?(backend_name)
old_backend = backends[backend_name]
if (old_backend.fetch('haproxy_server_options', "") !=
backend.fetch('haproxy_server_options', ""))
log.info "synapse: restart required because haproxy_server_options changed for #{backend_name}"
@restart_required = true
end
end
backends[backend_name] = backend.merge('enabled' => true)
end

Expand All @@ -718,7 +728,8 @@ def generate_backend_stanza(watcher, config)
backend = backends[backend_name]
b = "\tserver #{backend_name} #{backend['host']}:#{backend['port']}"
b = "#{b} cookie #{backend_name}" unless config.include?('mode tcp')
b = "#{b} #{watcher.haproxy['server_options']}"
b = "#{b} #{watcher.haproxy['server_options']}" if watcher.haproxy['server_options']
b = "#{b} #{backend['haproxy_server_options']}" if backend['haproxy_server_options']
b = "#{b} disabled" unless backend['enabled']
b }
]
Expand Down
46 changes: 28 additions & 18 deletions lib/synapse/service_watcher/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
## Watcher Classes

Watchers are the piece of Synapse that watch an external service registry
and reflect those changes in the local HAProxy state. Watchers should look
like:
and reflect those changes in the local HAProxy state. Watchers should conform
to the interface specified by `BaseWatcher` and when your watcher has received
an update from the service registry you should call
`set_backends(new_backends)` to trigger a sync of your watcher state with local
HAProxy state. See the [`Backend Interface`](#backend_interface) section for
what service registrations Synapse understands.

```ruby
require "synapse/service\_watcher/base"
require "synapse/service_watcher/base"

module Synapse::ServiceWatcher
class MyWatcher < BaseWatcher
Expand All @@ -26,7 +30,8 @@ module Synapse::ServiceWatcher
# here, validate any required options in @discovery
end

...
... setup watches, poll, etc ... and call set_backends when you have new
... backends to set

end
end
Expand All @@ -38,7 +43,7 @@ the watcher configuration. Every watcher is passed configuration with the
`method` key, e.g. `zookeeper` or `ec2tag`.

#### Class Location
Synapse expects to find your class at `synapse/service\_watcher/#{method}`. You
Synapse expects to find your class at `synapse/service_watcher/#{method}`. You
must make your watcher available at that path, and Synapse can "just work" and
find it.

Expand All @@ -51,24 +56,29 @@ method_class = method.split('_').map{|x| x.capitalize}.join.concat('Watcher')
```

This has the effect of taking the method, splitting on '_', capitalizing each
part and recombining with an added 'Watcher' on the end. So `zookeeper\_dns`
part and recombining with an added 'Watcher' on the end. So `zookeeper_dns`
becomes `ZookeeperDnsWatcher`, and `zookeeper` becomes `Zookeeper`. Make sure
your class name is correct.

### Watcher Class Interface
ServiceWatchers should conform to the interface provided by `BaseWatcher`:
<a name="backend_interface"/>
### Backend interface
Synapse understands the following fields in service backends (which are pulled
from the service registries):

```
start: start the watcher on a service registry
`host` (string): The hostname of the service instance

stop: stop the watcher on a service registry
`port` (integer): The port running the service on `host`

ping?: healthcheck the watcher's connection to the service registry
`name` (string, optional): The human readable name to refer to this service instance by

private
validate_discovery_opts: check if the configuration has the right options
```
`weight` (float, optional): The weight that this backend should get when load
balancing to this service instance. Full support for updating HAProxy based on
this is still a WIP.

When your watcher has received an update from the service registry you should
call `set\_backends(new\_backends)` to trigger a sync of your watcher state
with local HAProxy state.
`haproxy_server_options` (string, optional): Any haproxy server options
specific to this particular server. They will be applied to the generated
`server` line in the HAProxy configuration. If you want Synapse to react to
changes in these lines you will need to enable the `state_file_path` option
in the main synapse configuration. In general the HAProxy backend level
`haproxy.server_options` setting is preferred to setting this per server
in your backends.
13 changes: 13 additions & 0 deletions spec/lib/synapse/haproxy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ class MockWatcher; end;
mockWatcher
end

let(:mockwatcher_with_server_options) do
mockWatcher = double(Synapse::ServiceWatcher)
allow(mockWatcher).to receive(:name).and_return('example_service')
backends = [{ 'host' => 'somehost', 'port' => '5555', 'haproxy_server_options' => 'backup'}]
allow(mockWatcher).to receive(:backends).and_return(backends)
allow(mockWatcher).to receive(:haproxy).and_return({'server_options' => "check inter 2000 rise 3 fall 2"})
mockWatcher
end

it 'updating the config' do
expect(subject).to receive(:generate_config)
subject.update_config([mockwatcher])
Expand All @@ -29,4 +38,8 @@ class MockWatcher; end;
expect(subject.generate_backend_stanza(mockwatcher, mockConfig)).to eql(["\nbackend example_service", ["\tmode tcp"], ["\tserver somehost:5555 somehost:5555 check inter 2000 rise 3 fall 2"]])
end

it 'respects haproxy_server_options' do
mockConfig = []
expect(subject.generate_backend_stanza(mockwatcher_with_server_options, mockConfig)).to eql(["\nbackend example_service", [], ["\tserver somehost:5555 somehost:5555 cookie somehost:5555 check inter 2000 rise 3 fall 2 backup"]])
end
end