diff --git a/lib/synapse/haproxy.rb b/lib/synapse/haproxy.rb index 94adbaca..2d2d577a 100644 --- a/lib/synapse/haproxy.rb +++ b/lib/synapse/haproxy.rb @@ -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 @@ -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 } ] diff --git a/lib/synapse/service_watcher/README.md b/lib/synapse/service_watcher/README.md index 9fb8c132..a554d8cc 100644 --- a/lib/synapse/service_watcher/README.md +++ b/lib/synapse/service_watcher/README.md @@ -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 @@ -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 @@ -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. @@ -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`: + +### 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. diff --git a/spec/lib/synapse/haproxy_spec.rb b/spec/lib/synapse/haproxy_spec.rb index a90d2ec7..5e87e296 100644 --- a/spec/lib/synapse/haproxy_spec.rb +++ b/spec/lib/synapse/haproxy_spec.rb @@ -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]) @@ -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