Class: Synapse::Haproxy

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/synapse/haproxy.rb

Constant Summary collapse

SECTION_FIELDS =
{
  "backend" => [
    "acl",
    "appsession",
    "balance",
    "bind-process",
    "block",
    "compression",
    "contimeout",
    "cookie",
    "default-server",
    "description",
    "disabled",
    "dispatch",
    "email-alert from",
    "email-alert level",
    "email-alert mailers",
    "email-alert myhostname",
    "email-alert to",
    "enabled",
    "errorfile",
    "errorloc",
    "errorloc302",
    "errorloc303",
    "force-persist",
    "fullconn",
    "grace",
    "hash-type",
    "http-check disable-on-404",
    "http-check expect",
    "http-check send-state",
    "http-request",
    "http-response",
    "http-send-name-header",
    "http-reuse",
    "http-send-name-header",
    "id",
    "ignore-persist",
    "load-server-state-from-file",
    "log",
    "log-tag",
    "max-keep-alive-queue",
    "mode",
    "no log",
    "no option abortonclose",
    "no option accept-invalid-http-response",
    "no option allbackups",
    "no option allredisp",
    "no option checkcache",
    "no option forceclose",
    "no option forwardfor",
    "no option http-buffer-request",
    "no option http-keep-alive",
    "no option http-no-delay",
    "no option http-pretend-keepalive",
    "no option http-server-close",
    "no option http-tunnel",
    "no option httpchk",
    "no option httpclose",
    "no option httplog",
    "no option http_proxy",
    "no option independent-streams",
    "no option lb-agent-chk",
    "no option ldap-check",
    "no option external-check",
    "no option log-health-checks",
    "no option mysql-check",
    "no option pgsql-check",
    "no option nolinger",
    "no option originalto",
    "no option persist",
    "no option pgsql-check",
    "no option prefer-last-server",
    "no option redispatch",
    "no option redis-check",
    "no option smtpchk",
    "no option splice-auto",
    "no option splice-request",
    "no option splice-response",
    "no option srvtcpka",
    "no option ssl-hello-chk",
    "no option tcp-check",
    "no option tcp-smart-connect",
    "no option tcpka",
    "no option tcplog",
    "no option transparent",
    "option abortonclose",
    "option accept-invalid-http-response",
    "option allbackups",
    "option allredisp",
    "option checkcache",
    "option forceclose",
    "option forwardfor",
    "option http-buffer-request",
    "option http-keep-alive",
    "option http-no-delay",
    "option http-pretend-keepalive",
    "option http-server-close",
    "option http-tunnel",
    "option httpchk",
    "option httpclose",
    "option httplog",
    "option http_proxy",
    "option independent-streams",
    "option lb-agent-chk",
    "option ldap-check",
    "option external-check",
    "option log-health-checks",
    "option mysql-check",
    "option pgsql-check",
    "option nolinger",
    "option originalto",
    "option persist",
    "option pgsql-check",
    "option prefer-last-server",
    "option redispatch",
    "option redis-check",
    "option smtpchk",
    "option splice-auto",
    "option splice-request",
    "option splice-response",
    "option srvtcpka",
    "option ssl-hello-chk",
    "option tcp-check",
    "option tcp-smart-connect",
    "option tcpka",
    "option tcplog",
    "option transparent",
    "external-check command",
    "external-check path",
    "persist rdp-cookie",
    "redirect",
    "redisp",
    "redispatch",
    "reqadd",
    "reqallow",
    "reqdel",
    "reqdeny",
    "reqiallow",
    "reqidel",
    "reqideny",
    "reqipass",
    "reqirep",
    "reqisetbe",
    "reqitarpit",
    "reqpass",
    "reqrep",
    "reqsetbe",
    "reqtarpit",
    "retries",
    "rspadd",
    "rspdel",
    "rspdeny",
    "rspidel",
    "rspideny",
    "rspirep",
    "rsprep",
    "server",
    "server-state-file-name",
    "source",
    "srvtimeout",
    "stats admin",
    "stats auth",
    "stats enable",
    "stats hide-version",
    "stats http-request",
    "stats realm",
    "stats refresh",
    "stats scope",
    "stats show-desc",
    "stats show-legends",
    "stats show-node",
    "stats uri",
    "stick match",
    "stick on",
    "stick store-request",
    "stick store-response",
    "stick-table",
    "tcp-check connect",
    "tcp-check expect",
    "tcp-check send",
    "tcp-check send-binary",
    "tcp-request content",
    "tcp-request inspect-delay",
    "tcp-response content",
    "tcp-response inspect-delay",
    "timeout check",
    "timeout connect",
    "timeout contimeout",
    "timeout http-keep-alive",
    "timeout http-request",
    "timeout queue",
    "timeout server",
    "timeout server-fin",
    "timeout srvtimeout",
    "timeout tarpit",
    "timeout tunnel",
    "transparent",
    "use-server"
  ],
  "defaults" => [
    "backlog",
    "balance",
    "bind-process",
    "clitimeout",
    "compression",
    "contimeout",
    "cookie",
    "default-server",
    "default_backend",
    "disabled",
    "email-alert from",
    "email-alert level",
    "email-alert mailers",
    "email-alert myhostname",
    "email-alert to",
    "enabled",
    "errorfile",
    "errorloc",
    "errorloc302",
    "errorloc303",
    "fullconn",
    "grace",
    "hash-type",
    "http-check disable-on-404",
    "http-check send-state",
    "http-reuse",
    "load-server-state-from-file",
    "log",
    "log-format",
    "log-format-sd",
    "log-tag",
    "max-keep-alive-queue",
    "maxconn",
    "mode",
    "monitor-net",
    "monitor-uri",
    "no log",
    "no option abortonclose",
    "no option accept-invalid-http-request",
    "no option accept-invalid-http-response",
    "no option allbackups",
    "no option allredisp",
    "no option checkcache",
    "no option clitcpka",
    "no option contstats",
    "no option dontlog-normal",
    "no option dontlognull",
    "no option forceclose",
    "no option forwardfor",
    "no option http-buffer-request",
    "no option http-ignore-probes",
    "no option http-keep-alive",
    "no option http-no-delay",
    "no option http-pretend-keepalive",
    "no option http-server-close",
    "no option http-tunnel",
    "no option http-use-proxy-header",
    "no option httpchk",
    "no option httpclose",
    "no option httplog",
    "no option http_proxy",
    "no option independent-streams",
    "no option lb-agent-chk",
    "no option ldap-check",
    "no option external-check",
    "no option log-health-checks",
    "no option log-separate-errors",
    "no option logasap",
    "no option mysql-check",
    "no option pgsql-check",
    "no option nolinger",
    "no option originalto",
    "no option persist",
    "no option pgsql-check",
    "no option prefer-last-server",
    "no option redispatch",
    "no option redis-check",
    "no option smtpchk",
    "no option socket-stats",
    "no option splice-auto",
    "no option splice-request",
    "no option splice-response",
    "no option srvtcpka",
    "no option ssl-hello-chk",
    "no option tcp-check",
    "no option tcp-smart-accept",
    "no option tcp-smart-connect",
    "no option tcpka",
    "no option tcplog",
    "no option transparent",
    "option abortonclose",
    "option accept-invalid-http-request",
    "option accept-invalid-http-response",
    "option allbackups",
    "option allredisp",
    "option checkcache",
    "option clitcpka",
    "option contstats",
    "option dontlog-normal",
    "option dontlognull",
    "option forceclose",
    "option forwardfor",
    "option http-buffer-request",
    "option http-ignore-probes",
    "option http-keep-alive",
    "option http-no-delay",
    "option http-pretend-keepalive",
    "option http-server-close",
    "option http-tunnel",
    "option http-use-proxy-header",
    "option httpchk",
    "option httpclose",
    "option httplog",
    "option http_proxy",
    "option independent-streams",
    "option lb-agent-chk",
    "option ldap-check",
    "option external-check",
    "option log-health-checks",
    "option log-separate-errors",
    "option logasap",
    "option mysql-check",
    "option pgsql-check",
    "option nolinger",
    "option originalto",
    "option persist",
    "option pgsql-check",
    "option prefer-last-server",
    "option redispatch",
    "option redis-check",
    "option smtpchk",
    "option socket-stats",
    "option splice-auto",
    "option splice-request",
    "option splice-response",
    "option srvtcpka",
    "option ssl-hello-chk",
    "option tcp-check",
    "option tcp-smart-accept",
    "option tcp-smart-connect",
    "option tcpka",
    "option tcplog",
    "option transparent",
    "external-check command",
    "external-check path",
    "persist rdp-cookie",
    "rate-limit sessions",
    "redisp",
    "redispatch",
    "retries",
    "server-state-file-name",
    "source",
    "srvtimeout",
    "stats auth",
    "stats enable",
    "stats hide-version",
    "stats realm",
    "stats refresh",
    "stats scope",
    "stats show-desc",
    "stats show-legends",
    "stats show-node",
    "stats uri",
    "timeout check",
    "timeout client",
    "timeout client-fin",
    "timeout clitimeout",
    "timeout connect",
    "timeout contimeout",
    "timeout http-keep-alive",
    "timeout http-request",
    "timeout queue",
    "timeout server",
    "timeout server-fin",
    "timeout srvtimeout",
    "timeout tarpit",
    "timeout tunnel",
    "transparent",
    "unique-id-format",
    "unique-id-header"
  ],
  "frontend" => [
    "acl",
    "backlog",
    "bind",
    "bind-process",
    "block",
    "capture cookie",
    "capture request header",
    "capture response header",
    "clitimeout",
    "compression",
    "declare capture",
    "default_backend",
    "description",
    "disabled",
    "email-alert from",
    "email-alert level",
    "email-alert mailers",
    "email-alert myhostname",
    "email-alert to",
    "enabled",
    "errorfile",
    "errorloc",
    "errorloc302",
    "errorloc303",
    "force-persist",
    "grace",
    "http-request",
    "http-response",
    "id",
    "ignore-persist",
    "log",
    "log-format",
    "log-format-sd",
    "log-tag",
    "maxconn",
    "mode",
    "monitor fail",
    "monitor-net",
    "monitor-uri",
    "no log",
    "no option accept-invalid-http-request",
    "no option clitcpka",
    "no option contstats",
    "no option dontlog-normal",
    "no option dontlognull",
    "no option forceclose",
    "no option forwardfor",
    "no option http-buffer-request",
    "no option http-ignore-probes",
    "no option http-keep-alive",
    "no option http-no-delay",
    "no option http-pretend-keepalive",
    "no option http-server-close",
    "no option http-tunnel",
    "no option http-use-proxy-header",
    "no option httpclose",
    "no option httplog",
    "no option http_proxy",
    "no option independent-streams",
    "no option log-separate-errors",
    "no option logasap",
    "no option nolinger",
    "no option originalto",
    "no option socket-stats",
    "no option splice-auto",
    "no option splice-request",
    "no option splice-response",
    "no option tcp-smart-accept",
    "no option tcpka",
    "no option tcplog",
    "option accept-invalid-http-request",
    "option clitcpka",
    "option contstats",
    "option dontlog-normal",
    "option dontlognull",
    "option forceclose",
    "option forwardfor",
    "option http-buffer-request",
    "option http-ignore-probes",
    "option http-keep-alive",
    "option http-no-delay",
    "option http-pretend-keepalive",
    "option http-server-close",
    "option http-tunnel",
    "option http-use-proxy-header",
    "option httpclose",
    "option httplog",
    "option http_proxy",
    "option independent-streams",
    "option log-separate-errors",
    "option logasap",
    "option nolinger",
    "option originalto",
    "option socket-stats",
    "option splice-auto",
    "option splice-request",
    "option splice-response",
    "option tcp-smart-accept",
    "option tcpka",
    "option tcplog",
    "rate-limit sessions",
    "redirect",
    "reqadd",
    "reqallow",
    "reqdel",
    "reqdeny",
    "reqiallow",
    "reqidel",
    "reqideny",
    "reqipass",
    "reqirep",
    "reqisetbe",
    "reqitarpit",
    "reqpass",
    "reqrep",
    "reqsetbe",
    "reqtarpit",
    "rspadd",
    "rspdel",
    "rspdeny",
    "rspidel",
    "rspideny",
    "rspirep",
    "rsprep",
    "stats admin",
    "stats auth",
    "stats enable",
    "stats hide-version",
    "stats http-request",
    "stats realm",
    "stats refresh",
    "stats scope",
    "stats show-desc",
    "stats show-legends",
    "stats show-node",
    "stats uri",
    "tcp-request connection",
    "tcp-request content",
    "tcp-request inspect-delay",
    "timeout client",
    "timeout client-fin",
    "timeout clitimeout",
    "timeout http-keep-alive",
    "timeout http-request",
    "timeout tarpit",
    "unique-id-format",
    "unique-id-header",
    "use_backend"
  ],
  "listen" => [
    "acl",
    "appsession",
    "backlog",
    "balance",
    "bind",
    "bind-process",
    "block",
    "capture cookie",
    "capture request header",
    "capture response header",
    "clitimeout",
    "compression",
    "contimeout",
    "cookie",
    "declare capture",
    "default-server",
    "default_backend",
    "description",
    "disabled",
    "dispatch",
    "email-alert from",
    "email-alert level",
    "email-alert mailers",
    "email-alert myhostname",
    "email-alert to",
    "enabled",
    "errorfile",
    "errorloc",
    "errorloc302",
    "errorloc303",
    "force-persist",
    "fullconn",
    "grace",
    "hash-type",
    "http-check disable-on-404",
    "http-check expect",
    "http-check send-state",
    "http-request",
    "http-response",
    "http-send-name-header",
    "http-reuse",
    "http-send-name-header",
    "id",
    "ignore-persist",
    "load-server-state-from-file",
    "log",
    "log-format",
    "log-format-sd",
    "log-tag",
    "max-keep-alive-queue",
    "maxconn",
    "mode",
    "monitor fail",
    "monitor-net",
    "monitor-uri",
    "no log",
    "no option abortonclose",
    "no option accept-invalid-http-request",
    "no option accept-invalid-http-response",
    "no option allbackups",
    "no option allredisp",
    "no option checkcache",
    "no option clitcpka",
    "no option contstats",
    "no option dontlog-normal",
    "no option dontlognull",
    "no option forceclose",
    "no option forwardfor",
    "no option http-buffer-request",
    "no option http-ignore-probes",
    "no option http-keep-alive",
    "no option http-no-delay",
    "no option http-pretend-keepalive",
    "no option http-server-close",
    "no option http-tunnel",
    "no option http-use-proxy-header",
    "no option httpchk",
    "no option httpclose",
    "no option httplog",
    "no option http_proxy",
    "no option independent-streams",
    "no option lb-agent-chk",
    "no option ldap-check",
    "no option external-check",
    "no option log-health-checks",
    "no option log-separate-errors",
    "no option logasap",
    "no option mysql-check",
    "no option pgsql-check",
    "no option nolinger",
    "no option originalto",
    "no option persist",
    "no option pgsql-check",
    "no option prefer-last-server",
    "no option redispatch",
    "no option redis-check",
    "no option smtpchk",
    "no option socket-stats",
    "no option splice-auto",
    "no option splice-request",
    "no option splice-response",
    "no option srvtcpka",
    "no option ssl-hello-chk",
    "no option tcp-check",
    "no option tcp-smart-accept",
    "no option tcp-smart-connect",
    "no option tcpka",
    "no option tcplog",
    "no option transparent",
    "option abortonclose",
    "option accept-invalid-http-request",
    "option accept-invalid-http-response",
    "option allbackups",
    "option allredisp",
    "option checkcache",
    "option clitcpka",
    "option contstats",
    "option dontlog-normal",
    "option dontlognull",
    "option forceclose",
    "option forwardfor",
    "option http-buffer-request",
    "option http-ignore-probes",
    "option http-keep-alive",
    "option http-no-delay",
    "option http-pretend-keepalive",
    "option http-server-close",
    "option http-tunnel",
    "option http-use-proxy-header",
    "option httpchk",
    "option httpclose",
    "option httplog",
    "option http_proxy",
    "option independent-streams",
    "option lb-agent-chk",
    "option ldap-check",
    "option external-check",
    "option log-health-checks",
    "option log-separate-errors",
    "option logasap",
    "option mysql-check",
    "option pgsql-check",
    "option nolinger",
    "option originalto",
    "option persist",
    "option pgsql-check",
    "option prefer-last-server",
    "option redispatch",
    "option redis-check",
    "option smtpchk",
    "option socket-stats",
    "option splice-auto",
    "option splice-request",
    "option splice-response",
    "option srvtcpka",
    "option ssl-hello-chk",
    "option tcp-check",
    "option tcp-smart-accept",
    "option tcp-smart-connect",
    "option tcpka",
    "option tcplog",
    "option transparent",
    "external-check command",
    "external-check path",
    "persist rdp-cookie",
    "rate-limit sessions",
    "redirect",
    "redisp",
    "redispatch",
    "reqadd",
    "reqallow",
    "reqdel",
    "reqdeny",
    "reqiallow",
    "reqidel",
    "reqideny",
    "reqipass",
    "reqirep",
    "reqisetbe",
    "reqitarpit",
    "reqpass",
    "reqrep",
    "reqsetbe",
    "reqtarpit",
    "retries",
    "rspadd",
    "rspdel",
    "rspdeny",
    "rspidel",
    "rspideny",
    "rspirep",
    "rsprep",
    "server",
    "server-state-file-name",
    "source",
    "srvtimeout",
    "stats admin",
    "stats auth",
    "stats enable",
    "stats hide-version",
    "stats http-request",
    "stats realm",
    "stats refresh",
    "stats scope",
    "stats show-desc",
    "stats show-legends",
    "stats show-node",
    "stats uri",
    "stick match",
    "stick on",
    "stick store-request",
    "stick store-response",
    "stick-table",
    "tcp-check connect",
    "tcp-check expect",
    "tcp-check send",
    "tcp-check send-binary",
    "tcp-request connection",
    "tcp-request content",
    "tcp-request inspect-delay",
    "tcp-response content",
    "tcp-response inspect-delay",
    "timeout check",
    "timeout client",
    "timeout client-fin",
    "timeout clitimeout",
    "timeout connect",
    "timeout contimeout",
    "timeout http-keep-alive",
    "timeout http-request",
    "timeout queue",
    "timeout server",
    "timeout server-fin",
    "timeout srvtimeout",
    "timeout tarpit",
    "timeout tunnel",
    "transparent",
    "unique-id-format",
    "unique-id-header",
    "use_backend",
    "use-server"
  ]
}.freeze
DEFAULT_STATE_FILE_TTL =

