Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weights support in Synapse #179

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<a name="haproxy"/>
### Configuring HAProxy ###
Expand Down
16 changes: 16 additions & 0 deletions lib/synapse/haproxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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', "") !=
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, it looks like this section of code is only invoked if the state file is enabled. i think this needs a little re-thinking...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We detect a restart as long as the output config file is different: https://github.com/airbnb/synapse/blob/master/lib/synapse/haproxy.rb#L844. So you're right to point out this block of code as being out-of-place. I'm writing some tests to see if we even need any of the logic here.

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
Expand Down Expand Up @@ -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'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately, due to how the parsing logic in ServiceWatcher works today, haproxy_server_options always exists as a key in backend and defaults to value nil

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 }
Expand Down
27 changes: 25 additions & 2 deletions spec/lib/synapse/haproxy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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
32 changes: 32 additions & 0 deletions spec/lib/synapse/service_watcher_base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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