proxymgr 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/.gitignore +8 -0
 - data/.rspec +2 -0
 - data/.rubocop.yml +11 -0
 - data/Gemfile +8 -0
 - data/Gemfile.lock +52 -0
 - data/README.md +99 -0
 - data/Rakefile +5 -0
 - data/bin/proxymgr +74 -0
 - data/etc/haproxy.cfg.erb +11 -0
 - data/examples/config.yml +5 -0
 - data/lib/proxymgr.rb +20 -0
 - data/lib/proxymgr/callbacks.rb +17 -0
 - data/lib/proxymgr/config.rb +130 -0
 - data/lib/proxymgr/haproxy.rb +51 -0
 - data/lib/proxymgr/haproxy/control.rb +46 -0
 - data/lib/proxymgr/haproxy/process.rb +107 -0
 - data/lib/proxymgr/haproxy/server.rb +24 -0
 - data/lib/proxymgr/haproxy/socket.rb +67 -0
 - data/lib/proxymgr/haproxy/socket_manager.rb +62 -0
 - data/lib/proxymgr/haproxy/state.rb +124 -0
 - data/lib/proxymgr/haproxy/updater.rb +74 -0
 - data/lib/proxymgr/logging.rb +26 -0
 - data/lib/proxymgr/platform.rb +16 -0
 - data/lib/proxymgr/platform/linux.rb +9 -0
 - data/lib/proxymgr/process_manager.rb +101 -0
 - data/lib/proxymgr/process_manager/signal_handler.rb +44 -0
 - data/lib/proxymgr/service_config.rb +12 -0
 - data/lib/proxymgr/service_config/base.rb +16 -0
 - data/lib/proxymgr/service_config/zookeeper.rb +33 -0
 - data/lib/proxymgr/service_manager.rb +53 -0
 - data/lib/proxymgr/sink.rb +100 -0
 - data/lib/proxymgr/watcher.rb +9 -0
 - data/lib/proxymgr/watcher/base.rb +75 -0
 - data/lib/proxymgr/watcher/campanja_zk.rb +20 -0
 - data/lib/proxymgr/watcher/dns.rb +36 -0
 - data/lib/proxymgr/watcher/file.rb +45 -0
 - data/lib/proxymgr/watcher/zookeeper.rb +61 -0
 - data/packaging/profile.sh +1 -0
 - data/packaging/recipe.rb +35 -0
 - data/proxymgr.gemspec +20 -0
 - data/spec/spec_helper.rb +23 -0
 - data/spec/support/dummy_watcher.rb +21 -0
 - data/spec/support/fake_proxy.rb +15 -0
 - data/spec/support/fake_zookeeper.rb +170 -0
 - data/spec/support/mock_servers.rb +7 -0
 - data/spec/unit/haproxy/socket_manager_spec.rb +40 -0
 - data/spec/unit/haproxy/updater_spec.rb +123 -0
 - data/spec/unit/service_manager_spec.rb +49 -0
 - data/spec/unit/sink_spec.rb +41 -0
 - data/spec/unit/watcher/base_spec.rb +27 -0
 - metadata +188 -0
 
| 
         @@ -0,0 +1,51 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module ProxyMgr
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Haproxy
         
     | 
| 
      
 3 
     | 
    
         
            +
                require 'proxymgr/haproxy/socket'
         
     | 
| 
      
 4 
     | 
    
         
            +
                require 'proxymgr/haproxy/updater'
         
     | 
| 
      
 5 
     | 
    
         
            +
                require 'proxymgr/haproxy/server'
         
     | 
| 
      
 6 
     | 
    
         
            +
                require 'proxymgr/haproxy/control'
         
     | 
| 
      
 7 
     | 
    
         
            +
                require 'proxymgr/haproxy/process'
         
     | 
| 
      
 8 
     | 
    
         
            +
                require 'proxymgr/haproxy/state'
         
     | 
| 
      
 9 
     | 
    
         
            +
                require 'proxymgr/haproxy/socket_manager'
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def initialize(path, config_file, opts = {})
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @path             = path
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @config_file      = config_file
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  @socket_path      = opts[:socket]
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @global_config    = opts[:global]
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @defaults_config  = opts[:defaults]
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  @socket           = @socket_path ? Socket.new(@socket_path) : nil
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  @control          = nil
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def version
         
     | 