24 hours

(60 * 60 * 24).freeze
STATE_FILE_UPDATE_INTERVAL =

iterations; not a unit of time

60.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logging

configure_logger_for, #log, logger_for

Constructor Details

#initialize(opts) ⇒ Haproxy

Returns a new instance of Haproxy.



794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
# File 'lib/synapse/haproxy.rb', line 794

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

  @opts['do_writes'] = true unless @opts.key?('do_writes')
  @opts['do_socket'] = true unless @opts.key?('do_socket')
  @opts['do_reloads'] = true unless @opts.key?('do_reloads')

  # socket_file_path can be a string or a list
  # lets make a new option which is always a list (plural)
  @opts['socket_file_paths'] = [@opts['socket_file_path']].flatten

  # how to restart haproxy
  @restart_interval = @opts.fetch('restart_interval', 2).to_i
  @restart_jitter = @opts.fetch('restart_jitter', 0).to_f
  @restart_required = true

  # virtual clock bookkeeping for controlling how often haproxy restarts
  @time = 0
  @next_restart = @time

  # a place to store the parsed haproxy config from each watcher
  @watcher_configs = {}

  @state_file_path = @opts['state_file_path']
  @state_file_ttl = @opts.fetch('state_file_ttl', DEFAULT_STATE_FILE_TTL).to_i
