class Synapse::ConfigGenerator::Nginx
Constants
- NAME
Public Class Methods
new(opts)
click to toggle source
# File lib/synapse/config_generator/nginx.rb, line 12 def initialize(opts) %w{main events}.each do |req| if !opts.fetch('contexts', {}).has_key?(req) raise ArgumentError, "nginx requires a contexts.#{req} section" end end @opts = opts @contexts = opts['contexts'] @opts['do_writes'] = true unless @opts.key?('do_writes') @opts['do_reloads'] = true unless @opts.key?('do_reloads') req_pairs = { 'do_writes' => ['config_file_path', 'check_command'], 'do_reloads' => ['reload_command', 'start_command'], } req_pairs.each do |cond, reqs| if opts[cond] unless reqs.all? {|req| opts[req]} missing = reqs.select {|req| not opts[req]} raise ArgumentError, "the `#{missing}` option(s) are required when `#{cond}` is true" end end end # how to restart nginx @restart_interval = @opts.fetch('restart_interval', 2).to_i @restart_jitter = @opts.fetch('restart_jitter', 0).to_f @restart_required = false @has_started = false # virtual clock bookkeeping for controlling how often nginx restarts @time = 0 @next_restart = @time # a place to store generated server + upstream stanzas, and watcher # revisions so we can save CPU on updates by not re-computing stanzas @servers_cache = {} @upstreams_cache = {} @watcher_revisions = {} end
Public Instance Methods
construct_name(backend)
click to toggle source
used to build unique, consistent nginx names for backends
# File lib/synapse/config_generator/nginx.rb, line 334 def construct_name(backend) name = "#{backend['host']}:#{backend['port']}" if backend['name'] && !backend['name'].empty? name = "#{backend['name']}_#{name}" end return name end
generate_base_config()
click to toggle source
generates the global and defaults sections of the config file
# File lib/synapse/config_generator/nginx.rb, line 151 def generate_base_config base_config = ["# auto-generated by synapse at #{Time.now}\n"] # The "main" context is special and is the top level @contexts['main'].each do |option| base_config << "#{option};" end base_config << "\n" # http and streams are generated separately @contexts.keys.select{|key| !(["main", "http", "stream"].include?(key))}.each do |context| base_config << "#{context} {" @contexts[context].each do |option| base_config << "\t#{option};" end base_config << "}\n" 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/config_generator/nginx.rb, line 96 def generate_config(watchers) new_config = generate_base_config http = (@contexts['http'] || []).collect {|option| "\t#{option};"} stream = (@contexts['stream'] || []).collect {|option| "\t#{option};"} watchers.each do |watcher| watcher_config = watcher.config_for_generator[name] next if watcher_config['disabled'] # There seems to be no way to have empty TCP listeners ... just # don't bind the port at all? ... idk next if watcher_config['mode'] == 'tcp' && watcher.backends.empty? # Only regenerate if something actually changed. This saves a lot # of CPU load for high churn systems regenerate = watcher.revision != @watcher_revisions[watcher.name] || @servers_cache[watcher.name].nil? || @upstreams_cache[watcher.name].nil? if regenerate @servers_cache[watcher.name] = generate_server(watcher).flatten @upstreams_cache[watcher.name] = generate_upstream(watcher).flatten @watcher_revisions[watcher.name] = watcher.revision end section = case watcher_config['mode'] when 'http' http when 'tcp' stream else raise ArgumentError, "synapse does not understand #{watcher_config['mode']} as a service mode" end section << @servers_cache[watcher.name] section << @upstreams_cache[watcher.name] end unless http.empty? new_config << 'http {' new_config.concat(http.flatten) new_config << "}\n" end unless stream.empty? new_config << 'stream {' new_config.concat(stream.flatten) new_config << "}\n" end log.debug "synapse: new nginx config: #{new_config}" return new_config.flatten.join("\n") end
generate_proxy(mode, upstream_name, empty_upstream)
click to toggle source
Nginx
has some annoying differences between how upstreams in the http (http) module and the stream (tcp) module address upstreams
# File lib/synapse/config_generator/nginx.rb, line 206 def generate_proxy(mode, upstream_name, empty_upstream) upstream_name = "http://#{upstream_name}" if mode == 'http' case mode when 'http' if empty_upstream value = "\t\t\treturn 503;" else value = "\t\t\tproxy_pass #{upstream_name};" end stanza = [ "\t\tlocation / {", value, "\t\t}" ] when 'tcp' stanza = [ "\t\tproxy_pass #{upstream_name};", ] else [] end end
generate_server(watcher)
click to toggle source
# File lib/synapse/config_generator/nginx.rb, line 171 def generate_server(watcher) watcher_config = watcher.config_for_generator[name] unless watcher_config.has_key?('port') log.debug "synapse: not generating server stanza for watcher #{watcher.name} because it has no port defined" return [] else port = watcher_config['port'] end listen_address = ( watcher_config['listen_address'] || opts['listen_address'] || 'localhost' ) listen_line= [ "\t\tlisten", "#{listen_address}:#{port}", watcher_config['listen_options'], ';', ].compact.join(' ') upstream_name = watcher_config.fetch('upstream_name', watcher.name) stanza = [ "\tserver {", listen_line, watcher_config['server'].map {|c| "\t\t#{c};"}, generate_proxy(watcher_config['mode'], upstream_name, watcher.backends.empty?), "\t}", ] end
generate_upstream(watcher)
click to toggle source
# File lib/synapse/config_generator/nginx.rb, line 230 def generate_upstream(watcher) backends = {} watcher_config = watcher.config_for_generator[name] upstream_name = watcher_config.fetch('upstream_name', watcher.name) watcher.backends.each {|b| backends[construct_name(b)] = b} # nginx doesn't like upstreams with no backends? return [] if backends.empty? # Note that because we use the config file as the source of truth # for whether or not to reload, we want some kind of sorted order # by default, in this case we choose asc keys = case watcher_config['upstream_order'] when 'desc' backends.keys.sort.reverse when 'shuffle' backends.keys.shuffle when 'no_shuffle' backends.keys else backends.keys.sort end stanza = [ "\tupstream #{upstream_name} {", watcher_config['upstream'].map {|c| "\t\t#{c};"}, keys.map {|backend_name| backend = backends[backend_name] b = "\t\tserver #{backend['host']}:#{backend['port']}" b = "#{b} #{watcher_config['server_options']}" if watcher_config['server_options'] "#{b};" }, "\t}" ] end
normalize_watcher_provided_config(service_watcher_name, service_watcher_config)
click to toggle source
Calls superclass method
# File lib/synapse/config_generator/nginx.rb, line 55 def normalize_watcher_provided_config(service_watcher_name, service_watcher_config) service_watcher_config = super(service_watcher_name, service_watcher_config) defaults = { 'mode' => 'http', 'upstream' => [], 'server' => [], 'disabled' => false, } unless service_watcher_config.include?('port') || service_watcher_config['disabled'] log.warn "synapse: service #{service_watcher_name}: nginx config does not include a port; only upstream sections for the service will be created; you must move traffic there manually using server sections" end defaults.merge(service_watcher_config) end
restart()
click to toggle source
restarts nginx if the time is right
# File lib/synapse/config_generator/nginx.rb, line 311 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) # On the very first restart we may need to start start unless @has_started res = `#{opts['reload_command']}`.chomp unless $?.success? log.error "failed to reload nginx via #{opts['reload_command']}: #{res}" return end log.info "synapse: restarted nginx" @restart_required = false end
start()
click to toggle source
# File lib/synapse/config_generator/nginx.rb, line 297 def start log.info "synapse: attempting to run #{opts['start_command']} to get nginx started" log.info 'synapse: this can fail if nginx is already running' begin `#{opts['start_command']}`.chomp rescue Exception => e log.warn "synapse: error in NGINX start: #{e.inspect}" log.warn e.backtrace ensure @has_started = true end end
tick(watchers)
click to toggle source
# File lib/synapse/config_generator/nginx.rb, line 71 def tick(watchers) @time += 1 # Always ensure we try to start at least once # Note that this should only trigger during error cases where for # some reason Synapse could not start NGINX during the initial restart start if opts['do_reloads'] && !@has_started # 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_config(watchers)
click to toggle source
# File lib/synapse/config_generator/nginx.rb, line 84 def update_config(watchers) # 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'] @restart_required = write_config(new_config) restart if opts['do_reloads'] && @restart_required end end
write_config(new_config)
click to toggle source
writes the config
# File lib/synapse/config_generator/nginx.rb, line 268 def write_config(new_config) begin old_config = File.read(opts['config_file_path']) rescue Errno::ENOENT => e log.info "synapse: could not open nginx config file at #{opts['config_file_path']}" old_config = "" end # The first line of the config files contain a timestamp, so to prevent # un-needed restarts, only compare after that. We do not split on # newlines and compare because this is called a lot, and we need to be # as CPU efficient as possible. old_version = old_config[(old_config.index("\n") || 0) + 1..-1] new_version = new_config[(new_config.index("\n") || 0) + 1..-1] if old_version == new_version return false else File.open(opts['config_file_path'],'w') {|f| f.write(new_config)} check = `#{opts['check_command']}`.chomp unless $?.success? log.error "synapse: nginx configuration is invalid according to #{opts['check_command']}!" log.error 'synapse: not restarting nginx as a result' return false end return true end end