| 
      
 25 
     | 
    
         
            +
                  `#{@path} -v`[/version ([\d\.]+)/, 1].to_f
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def start
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @socket         = @socket_path ? Socket.new(@socket_path) : nil
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @control        = Control.new(@path, @config_file)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  opts            = {:defaults    => @defaults_config,
         
     | 
| 
      
 32 
     | 
    
         
            +
                                     :global      => @global_config,
         
     | 
| 
      
 33 
     | 
    
         
            +
                                     :socket_path => @socket_path}
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @socket_manager = SocketManager.new
         
     | 
| 
      
 35 
     | 
    
         
            +
                  @state          = State.new(@control, @config_file, @socket_manager, @socket, opts)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @updater        = Updater.new(@socket)
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  @state.start
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def shutdown
         
     | 
| 
      
 42 
     | 
    
         
            +
                  @state.stop
         
     | 
| 
      
 43 
     | 
    
         
            +
                  @socket_manager.shutdown
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                def update_backends(watchers)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  changeset = @updater.produce_changeset(watchers)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  @state.update_state(watchers, changeset)
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,46 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module ProxyMgr
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Haproxy
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Control
         
     | 
| 
      
 4 
     | 
    
         
            +
                  include Callbacks
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  attr_reader :exit_code
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def initialize(path, config_file)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @path        = path
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @config_file = config_file
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                    @mutex       = Mutex.new
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                    callbacks :on_stop
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def start
         
     | 
| 
      
 18 
     | 
    
         
            +
                    restart
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def restart(fds = [])
         
     | 
| 
      
 22 
     | 
    
         
            +
                    @mutex.synchronize do
         
     | 
| 
      
 23 
     | 
    
         
            +
                      if @process
         
     | 
| 
      
 24 
     | 
    
         
            +
                        run(@process.pid, fds)
         
     | 
| 
      
 25 
     | 
    
         
            +
                      else
         
     | 
| 
      
 26 
     | 
    
         
            +
                        run(nil, fds)
         
     | 
| 
      
 27 
     | 
    
         
            +
                      end
         
     | 
| 
      
 28 
     | 
    
         
            +
                    end
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  [:wait, :stop, :exited?].each do |sym|
         
     | 
| 
      
 32 
     | 
    
         
            +
                    define_method(sym) { |*args, &blk| @process.send(sym, *args, &blk) }
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  private
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  def run(pid = nil, fds = [])
         
     | 
| 
      
 38 
     | 
    
         
            +
                    @process.replace if @process
         
     | 
| 
      
 39 
     | 
    
         
            +
                    @process = Process.new(@path, @config_file, fds, pid) do |status|
         
     | 
| 
      
 40 
     | 
    
         
            +
                      call(:on_stop, status)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
                    @process.start
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,107 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module ProxyMgr
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Haproxy
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Process
         
     | 
| 
      
 4 
     | 
    
         
            +
                  require 'state_machine'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  include Logging
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  state_machine :state, :initial => :stopped do
         
     | 
| 
      
 9 
     | 
    
         
            +
                    event :start do
         
     | 
| 
      
 10 
     | 
    
         
            +
                      transition [:stopped, :exited] => :running, :if => :run
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    event :exited do
         
     | 
| 
      
 14 
     | 
    
         
            +
                      transition :running => :exited
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    event :stop do
         
     | 
| 
      
 18 
     | 
    
         
            +
                      transition :running => :shutdown
         
     | 
| 
      
 19 
     | 
    
         
            +
                    end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    after_transition :running => :shutdown do |process|
         
     | 
| 
      
 22 
     | 
    
         
            +
                      process.stopping
         
     | 
| 
      
 23 
     | 
    
         
            +
                      process.process_manager.stop
         
     | 
| 
      
 24 
     | 
    
         
            +
                    end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                    event :replace do
         
     | 
| 
      
 27 
     | 
    
         
            +
                      transition :running => :stopping
         
     | 
| 
      
 28 
     | 
    
         
            +
                    end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    event :stopping do
         
     | 
| 
      
 31 
     | 
    
         
            +
                      transition :shutdown => :stopping
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    event :stopped do
         
     | 
| 
      
 35 
     | 
    
         
            +
                      transition :stopping => :stopped
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    state :running do
         
     | 
| 
      
 39 
     | 
    
         
            +
                      def handle_stop(status)
         
     | 
| 
      
 40 
     | 
    
         
            +
                        @exit_code = status
         
     | 