end

Instance Attribute Details

#optsObject (readonly)

Returns the value of attribute opts.



9
10
11
# File 'lib/synapse/haproxy.rb', line 9

def opts
  @opts
end

Instance Method Details

#construct_name(backend) ⇒ Object

used to build unique, consistent haproxy names for backends



1164
1165
1166
1167
1168
1169
1170
1171
# File 'lib/synapse/haproxy.rb', line 1164

def construct_name(backend)
  name = "#{backend['host']}:#{backend['port']}"
  if backend['name'] && !backend['name'].empty?
    name = "#{backend['name']}_#{name}"
  end

  return name
end

#generate_backend_stanza(watcher, config) ⇒ Object



977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
# File 'lib/synapse/haproxy.rb', line 977

def generate_backend_stanza(watcher, config)
  backends = {}

  # The ordering here is important.  First we add all the backends in the
  # disabled state...
  seen.fetch(watcher.name, []).each do |backend_name, backend|
    backends[backend_name] = backend.merge('enabled' => false)
  end

  # ... and then we overwite any backends that the watchers know about,
  # 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

  if watcher.backends.empty?
    log.debug "synapse: no backends found for watcher #{watcher.name}"
  end

  keys = case watcher.haproxy['backend_order']
  when 'asc'
    backends.keys.sort
  when 'desc'
    backends.keys.sort.reverse
  when 'no_shuffle'
    backends.keys
  else
    backends.keys.shuffle
  end

  stanza = [
    "\nbackend #{watcher.haproxy.fetch('backend_name', watcher.name)}",
    config.map {|c| "\t#{c}"},
    keys.map {|backend_name|
      backend = backends[backend_name]
      b = "\tserver #{backend_name} #{backend['host']}:#{backend['port']}"
      unless config.include?('mode tcp')
        b = case watcher.haproxy['cookie_value_method']
        when 'hash'
          b = "#{b} cookie #{Digest::SHA1.hexdigest(backend_name)}"
        else
          b = "#{b} cookie #{backend_name}"
        end
      end
      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 }
  ]
