class Synapse::Haproxy
Attributes
opts[R]
Public Class Methods
new(opts)
click to toggle source
Calls superclass method
# File lib/synapse/haproxy.rb, line 507 def initialize(opts) super() %w{global defaults reload_command}.each do |req| raise ArgumentError, "haproxy requires a #{req} section" if !opts.has_key?(req) end req_pairs = { 'do_writes' => 'config_file_path', 'do_socket' => 'socket_file_path', 'do_reloads' => 'reload_command'} req_pairs.each do |cond, req| if opts[cond] raise ArgumentError, "the `#{req}` option is required when `#{cond}` is true" unless opts[req] end end @opts = opts # how to restart haproxy @restart_interval = 2 @restart_required = true @last_restart = Time.new(0) # a place to store the parsed haproxy config from each watcher @watcher_configs = {} end
Public Instance Methods
construct_name(backend)
click to toggle source
used to build unique, consistent haproxy names for backends
# File lib/synapse/haproxy.rb, line 788 def construct_name(backend) name = "#{backend['host']}:#{backend['port']}" if backend['name'] && !backend['name'].empty? name = "#{name}_#{backend['name']}" end return name end
generate_backend_stanza(watcher, config)
click to toggle source
# File lib/synapse/haproxy.rb, line 657 def generate_backend_stanza(watcher, config) if watcher.backends.empty? log.warn "synapse: no backends found for watcher #{watcher.name}" end stanza = [ "\nbackend #{watcher.name}", config.map {|c| "\t#{c}"}, watcher.backends.shuffle.map {|backend| backend_name = construct_name(backend) 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 } ] end
generate_base_config()
click to toggle source
generates the global and defaults sections of the config file
# File lib/synapse/haproxy.rb, line 587 def generate_base_config base_config = ["# auto-generated by synapse at #{Time.now}\n"] %w{global defaults}.each do |section| base_config << "#{section}" @opts[section].each do |option| base_config << "\t#{option}" end end if @opts['extra_sections'] @opts['extra_sections'].each do |title, section| base_config << "\n#{title}" section.each do |option| base_config << "\t#{option}" end end end return base_config end
generate_config(watchers)
click to toggle source
generates a new config based on the state of the watchers
# File lib/synapse/haproxy.rb, line 555 def generate_config(watchers) new_config = generate_base_config shared_frontend_lines = generate_shared_frontend watchers.each do |watcher| @watcher_configs[watcher.name] ||= parse_watcher_config(watcher) new_config << generate_frontend_stanza(watcher, @watcher_configs[watcher.name]['frontend']) new_config << generate_backend_stanza(watcher, @watcher_configs[watcher.name]['backend']) if watcher.haproxy.include?('shared_frontend') if @opts['shared_frontend'] == nil log.warn "synapse: service #{watcher.name} contains a shared frontend section but the base config does not! skipping." else shared_frontend_lines << validate_haproxy_stanza(watcher.haproxy['shared_frontend'].map{|l| "\t#{l}"}, "frontend", "shared frontend section for #{watcher.name}") end end end new_config << shared_frontend_lines.flatten if shared_frontend_lines log.debug "synapse: new haproxy config: #{new_config}" return new_config.flatten.join("\n") end
generate_frontend_stanza(watcher, config)
click to toggle source
generates an individual stanza for a particular watcher
# File lib/synapse/haproxy.rb, line 643 def generate_frontend_stanza(watcher, config) unless watcher.haproxy.has_key?("port") log.debug "synapse: not generating frontend stanza for watcher #{watcher.name} because it has no port defined" return [] end stanza = [ "\nfrontend #{watcher.name}", config.map {|c| "\t#{c}"}, "\tbind #{@opts['bind_address'] || 'localhost'}:#{watcher.haproxy['port']}", "\tdefault_backend #{watcher.name}" ] end
parse_watcher_config(watcher)
click to toggle source
split the haproxy config in each watcher into fields applicable in frontend and backend sections
# File lib/synapse/haproxy.rb, line 611 def parse_watcher_config(watcher) config = {} %w{frontend backend}.each do |section| config[section] = watcher.haproxy[section] || [] # copy over the settings from the 'listen' section that pertain to section config[section].concat( watcher.haproxy['listen'].select {|setting| parsed_setting = setting.strip.gsub(/\s+/, ' ').downcase @@section_fields[section].any? {|field| parsed_setting.start_with?(field)} }) # pick only those fields that are valid and warn about the invalid ones config[section] = validate_haproxy_stanza(config[section], section, watcher.name) end return config end
restart()
click to toggle source
restarts haproxy
# File lib/synapse/haproxy.rb, line 773 def restart # sleep if we restarted too recently delay = (@last_restart - Time.now) + @restart_interval sleep(delay) if delay > 0 # do the actual restart res = `#{opts['reload_command']}`.chomp raise "failed to reload haproxy via #{opts['reload_command']}: #{res}" unless $?.success? log.info "synapse: restarted haproxy" @last_restart = Time.now() @restart_required = false end
update_backends(watchers)
click to toggle source
tries to set active backends via haproxy's stats socket because we can't add backends via the socket, we might still need to restart haproxy
# File lib/synapse/haproxy.rb, line 676 def update_backends(watchers) # first, get a list of existing servers for various backends begin s = UNIXSocket.new(@opts['socket_file_path']) s.write("show stat\n") info = s.read() rescue StandardError => e log.warn "synapse: unhandled error reading stats socket: #{e.inspect}" @restart_required = true return end # parse the stats output to get current backends cur_backends = {} info.split("\n").each do |line| next if line[0] == '#' parts = line.split(',') next if ['FRONTEND', 'BACKEND'].include?(parts[1]) cur_backends[parts[0]] ||= [] cur_backends[parts[0]] << parts[1] end # build a list of backends that should be enabled enabled_backends = {} watchers.each do |watcher| enabled_backends[watcher.name] = [] next if watcher.backends.empty? unless cur_backends.include? watcher.name log.debug "synapse: restart required because we added new section #{watcher.name}" @restart_required = true return end watcher.backends.each do |backend| backend_name = construct_name(backend) unless cur_backends[watcher.name].include? backend_name log.debug "synapse: restart required because we have a new backend #{watcher.name}/#{backend_name}" @restart_required = true return end enabled_backends[watcher.name] << backend_name end end # actually enable the enabled backends, and disable the disabled ones cur_backends.each do |section, backends| backends.each do |backend| if enabled_backends[section].include? backend command = "enable server #{section}/#{backend}\n" else command = "disable server #{section}/#{backend}\n" end # actually write the command to the socket begin s = UNIXSocket.new(@opts['socket_file_path']) s.write(command) output = s.read() rescue StandardError => e log.warn "synapse: unknown error writing to socket" @restart_required = true return else unless output == "\n" log.warn "synapse: socket command #{command} failed: #{output}" @restart_required = true return end end end end log.info "synapse: reconfigured haproxy" end
update_config(watchers)
click to toggle source
# File lib/synapse/haproxy.rb, line 536 def update_config(watchers) # if we support updating backends, try that whenever possible if @opts['do_socket'] update_backends(watchers) unless @restart_required else @restart_required = true end # generate a new config new_config = generate_config(watchers) # if we write config files, lets do that and then possibly restart if @opts['do_writes'] write_config(new_config) restart if @opts['do_reloads'] && @restart_required end end
validate_haproxy_stanza(stanza, stanza_type, service_name)
click to toggle source
# File lib/synapse/haproxy.rb, line 630 def validate_haproxy_stanza(stanza, stanza_type, service_name) return stanza.select {|setting| parsed_setting = setting.strip.gsub(/\s+/, ' ').downcase if @@section_fields[stanza_type].any? {|field| parsed_setting.start_with?(field)} true else log.warn "synapse: service #{service_name} contains invalid #{stanza_type} setting: '#{setting}', discarding" false end } end
write_config(new_config)
click to toggle source
writes the config
# File lib/synapse/haproxy.rb, line 756 def write_config(new_config) begin old_config = File.read(@opts['config_file_path']) rescue Errno::ENOENT => e log.info "synapse: could not open haproxy config file at #{@opts['config_file_path']}" old_config = "" end if old_config == new_config return false else File.open(@opts['config_file_path'],'w') {|f| f.write(new_config)} return true end end