| 
      
 41 
     | 
    
         
            +
                        if abnormal_exit?
         
     | 
| 
      
 42 
     | 
    
         
            +
                          exited
         
     | 
| 
      
 43 
     | 
    
         
            +
                        else
         
     | 
| 
      
 44 
     | 
    
         
            +
                          stopped
         
     | 
| 
      
 45 
     | 
    
         
            +
                        end
         
     | 
| 
      
 46 
     | 
    
         
            +
                        @callback.call status
         
     | 
| 
      
 47 
     | 
    
         
            +
                      end
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                    state :stopping do
         
     | 
| 
      
 51 
     | 
    
         
            +
                      def handle_stop(status)
         
     | 
| 
      
 52 
     | 
    
         
            +
                        @exit_code = status
         
     | 
| 
      
 53 
     | 
    
         
            +
                        stopped
         
     | 
| 
      
 54 
     | 
    
         
            +
                      end
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  attr_reader :exit_code, :process_manager
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  def initialize(path, config_file, fds, old_pid = nil, &callback)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    @path        = path
         
     | 
| 
      
 62 
     | 
    
         
            +
                    @config_file = config_file
         
     | 
| 
      
 63 
     | 
    
         
            +
                    @old_pid     = old_pid
         
     | 
| 
      
 64 
     | 
    
         
            +
                    @callback    = callback
         
     | 
| 
      
 65 
     | 
    
         
            +
                    @fds         = fds
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 68 
     | 
    
         
            +
                  end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  [:pid, :wait].each do |sym|
         
     | 
| 
      
 71 
     | 
    
         
            +
                    define_method(sym) { @process_manager.send(sym) }
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                  private
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                  def abnormal_exit?
         
     | 
| 
      
 77 
     | 
    
         
            +
                    @exit_code && @exit_code > 0
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  def run
         
     | 
| 
      
 81 
     | 
    
         
            +
                    args = ['-f', @config_file, '-db']
         
     | 
| 
      
 82 
     | 
    
         
            +
                    if @old_pid
         
     | 
| 
      
 83 
     | 
    
         
            +
                      args << '-sf'
         
     | 
| 
      
 84 
     | 
    
         
            +
                      args << @old_pid.to_s
         
     | 
| 
      
 85 
     | 
    
         
            +
                    end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                    @process_manager = ProcessManager.new(@path, args, :fds => @fds)
         
     | 
| 
      
 88 
     | 
    
         
            +
                    [:on_stdout, :on_stderr].each do |cb|
         
     | 
| 
      
 89 
     | 
    
         
            +
                      @process_manager.send(cb, &method(:parse_haproxy_log))
         
     | 
| 
      
 90 
     | 
    
         
            +
                    end
         
     | 
| 
      
 91 
     | 
    
         
            +
                    @process_manager.on_stop(&method(:handle_stop))
         
     | 
| 
      
 92 
     | 
    
         
            +
                    @process_manager.start
         
     | 
| 
      
 93 
     | 
    
         
            +
                  end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  def parse_haproxy_log(line)
         
     | 
| 
      
 96 
     | 
    
         
            +
                    matches = line.scan(/^\[(.*)\] (.*)/)[0]
         
     | 
| 
      
 97 
     | 
    
         
            +
                    if matches
         
     | 
| 
      
 98 
     | 
    
         
            +
                      haproxy_level, msg = matches
         
     | 
| 
      
 99 
     | 
    
         
            +
                      level = haproxy_level == 'WARNING' ? :warn : :info
         
     | 
| 
      
 100 
     | 
    
         
            +
                      logger.send(level, msg)
         
     | 
| 
      
 101 
     | 
    
         
            +
                    else
         
     | 
| 
      
 102 
     | 
    
         
            +
                      logger.info(line)
         
     | 
| 
      
 103 
     | 
    
         
            +
                    end
         
     | 
| 
      
 104 
     | 
    
         
            +
                  end
         
     | 
| 
      
 105 
     | 
    
         
            +
                end
         
     | 
| 
      
 106 
     | 
    
         
            +
              end
         
     | 
| 
      
 107 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module ProxyMgr
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Haproxy
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Server
         
     | 
| 
      
 4 
     | 
    
         
            +
                  attr_reader :stats
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  def initialize(haproxy, stats)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    @haproxy = haproxy
         
     | 