end

#generate_base_configObject

generates the global and defaults sections of the config file



907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
# File 'lib/synapse/haproxy.rb', line 907

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) ⇒ Object

generates a new config based on the state of the watchers



875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
# File 'lib/synapse/haproxy.rb', line 875

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) ⇒ Object

generates an individual stanza for a particular watcher



963
964
965
966
967
968
969
970
971
972
973
974
975
# File 'lib/synapse/haproxy.rb', line 963

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 #{ watcher.haproxy['bind_address'] || @opts['bind_address'] || 'localhost'}:#{watcher.haproxy['port']}",
    "\tdefault_backend #{watcher.haproxy.fetch('backend_name', watcher.name)}"
  ]
end

#generate_shared_frontendObject

pull out the shared frontend section if any



898
899
900
901
902
903
904
# File 'lib/synapse/haproxy.rb', line 898

def generate_shared_frontend
  return nil unless @opts.include?('shared_frontend')
  log.debug "synapse: found a shared frontend section"
  shared_frontend_lines = ["\nfrontend shared-frontend"]
  shared_frontend_lines << validate_haproxy_stanza(@opts['shared_frontend'].map{|l| "\t#{l}"}, "frontend", "shared frontend")
  return shared_frontend_lines
end

#nameObject



838
839
840
# File 'lib/synapse/haproxy.rb', line 838

