diff --git a/README.md b/README.md index b63f9735..1e8c00c5 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,7 @@ This section is its own hash, which should contain the following keys: * `backend_order`: optional: how backends should be ordered in the `backend` stanza. (default is shuffling). Setting to `asc` means sorting backends in ascending alphabetical order before generating stanza. `desc` means descending alphabetical order. `no_shuffle` means no shuffling or sorting. * `shared_frontend`: optional: haproxy configuration directives for a shared http frontend (see below) * `cookie_value_method`: optional: default value is `name`, it defines the way your backends receive a cookie value in http mode. If equal to `hash`, synapse hashes backend names on cookie value assignation of your discovered backends, useful when you want to use haproxy cookie feature but you do not want that your end users receive a Set-Cookie with your server name and ip readable in clear. +* `ignore_weights`: optional: stops haproxy backend 'weight' options being generated, even if the Nerve registrations contain this information. This will cause all backend servers to be treated equally by haproxy. This defaults to true so weights will *NOT* be used by default. ### Configuring HAProxy ### diff --git a/lib/synapse/haproxy.rb b/lib/synapse/haproxy.rb index 439686e4..41f83ed0 100644 --- a/lib/synapse/haproxy.rb +++ b/lib/synapse/haproxy.rb @@ -714,6 +714,11 @@ def generate_backend_stanza(watcher, config) log.info "synapse: restart required because haproxy_server_options changed for #{backend_name}" @restart_required = true end + if (old_backend.fetch('weight', "") != + backend.fetch('weight', "")) + log.info "synapse: restart required because weight changed for #{backend_name}" + @restart_required = true + end end backends[backend_name] = backend.merge('enabled' => true) end @@ -748,6 +753,17 @@ def generate_backend_stanza(watcher, config) end end b = "#{b} #{watcher.haproxy['server_options']}" if watcher.haproxy['server_options'] + if backend.has_key?('weight') + # Check if server_options/haproxy_server_options already contains weight, if so log a warning + if watcher.haproxy.fetch('server_options', '').include? 'weight' + log.warn "synapse: weight is defined in both server_options and nerve. nerve weight takes precedence" + end + if backend['haproxy_server_options'] and backend['haproxy_server_options'].include? 'weight' + log.warn "synapse: weight is defined in both haproxy_server_options and nerve. nerve weight takes precedence" + end + weight = backend['weight'].to_i + b = "#{b} weight #{weight}" + end b = "#{b} #{backend['haproxy_server_options']}" if backend['haproxy_server_options'] b = "#{b} disabled" unless backend['enabled'] b } diff --git a/spec/lib/synapse/haproxy_spec.rb b/spec/lib/synapse/haproxy_spec.rb index 7791dd86..534debfc 100644 --- a/spec/lib/synapse/haproxy_spec.rb +++ b/spec/lib/synapse/haproxy_spec.rb @@ -5,15 +5,18 @@ class MockWatcher; end; describe Synapse::Haproxy do subject { Synapse::Haproxy.new(config['haproxy']) } - let(:mockwatcher) do + def createmockwatcher(backends) mockWatcher = double(Synapse::ServiceWatcher) allow(mockWatcher).to receive(:name).and_return('example_service') - backends = [{ 'host' => 'somehost', 'port' => 5555}] 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 + let(:mockwatcher) do + createmockwatcher [{ 'host' => 'somehost', 'port' => '5555'}] + end + let(:mockwatcher_with_server_options) do mockWatcher = double(Synapse::ServiceWatcher) allow(mockWatcher).to receive(:name).and_return('example_service2') @@ -275,4 +278,24 @@ class MockWatcher; end; expect(subject.generate_frontend_stanza(mockwatcher_frontend_with_bind_address, mockConfig)).to eql(["\nfrontend example_service5", [], "\tbind 127.0.0.3:2200", "\tdefault_backend example_service5"]) end + it 'generates backend stanza with weight' do + mockConfig = [] + expect(subject.generate_backend_stanza(createmockwatcher([{ 'weight' => 1, 'host' => 'somehost', 'port' => '5555'}]), mockConfig)).to eql(["\nbackend example_service", [], ["\tserver somehost:5555 somehost:5555 cookie somehost:5555 check inter 2000 rise 3 fall 2 weight 1"]]) + end + + it 'generates backend stanza with bad weight = 0' do + mockConfig = [] + expect(subject.generate_backend_stanza(createmockwatcher([{ 'weight' => 'hi', 'host' => 'somehost', 'port' => '5555'}]), mockConfig)).to eql(["\nbackend example_service", [], ["\tserver somehost:5555 somehost:5555 cookie somehost:5555 check inter 2000 rise 3 fall 2 weight 0"]]) + end + + it 'generates backend stanza with nil weight = 0' do + mockConfig = [] + expect(subject.generate_backend_stanza(createmockwatcher([{ 'weight' => nil, 'host' => 'somehost', 'port' => '5555'}]), mockConfig)).to eql(["\nbackend example_service", [], ["\tserver somehost:5555 somehost:5555 cookie somehost:5555 check inter 2000 rise 3 fall 2 weight 0"]]) + end + + it 'generates backend stanza without weight' do + mockConfig = [] + expect(subject.generate_backend_stanza(createmockwatcher([{ 'host' => 'somehost', 'port' => '5555'}]), mockConfig)).to eql(["\nbackend example_service", [], ["\tserver somehost:5555 somehost:5555 cookie somehost:5555 check inter 2000 rise 3 fall 2"]]) + end + end diff --git a/spec/lib/synapse/service_watcher_base_spec.rb b/spec/lib/synapse/service_watcher_base_spec.rb index da317470..42621575 100644 --- a/spec/lib/synapse/service_watcher_base_spec.rb +++ b/spec/lib/synapse/service_watcher_base_spec.rb @@ -135,5 +135,37 @@ def remove_arg(name) expect(subject.backends).to eq(matching_labeled_backends) end end + + context 'with weights defined' do + let(:backends_with_weight) { [ + { 'name' => 'server1', 'host' => 'server1', 'port' => 1111, 'weight' => 11 }, + { 'name' => 'server2', 'host' => 'server2', 'port' => 2222, 'weight' => 22 }, + ] } + let(:non_matching_weight_backends) { [ + { 'name' => 'server1', 'host' => 'server1', 'port' => 1111, 'weight' => 33 }, + { 'name' => 'server2', 'host' => 'server2', 'port' => 2222, 'weight' => 22 }, + ] } + let(:backends_with_non_matching_weight_duplicates) { [ + { 'name' => 'server1', 'host' => 'server1', 'port' => 1111, 'weight' => 11 }, + { 'name' => 'server1', 'host' => 'server1', 'port' => 1111, 'weight' => 33 }, + { 'name' => 'server2', 'host' => 'server2', 'port' => 2222, 'weight' => 22 }, + ] } + it 'updates backends only when weights change' do + expect(subject).to receive(:'reconfigure!').exactly(:twice) + expect(subject.send(:set_backends, backends_with_weight)).to equal(true) + expect(subject.backends).to eq(backends_with_weight) + expect(subject.send(:set_backends, non_matching_weight_backends)).to equal(true) + expect(subject.backends).to eq(non_matching_weight_backends) + expect(subject.send(:set_backends, non_matching_weight_backends)).to equal(false) + expect(subject.backends).to eq(non_matching_weight_backends) + end + it 'ignores duplicates even with non-matching weights' do + expect(subject).to receive(:'reconfigure!').exactly(:once) + expect(subject.send(:set_backends, backends_with_weight)).to equal(true) + expect(subject.backends).to eq(backends_with_weight) + expect(subject.send(:set_backends, backends_with_non_matching_weight_duplicates)).to equal(false) + expect(subject.backends).to eq(backends_with_weight) + end + end end end