| 
      
 8 
     | 
    
         
            +
                    @stats   = stats
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def backend
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @stats['pxname']
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def name
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @stats['svname']
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def disabled?
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @stats['status'] == 'MAINT'
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,67 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module ProxyMgr
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Haproxy
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Socket
         
     | 
| 
      
 4 
     | 
    
         
            +
                  require 'socket'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  attr_reader :path
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def initialize(path)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @path = path
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def stats
         
     | 
| 
      
 13 
     | 
    
         
            +
                    headers, *rest = write('show stat')
         
     | 
| 
      
 14 
     | 
    
         
            +
                    headers = headers.gsub(/^# /, '').split(',')
         
     | 
| 
      
 15 
     | 
    
         
            +
                    rest.pop
         
     | 
| 
      
 16 
     | 
    
         
            +
                    rest.map { |d| Hash[headers.zip(d.split(','))] }
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def enable(backend, host)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    write "enable server #{backend}/#{host}"
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def disable(backend, host)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    write "disable server #{backend}/#{host}"
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def shutdown(backend, host)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    write "shutdown sessions server #{backend}/#{host}"
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def servers
         
     | 
| 
      
 32 
     | 
    
         
            +
                    stats.each_with_object([]) do |stat, acc|
         
     | 
| 
      
 33 
     | 
    
         
            +
                      next if %w(FRONTEND BACKEND).include? stat['svname']
         
     | 
| 
      
 34 
     | 
    
         
            +
                      acc << Server.new(self, stat)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  def write(cmd)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    with do |socket|
         
     | 
| 
      
 40 
     | 
    
         
            +
                      socket.puts(cmd + "\n")
         
     | 
| 
      
 41 
     | 
    
         
            +
                      socket.readlines.map(&:chomp)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    end
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  def connected?
         
     | 
| 
      
 46 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 47 
     | 
    
         
            +
                      with do |socket|
         
     | 
| 
      
 48 
     | 
    
         
            +
                        socket.write "show info"
         
     | 
| 
      
 49 
     | 
    
         
            +
                      end
         
     | 
| 
      
 50 
     | 
    
         
            +
                    rescue Errno::ECONNREFUSED
         
     | 
| 
      
 51 
     | 
    
         
            +
                    end
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  private
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  def with
         
     | 
| 
      
 57 
     | 
    
         
            +
                    socket = nil
         
     | 
| 
      
 58 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 59 
     | 
    
         
            +
                      socket = UNIXSocket.new(@path)
         
     | 
| 
      
 60 
     | 
    
         
            +
                      yield socket
         
     | 
| 
      
 61 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 62 
     | 
    
         
            +
                      socket.close if socket
         
     | 
| 
      
 63 
     | 
    
         
            +
                    end
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
              end
         
     | 
| 
      
 67 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,62 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module ProxyMgr
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Haproxy
         
     | 
| 
      
 3 
     | 
    
         
            +
                class SocketManager
         
     | 
| 
      
 4 
     | 
    
         
            +
                  require 'socket'
         
     | 
| 
      
 5 
     | 
    
         
            +
                  require 'fcntl'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  include Logging
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  attr_reader :sockets
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def initialize
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @sockets = {}
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def shutdown
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @sockets.each do |_port, socket|
         
     | 
| 
      
 17 
     | 
    
         
            +
                      socket.close
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def update(backends)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    fds = backends.each_with_object({}) do |(name, backend), mapping|
         
     | 
| 
      
 23 
     | 
    
         
            +
                      socket = for_port(backend.port)
         
     | 
| 
      
 24 
     | 
    
         
            +
                      mapping[backend.port] = socket.fileno if socket
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    (@sockets.keys - fds.keys).each do |port|
         
     | 
| 
      
 28 
     | 
    
         
            +
                      @sockets.delete(port).close
         
     | 
| 
      
 29 
     | 
    
         
            +
                    end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                    fds
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  private
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  def for_port(port)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    unless @sockets[port]
         
     | 
| 
      
 38 
     | 
    
         
            +
                      @sockets[port] = ::Socket.new(::Socket::AF_INET, ::Socket::SOCK_STREAM, 0)
         
     | 
| 
      
 39 
     | 
    
         
            +
                      @sockets[port].setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
         
     | 
| 
      
 40 
     | 
    
         
            +
                      retries = 0
         
     | 
| 
      
 41 
     | 
    
         
            +
                      until retries > 5
         
     | 
| 
      
 42 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 43 
     | 
    
         
            +
                          @sockets[port].bind(::Socket.pack_sockaddr_in(port, '0.0.0.0'))
         
     | 
| 
      
 44 
     | 
    
         
            +
                          break
         
     | 
| 
      
 45 
     | 
    
         
            +
                        rescue Errno::EADDRINUSE
         
     | 
| 
      
 46 
     | 
    
         
            +
                          logger.info "Could not bind to #{port}: retrying..."
         
     | 
| 
      
 47 
     | 
    
         
            +
                          sleep 1
         
     | 
| 
      
 48 
     | 
    
         
            +
                          retries += 1
         
     | 
| 
      
 49 
     | 
    
         
            +
                        end
         
     | 
| 
      
 50 
     | 
    
         
            +
                      end
         
     | 
| 
      
 51 
     | 
    
         
            +
                      flags = @sockets[port].fcntl(Fcntl::F_GETFD) & ~Fcntl::FD_CLOEXEC
         
     | 
| 
      
 52 
     | 
    
         
            +
                      @sockets[port].fcntl(Fcntl::F_SETFD, flags)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
      
 54 
     | 
    
         
            +
                    @sockets[port]
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  def stop_port(port)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    @sockets[port].close if @sockets[port]
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
              end
         
     | 
| 
      
 62 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,124 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module ProxyMgr
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Haproxy
         
     | 
| 
      
 3 
     | 
    
         
            +
                class State
         
     | 
| 
      
 4 
     | 
    
         
            +
                  require 'tempfile'
         
     | 
| 
      
 5 
     | 
    
         
            +
                  require 'pathname'
         
     | 
| 
      
 6 
     | 
    
         
            +
                  require 'erb'
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  include Logging
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def initialize(process, config_file, socket_manager, socket = nil, opts = {})
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @process         = process
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @config_file     = config_file
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @socket          = socket
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @socket_manager  = socket_manager
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                    @sleep_interval  = opts[:sleep_interval] || 5
         
     | 
| 
      
 17 
     | 
    
         
            +
                    @global_config   = opts[:global]
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @defaults_config = opts[:defaults]
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @socket_path     = opts[:socket_path]
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    @file_descriptors = {}
         
     | 
| 
      
 22 
     | 
    
         
            +
                    @backends         = {}
         
     | 
| 
      
 23 
     | 
    
         
            +
                    @config_template  = ERB.new(File.read(File.join(ProxyMgr.template_dir, 'haproxy.cfg.erb')))
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @mutex            = Mutex.new
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @cv               = ConditionVariable.new
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def start
         
     | 
| 
      
 29 
     | 
    
         
            +
                    write_config
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                    @thread = Thread.new do
         
     | 
| 
      
 32 
     | 
    
         
            +
                      @mutex.synchronize do
         
     | 
| 
      
 33 
     | 
    
         
            +
                        sleep_interval = nil
         
     | 
| 
      
 34 
     | 
    
         
            +
                        loop do
         
     | 
| 
      
 35 
     | 
    
         
            +
                          logger.debug "Waiting..."
         
     | 
| 
      
 36 
     | 
    
         
            +
                          wait(sleep_interval)
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                          restart_needed = true
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                          if @changeset or @backends
         
     | 
| 
      
 41 
     | 
    
         
            +
                            if @changeset
         
     | 
| 
      
 42 
     | 
    
         
            +
                              update_state_with_changeset
         
     | 
| 
      
 43 
     | 
    
         
            +
                              restart_needed = @changeset.restart_needed?
         
     | 
| 
      
 44 
     | 
    
         
            +
                            end
         
     | 
| 
      
 45 
     | 
    
         
            +
                            @file_descriptors = @socket_manager.update(@backends)
         
     | 
| 
      
 46 
     | 
    
         
            +
                            write_config
         
     | 
| 
      
 47 
     | 
    
         
            +
                            @changeset = nil
         
     | 
| 
      
 48 
     | 
    
         
            +
                            @backends  = nil
         
     | 
| 
      
 49 
     | 
    
         
            +
                          elsif @process.exited? and !sleep_interval
         
     | 
| 
      
 50 
     | 
    
         
            +
                            sleep_interval = @sleep_interval
         
     | 
| 
      
 51 
     | 
    
         
            +
                            logger.info "Haproxy exited abnormally. Sleeping for #{sleep_interval}s"
         
     | 
| 
      
 52 
     | 
    
         
            +
                            next
         
     | 
| 
      
 53 
     | 
    
         
            +
                          end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                          sleep_interval = nil
         
     | 
| 
      
 56 
     | 
    
         
            +
                          @process.restart(@file_descriptors.values) if restart_needed
         
     | 
| 
      
 57 
     | 
    
         
            +
                        end
         
     | 
| 
      
 58 
     | 
    
         
            +
                      end
         
     | 
| 
      
 59 
     | 
    
         
            +
                    end
         
     | 
| 
      
 60 
     | 
    
         
            +
                    @thread.abort_on_exception = true
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    @process.on_stop do |status|
         
     | 
| 
      
 63 
     | 
    
         
            +
                      Thread.new { signal }.join if @process.exited?
         
     | 
| 
      
 64 
     | 
    
         
            +
                    end
         
     | 
| 
      
 65 
     | 
    
         
            +
                    @process.start
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  def socket?
         
     | 
| 
      
 69 
     | 
    
         
            +
                    @socket and @socket.connected?
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  def update_state(backends, changeset)
         
     | 
| 
      
 73 
     | 
    
         
            +
                    @mutex.synchronize do
         
     | 
| 
      
 74 
     | 
    
         
            +
                      @changeset = changeset
         
     | 
| 
      
 75 
     | 
    
         
            +
                      @backends  = backends
         
     | 
| 
      
 76 
     | 
    
         
            +
                    end
         
     | 
| 
      
 77 
     | 
    
         
            +
                    signal
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  def stop
         
     | 
| 
      
 81 
     | 
    
         
            +
                    @thread.kill
         
     | 
| 
      
 82 
     | 
    
         
            +
                    signal
         
     | 
| 
      
 83 
     | 
    
         
            +
                    @thread.join
         
     | 
| 
      
 84 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 85 
     | 
    
         
            +
                      @process.stop
         
     | 
| 
      
 86 
     | 
    
         
            +
                    rescue Errno::ESRCH # sometimes there's no process here. that's OK.
         
     | 
| 
      
 87 
     | 
    
         
            +
                    end
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  private
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                  def update_state_with_changeset
         
     | 
| 
      
 93 
     | 
    
         
            +
                    @changeset.disable.each do |backend, hosts|
         
     | 
| 
      
 94 
     | 
    
         
            +
                      hosts.each { |host| @socket.disable backend, host }
         
     | 
| 
      
 95 
     | 
    
         
            +
                    end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                    @changeset.enable.each do |backend, hosts|
         
     | 
| 
      
 98 
     | 
    
         
            +
                      hosts.each { |host| @socket.enable backend, host }
         
     | 
| 
      
 99 
     | 
    
         
            +
                    end
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  def signal
         
     | 
| 
      
 103 
     | 
    
         
            +
                    @mutex.synchronize { @cv.signal }
         
     | 
| 
      
 104 
     | 
    
         
            +
                  end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                  def wait(timeout = nil)
         
     | 
| 
      
 107 
     | 
    
         
            +
                    @cv.wait(@mutex, timeout)
         
     | 
| 
      
 108 
     | 
    
         
            +
                  end
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                  def write_config
         
     | 
| 
      
 111 
     | 
    
         
            +
                    f = nil
         
     | 
| 
      
 112 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 113 
     | 
    
         
            +
                      f = Tempfile.new('haproxy')
         
     | 
| 
      
 114 
     | 
    
         
            +
                      f.write @config_template.result(binding)
         
     | 
| 
      
 115 
     | 
    
         
            +
                      f.close
         
     | 
| 
      
 116 
     | 
    
         
            +
                      Pathname.new(f.path).rename(@config_file)
         
     | 
| 
      
 117 
     | 
    
         
            +
                    rescue Exception => e
         
     | 
| 
      
 118 
     | 
    
         
            +
                      logger.warn "Unable to write to #{@config_file}: #{e}"
         
     | 
| 
      
 119 
     | 
    
         
            +
                      File.unlink f.path if f
         
     | 
| 
      
 120 
     | 
    
         
            +
                    end
         
     | 
| 
      
 121 
     | 
    
         
            +
                  end
         
     | 
| 
      
 122 
     | 
    
         
            +
                end
         
     | 
| 
      
 123 
     | 
    
         
            +
              end
         
     | 
| 
      
 124 
     | 
    
         
            +
            end
         
     |