def name
  'haproxy'
end

#parse_watcher_config(watcher) ⇒ Object

split the haproxy config in each watcher into fields applicable in frontend and backend sections



931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
# File 'lib/synapse/haproxy.rb', line 931

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

#read_state_fileObject



1222
1223
1224
1225
1226
1227
1228
1229
# File 'lib/synapse/haproxy.rb', line 1222

def read_state_file
  # Some versions of JSON return nil on an empty file ...
  JSON.load(File.read(@state_file_path)) || {}
rescue StandardError => e
  # It's ok if the state file doesn't exist or contains invalid data
  # The state file will be rebuilt automatically
  {}
end

#restartObject

restarts haproxy if the time is right



1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
# File 'lib/synapse/haproxy.rb', line 1143

def restart
  if @time < @next_restart
    log.info "synapse: at time #{@time} waiting until #{@next_restart} to restart"
    return
  end

  @next_restart = @time + @restart_interval
  @next_restart += rand(@restart_jitter * @restart_interval + 1)

  # do the actual restart
  res = `#{opts['reload_command']}`.chomp
  unless $?.success?
    log.error "failed to reload haproxy via #{opts['reload_command']}: #{res}"
    return
  end
  log.info "synapse: restarted haproxy"

  @restart_required = false
end

#seenObject

methods for managing the state file



1176
1177
1178
1179
1180
1181
1182
1183
1184
# File 'lib/synapse/haproxy.rb', line 1176

def seen
  # if we don't support the state file, return nothing
  return {} if @state_file_path.nil?

  # if we've never needed the backends, now is the time to load them
  @seen = read_state_file if @seen.nil?

  @seen
end

#talk_to_socket(socket_file_path, command) ⇒ Object



1039
1040
1041
1042
1043
1044
1045
# File 'lib/synapse/haproxy.rb', line 1039

def talk_to_socket(socket_file_path, command)
  s = UNIXSocket.new(socket_file_path)
  s.write(command)
  s.read
ensure
  s.close if s
end

#tick(watchers) ⇒ Object



842
843
844
845
846
847
848
849
850
851
852
# File 'lib/synapse/haproxy.rb', line 842

def tick(watchers)
  if (@time % STATE_FILE_UPDATE_INTERVAL) == 0
    update_state_file(watchers)
  end

  @time += 1

  # We potentially have to restart if the restart was rate limited
  # in the original call to update_config
  restart if @opts['do_reloads'] && @restart_required
end

#update_backends_at(socket_file_path, watchers) ⇒ Object

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



1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
# File 'lib/synapse/haproxy.rb', line 1049

def update_backends_at(socket_file_path, watchers)
  # first, get a list of existing servers for various backends
  begin
    stat_command = "show stat\n"
    info = talk_to_socket(socket_file_path, stat_command)
  rescue StandardError => e
    log.warn "synapse: restart required because socket command #{stat_command} failed "\
             "with error #{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.info "synapse: restart required because we added new section #{watcher.name}"
      @restart_required = true
      next
    end

    watcher.backends.each do |backend|
      backend_name = construct_name(backend)
      if cur_backends[watcher.name].include? backend_name
        enabled_backends[watcher.name] << backend_name
      else
        log.info "synapse: restart required because we have a new backend #{watcher.name}/#{backend_name}"
        @restart_required = true
      end
    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.fetch(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
        output = talk_to_socket(socket_file_path, command)
      rescue StandardError => e
        log.warn "synapse: restart required because socket command #{command} failed with "\
                 "error #{e.inspect}"
        @restart_required = true
      else
        unless output == "\n"
          log.warn "synapse: restart required because socket command #{command} failed with "\
                  "output #{output}"
          @restart_required = true
        end
      end
    end
  end

  log.info "synapse: reconfigured haproxy via #{socket_file_path}"
end

#update_config(watchers) ⇒ Object



854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
# File 'lib/synapse/haproxy.rb', line 854

def update_config(watchers)
  # if we support updating backends, try that whenever possible
  if @opts['do_socket']
    @opts['socket_file_paths'].each do |socket_path|
      update_backends_at(socket_path, watchers)
    end
  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

#update_state_file(watchers) ⇒ Object



1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
# File 'lib/synapse/haproxy.rb', line 1186

def update_state_file(watchers)
  # if we don't support the state file, do nothing
  return if @state_file_path.nil?

  log.info "synapse: writing state file"
  timestamp = Time.now.to_i

  # Remove stale backends
  seen.each do |watcher_name, backends|
    backends.each do |backend_name, backend|
      ts = backend.fetch('timestamp', 0)
      delta = (timestamp - ts).abs
      if delta > @state_file_ttl
        log.info "synapse: expiring #{backend_name} with age #{delta}"
        backends.delete(backend_name)
      end
    end
  end

  # Remove any services which no longer have any backends
  seen.reject!{|watcher_name, backends| backends.keys.length == 0}

  # Add backends from watchers
  watchers.each do |watcher|
    seen[watcher.name] ||= {}

    watcher.backends.each do |backend|
      backend_name = construct_name(backend)
      seen[watcher.name][backend_name] = backend.merge('timestamp' => timestamp)
    end
  end

  # write the data!
  write_data_to_state_file(seen)
end

#validate_haproxy_stanza(stanza, stanza_type, service_name) ⇒ Object



950
951
952
953
954
955
956
957
958
959
960
# File 'lib/synapse/haproxy.rb', line 950

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) ⇒ Object

writes the config



1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
# File 'lib/synapse/haproxy.rb', line 1126

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

#write_data_to_state_file(data) ⇒ Object

we do this atomically so the state file is always consistent



1232
1233
1234
1235
1236
# File 'lib/synapse/haproxy.rb', line 1232

def write_data_to_state_file(data)
  tmp_state_file_path = @state_file_path + ".tmp"
  File.write(tmp_state_file_path, JSON.pretty_generate(data))
  FileUtils.mv(tmp_state_file_path, @state_file_path)
end