From 0d4640f9bc511479c0474709aa1576a16949d782 Mon Sep 17 00:00:00 2001 From: Corey Osman Date: Thu, 31 Oct 2013 01:54:36 -0700 Subject: [PATCH 1/8] moved backend and server logic from instance to their own classes --- lib/haproxy_manager.rb | 4 +- lib/haproxy_manager/backend.rb | 106 ++++++++++++++++++ lib/haproxy_manager/instance.rb | 59 ++++++++-- lib/haproxy_manager/server.rb | 187 ++++++++++++++++++++++++++++++++ spec/backend_spec.rb | 122 +++++++++++++++++++++ spec/fixtures/stat_response.txt | 7 ++ spec/instance_spec.rb | 19 +++- spec/server_spec.rb | 180 ++++++++++++++++++++++++++++++ spec/spec_helper.rb | 1 + 9 files changed, 672 insertions(+), 13 deletions(-) create mode 100644 lib/haproxy_manager/backend.rb create mode 100644 lib/haproxy_manager/server.rb create mode 100644 spec/backend_spec.rb create mode 100644 spec/fixtures/stat_response.txt create mode 100644 spec/server_spec.rb diff --git a/lib/haproxy_manager.rb b/lib/haproxy_manager.rb index 8034dbe..e9212c6 100644 --- a/lib/haproxy_manager.rb +++ b/lib/haproxy_manager.rb @@ -1 +1,3 @@ -require 'haproxy_manager/instance' \ No newline at end of file +require 'haproxy_manager/instance' +require 'haproxy_manager/backend' +require 'haproxy_manager/server' \ No newline at end of file diff --git a/lib/haproxy_manager/backend.rb b/lib/haproxy_manager/backend.rb new file mode 100644 index 0000000..66f72f6 --- /dev/null +++ b/lib/haproxy_manager/backend.rb @@ -0,0 +1,106 @@ +module HAProxyManager + class Backend + attr_reader :name, :servers, :socket + + def initialize(backend_name, socket_conn) + @name = backend_name + @print_response = Proc.new {|response| puts response} + @socket = socket_conn + @servers = {} + end + + def server_names + servers.keys + end + + def count + servers.length + end + + def servers + if @servers.length < 1 + srv_names = stats.keys - ['FRONTEND', 'BACKEND'] + srv_names.each do | srv| + @servers[srv] = Server.new(srv, socket) + end + end + @servers + end + + def has_server?(name) + servers.include?(name) + end + + def method_missing(method, *args, &block) + if not backend_stats.has_key?(method.to_s) + raise NoMethodError + else + backend_stats[method.to_s] + end + end + + def stats_to_json + JSON.pretty_generate(stats) + end + + def backend_stats + stats['BACKEND'] + end + + def frontend_stats + stats['FRONTEND'] + end + + def stats + mystats = {} + stats_data = socket.execute( "show stat -1 -1 -1" ) + headers = stats_data[0].split(",").collect do |name| + name.gsub('#', '').strip + end + stats_data[1..-1].inject({}) do |hash, line| + data = line.split(",") + if data.first == name + i = 0 + datahash = {} + data.each do |item| + header = headers[i] + datahash[header] = item + i = i + 1 + end + sv_name = datahash['svname'] + mystats[sv_name] = datahash + end + + end + mystats + end + + def up? + status == 'UP' + end + + def down? + status == 'DOWN' + end + + def disable + servers.each do |key, server| + server.disable + end + end + + def enable + servers.each do |key, server| + server.enable + end + end + + def status + backend_stats['status'] + end + + def stats_names + stats.keys + end + end +end diff --git a/lib/haproxy_manager/instance.rb b/lib/haproxy_manager/instance.rb index 1aa810c..285c35a 100644 --- a/lib/haproxy_manager/instance.rb +++ b/lib/haproxy_manager/instance.rb @@ -1,11 +1,12 @@ require 'socket' module HAProxyManager class Instance + attr_reader :backends, :backend_instances + def initialize(socket) @socket = HAPSocket.new(socket) @print_response = Proc.new {|response| puts response} - backends = @socket.execute( "show stat -1 4 -1" )[1..-1].collect{|item| item.split(",")[0..1]} - @backends = backends.inject({}){|hash, items| (hash[items[0]] ||=[]) << items[1]; hash} + end # given a list of socket files, return a hash of Instances with the filename as keys @@ -39,11 +40,33 @@ def enable(serverid, backend = nil) end end - def backends - @backends.keys + # rereads the backend data each time when refresh is true + def backend_instances(refresh=false) + if @backend_instances.nil? or refresh + @backend_instances = {} + backend_data = @socket.execute( "show stat -1 4 -1" )[1..-1].collect{|item| item.split(",")[0..1]} + backend_servers = backend_data.inject({}){|hash, items| + (hash[items[0]] ||=[]) << items[1]; hash + } + backend_servers.each do |backend, servers| + @backend_instances[backend] = HAProxyManager::Backend.new(backend, @socket) + end + end + @backend_instances end - + # This is actually redundant data kept here for backwards compatibility + # You should really be using backend_instances now + def backends + # Lets cache the values and return the cache + if @backends.nil? + @backends = {} + backend_instances.each do | name, backend| + @backends[name] = backend.servers + end + end + @backends + end def info @socket.execute( "show info").inject({}){|hash, item| @@ -80,8 +103,22 @@ def stats end end + # returns an array of servers def servers(backend = nil) - backend.nil? ? @backends.values.flatten : @backends[backend] + servers = [] + if backend.nil? + # return all servers + backend_instances.each_value do | backend| + servers << backend.servers + end + else + begin + servers = backend_instances[backend].servers + rescue KeyError => e + "The backend #{backend} is not a valid argument" + end + end + return servers.flatten end # resets Haproxy counters. If no option is specified backend and frontend counters are cleared, but @@ -93,9 +130,15 @@ def reset_counters(option = "") end private + # returns array will all backends the server belongs to def all_servers(serverid, backend) - if(backend.nil?) - items = @backends.collect{|a, b| [a, serverid] if b.include?(serverid)}.compact + # return all backends the serverid belongs to + if backend.nil? or ! backend_instances[backend].has_server?(serverid) + items = [] + backend_instances.each do |name, backend| + next if ! backend.has_server?(serverid) + items << [name, serverid] + end else items = [[backend, serverid]] end diff --git a/lib/haproxy_manager/server.rb b/lib/haproxy_manager/server.rb new file mode 100644 index 0000000..c6b2939 --- /dev/null +++ b/lib/haproxy_manager/server.rb @@ -0,0 +1,187 @@ +module HAProxyManager + class Server + attr_reader :backends, :name, :status, :socket + attr_accessor :weight + + def initialize(server_name, socket_conn) + @name = server_name + @socket = socket_conn + @print_response = Proc.new {|response| puts response} + end + + # returns a array of backend names + def backends + stats.collect do | key, value| + value['pxname'] + end + end + + def stat_names + stats.each do | key, value| + return value.keys + end + end + + # returns boolean if the the server is in the named backend + def has_backend?(backend) + backends.include?(backend) + end + + # Diables a server in the server in a backend for maintenance. + # If backend is not specified then all the backends in which the serverid exists are disabled. + # A disabled server shows up as in Maintance mode. + def disable(backend = nil) + if backend.nil? + mybackends = backends.flatten + else + if has_backend?(backend) + mybackends = [backend] + else + mybackends = [] + end + end + mybackends.each do |backen| + @socket.execute "disable server #{backen}/#{name}", &@print_response + end + end + + # Enables a server in the server in a backend. + # If backend is not specified then all the backends in which the serverid exists are enabled. + def enable(backend = nil) + if backend.nil? + mybackends = backends.flatten + else + if has_backend?(backend) + mybackends = [backend] + else + mybackends = [] + end + end + mybackends.each do |backen| + @socket.execute "enable server #{backen}/#{name}", &@print_response + end + end + + # get the status of the server for all backends or just one backend + # If any backend has marked the server as down, the status will be down, otherwise up + def status(backend=nil) + if backend.nil? or ! has_backend?(backend) + if stats.detect { |key, value| value['status'] == 'DOWN' } + 'DOWN' + else + 'UP' + end + else + stats["#{backend}/#{name}"].fetch('status') + end + + end + + # returns boolean if the server is up in all backends. Passing a backend will return boolean if up is in + # the specified backend + def up?(backend=nil) + status(backend) == 'UP' + end + + # returns boolean if the server is down in all backends. Passing a backend will return boolean if down is in + # the specified backend + def down?(backend=nil) + status(backend) == 'DOWN' + end + + + + def method_missing(method, *args, &block) + if not stat_names.include?(method.to_s) + raise NoMethodError + else + backend = args.shift + if backend.nil? + mybackends = backends.flatten + else + if has_backend?(backend) + mybackends = [backend] + else + mybackends = [] + end + end + data = {} + mybackends.each do |backend| + data["#{backend}/#{method.to_s}"] = stats["#{backend}/#{name}"].fetch(method.to_s) + end + data['total'] = total(data) + data + end + end + + # returns the array of weights for all backends or specified backends + def weight(backend=nil) + if backend.nil? + mybackends = backends.flatten + else + if has_backend?(backend) + mybackends = [backend] + else + mybackends = [] + end + end + mybackends.collect do |backend| + stats["#{backend}/#{name}"].fetch('weight').to_i + end + end + + # Sets weight for the server. If a numeric value is provider, that will become the absolute weight. It can be between 0 -256 + # If a weight has been provided ending with % then the weight is reduced by that percentage. It has to be between 0% - 100% + # Weight of a server defines, how many requests are passed to it. + def set_weight( weight, backend=nil) + if backend.nil? + mybackends = backends.flatten + else + if has_backend?(backend) + mybackends = [backend] + else + mybackends = [] + end + end + mybackends.each do | backend| + @socket.execute "set weight #{backend}/#{name} #{weight}" + end + end + + def stats + mystats = {} + stats_data = socket.execute( "show stat -1 -1 -1" ) + headers = stats_data[0].split(",").collect do |name| + name.gsub('#', '').strip + end + stats_data[1..-1].inject({}) do |hash, line| + data = line.split(",") + # match the svname with the name of this server + if data[1] == name + i = 0 + datahash = {} + data.each do |item| + header = headers[i] + datahash[header] = item + i = i + 1 + end + sv_name = datahash['svname'] + px_name = datahash['pxname'] + mystats["#{px_name}/#{sv_name}"] = datahash + end + + end + mystats + end + private + + def total(hash) + begin + hash.values.map(&:to_i).reduce(:+) + rescue TypeError + + end + end + + end +end diff --git a/spec/backend_spec.rb b/spec/backend_spec.rb new file mode 100644 index 0000000..2f892a4 --- /dev/null +++ b/spec/backend_spec.rb @@ -0,0 +1,122 @@ +require 'spec_helper' +module HAProxyManager + describe Backend do + before(:all) do + @stat_response = ["# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,", + "foo-farm,preprod-app,0,9,0,60,60,137789,34510620,3221358490,,0,,3,720,0,0,UP,12,1,0,562,143,45394,255790,,1,1,1,,113890,,2,0,,88,L7OK,200,20,0,134660,2028,147,230,0,0,,,,20,6,", + "foo-farm,preprod-bg,0,0,0,3,30,31,14333,380028,,0,,0,9,4,2,DOWN,5,1,0,4,10,2453494,4518397,,1,1,2,,6,,2,0,,2,L4CON,,0,0,16,0,0,0,0,0,,,,1,0,", + "foo-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5017534,5017534,,1,1,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", + "foo-https-farm,preprod-app,0,0,0,3,60,6219,2577996,71804141,,0,,1,30,3,0,UP,12,1,0,559,137,45394,255774,,1,2,1,,1948,,2,0,,2,L7OK,200,109,0,5912,181,11,29,0,0,,,,501,0,", + "foo-https-farm,preprod-bg,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,4,4,2453494,4518368,,1,2,2,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", + "foo-https-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5017532,5017532,,1,2,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,"] + @info_response = ["Name: HAProxy", "Version: 1.5-dev11", "Release_date: 2012/06/04", "Nbproc: 1", "Process_num: 1", "Pid: 4084", "Uptime: 58d 3h50m53s", "Uptime_sec: 5025053", "Memmax_MB: 0", "Ulimit-n: 40029", "Maxsock: 40029", "Maxconn: 20000", "Hard_maxconn: 20000", "Maxpipes: 0", "CurrConns: 0", "PipesUsed: 0", "PipesFree: 0", "ConnRate: 0", "ConnRateLimit: 0", "MaxConnRate: 69", "Tasks: 10", "Run_queue: 1", "Idle_pct: 100", "node: some machine on ec3", "description: Our awesome load balancer"] + @allstats = ["# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,", + "foo-farm,FRONTEND,,,0,150,2000,165893,38619996,3233457381,0,0,6504,,,,,OPEN,,,,,,,,,1,1,0,,,,0,0,0,69,,,,0,136147,2128,6654,20955,9,,0,144,165893,,,", + "foo-farm,preprod-app,0,9,0,60,60,139066,34839081,3222850212,,0,,3,725,0,0,UP,10,1,0,583,148,10893,257935,,1,1,1,,114847,,2,0,,88,L7OK,200,150,0,135827,2128,147,230,0,0,,,,20,11,", + "foo-farm,preprod-bg,0,0,0,3,30,31,14333,380028,,0,,0,9,4,2,DOWN,5,1,0,4,10,2538799,4603702,,1,1,2,,6,,2,0,,2,L4CON,,0,0,16,0,0,0,0,0,,,,1,0,", + "foo-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5102839,5102839,,1,1,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", + "foo-farm,BACKEND,0,84,0,150,200,159082,38619996,3233457381,0,0,,19991,734,4,2,UP,10,1,0,,148,10893,300268,,1,1,0,,114853,,1,0,,144,,,,0,135843,2128,147,20955,9,,,,,21,11,", + "foo-https-farm,FRONTEND,,,0,3,2000,6545,2675933,71871179,0,0,76,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,3,,,,0,5912,181,82,313,57,,0,3,6545,,,", + "foo-https-farm,preprod-app,0,0,0,3,60,6219,2577996,71804141,,0,,1,30,3,0,UP,12,1,0,580,142,10893,257923,,1,2,1,,1948,,2,0,,2,L7OK,200,70,0,5912,181,11,29,0,0,,,,501,0,", + "foo-https-farm,preprod-bg,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,4,4,2538799,4603673,,1,2,2,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", + "foo-https-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5102837,5102837,,1,2,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", + "foo-https-farm,BACKEND,0,0,0,3,200,6469,2675933,71871179,0,0,,254,30,3,0,UP,12,1,0,,142,10893,300288,,1,2,0,,1948,,1,0,,2,,,,0,5912,181,11,313,52,,,,,501,0,"] + @info_422 = ["Name: HAProxy", "Version: 1.4.22", "Release_date: 2012/08/09", "Nbproc: 1", "Process_num: 1", + "Pid: 3803", "Uptime: 0d 2h46m58s", "Uptime_sec: 10018", "Memmax_MB: 0", "Ulimit-n: 536", + "Maxsock: 536", "Maxconn: 256", "Maxpipes: 0", "CurrConns: 1", "PipesUsed: 0", "PipesFree: 0", + "Tasks: 12", "Run_queue: 1", "node: haproxy1.company.com", "description:"] + + + + end + + before :each do + HAProxyManager::HAPSocket.any_instance.stubs(:execute).returns(@allstats) + + instance = HAProxyManager::Instance.new("foo") + @backend = instance.backend_instances.to_a.first.last + end + + it 'should create a backend instance' do + @backend.should be_instance_of HAProxyManager::Backend + end + + it 'should have a socket instance' do + @backend.socket.should be_instance_of HAProxyManager::HAPSocket + end + + it 'should return an array of servers' do + @backend.server_names.should be_instance_of Array + @backend.server_names.include?('FRONTEND').should_not be_true + @backend.server_names.include?('BACKEND').should_not be_true + end + + it 'should have the server preprod-app in the backend' do + @backend.has_server?('preprod-app').should be_true + @backend.servers.fetch('preprod-app').should be_instance_of(HAProxyManager::Server) + + end + + it 'should return nil if server is not in backend' do + @backend.has_server?('blah').should be_false + + end + + it 'should have a backend name ' do + @backend.name.should eq('foo-farm') + end + + describe('stats') do + before :each do + HAPSocket.any_instance.expects(:execute).with('show stat -1 -1 -1').returns(@allstats) + end + + it 'should return hash' do + @backend.stats.should be_instance_of Hash + @backend.stats.length.should eq(5) + end + + it 'should have frontend data' do + @backend.frontend_stats.should_not be_nil + end + + it 'should have backend data' do + @backend.backend_stats.should_not be_nil + end + + end + + + it 'status should be UP' do + HAProxyManager::Server.any_instance.stubs('status').returns('UP') + @backend.status.should eq('UP') + end + + it 'UP should return true' do + @backend.up?.should be_true + end + + it 'Down should return true' do + @backend.down?.should be_false + + end + + it 'should return valid json' do + @backend.stats_to_json.should be_instance_of String + end + + it 'should disable backend servers' do + @backend.disable.should be_true + + end + + it 'should enable backend servers' do + @backend.enable.should be_true + end + + it 'should return a count of servers' do + @backend.count.should eq(3) + end + + end +end \ No newline at end of file diff --git a/spec/fixtures/stat_response.txt b/spec/fixtures/stat_response.txt new file mode 100644 index 0000000..9dabc9c --- /dev/null +++ b/spec/fixtures/stat_response.txt @@ -0,0 +1,7 @@ +# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt, + "foo-farm,preprod-app,0,9,0,60,60,137789,34510620,3221358490,,0,,3,720,0,0,UP,12,1,0,562,143,45394,255790,,1,1,1,,113890,,2,0,,88,L7OK,200,20,0,134660,2028,147,230,0,0,,,,20,6, + "foo-farm,preprod-bg,0,0,0,3,30,31,14333,380028,,0,,0,9,4,2,DOWN,5,1,0,4,10,2453494,4518397,,1,1,2,,6,,2,0,,2,L4CON,,0,0,16,0,0,0,0,0,,,,1,0, + "foo-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5017534,5017534,,1,1,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0, + "foo-https-farm,preprod-app,0,0,0,3,60,6219,2577996,71804141,,0,,1,30,3,0,UP,12,1,0,559,137,45394,255774,,1,2,1,,1948,,2,0,,2,L7OK,200,109,0,5912,181,11,29,0,0,,,,501,0, + "foo-https-farm,preprod-bg,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,4,4,2453494,4518368,,1,2,2,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0, + "foo-https-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5017532,5017532,,1,2,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0, diff --git a/spec/instance_spec.rb b/spec/instance_spec.rb index c59e6b2..5f1bae5 100644 --- a/spec/instance_spec.rb +++ b/spec/instance_spec.rb @@ -37,7 +37,11 @@ module HAProxyManager # tests that we can create three instances given a array of sockets it 'can create 3 new instances via unique sockets' do sockets = ['/tmp/xxx1', '/tmp/xxx2', '/tmp/xxx3'] - proxies = Instance.create_instances(sockets) + proxies = HAProxyManager::Instance.create_instances(sockets) + proxies.each do | socket, proxy| + proxy.backends + + end proxies.should be_instance_of Hash proxies.keys.length.should eq(3) proxies.values.length.should eq(3) @@ -50,10 +54,17 @@ module HAProxyManager @instance = Instance.new("foo") end + it 'should return a hash of backend instances' do + backend_instances = @instance.backend_instances + backend_instances.size.should == 2 + backend_instances.keys.should include('foo-farm') + backend_instances.keys.should include('foo-https-farm') + end + it "parses stats and lists backends" do @instance.backends.size.should == 2 - @instance.backends.should include "foo-farm" - @instance.backends.should include "foo-https-farm" + @instance.backends.keys.should include "foo-farm" + @instance.backends.keys.should include "foo-https-farm" end it "parses stats and lists servers" do @@ -78,7 +89,7 @@ module HAProxyManager @instance.enable("preprod-bg", "foo-farm") end - it "enables a all servers in multiple backends" do + it "enables all servers in multiple backends" do HAPSocket.any_instance.expects(:execute).with('enable server foo-farm/preprod-bg') HAPSocket.any_instance.expects(:execute).with('enable server foo-https-farm/preprod-bg') @instance.enable("preprod-bg") diff --git a/spec/server_spec.rb b/spec/server_spec.rb new file mode 100644 index 0000000..647b12b --- /dev/null +++ b/spec/server_spec.rb @@ -0,0 +1,180 @@ +require 'spec_helper' + +module HAProxyManager + describe Server do + before(:all) do + @stat_response = ["# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,", + "foo-farm,preprod-app,0,9,0,60,60,137789,34510620,3221358490,,0,,3,720,0,0,UP,12,1,0,562,143,45394,255790,,1,1,1,,113890,,2,0,,88,L7OK,200,20,0,134660,2028,147,230,0,0,,,,20,6,", + "foo-farm,preprod-bg,0,0,0,3,30,31,14333,380028,,0,,0,9,4,2,DOWN,5,1,0,4,10,2453494,4518397,,1,1,2,,6,,2,0,,2,L4CON,,0,0,16,0,0,0,0,0,,,,1,0,", + "foo-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5017534,5017534,,1,1,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", + "foo-https-farm,preprod-app,0,0,0,3,60,6219,2577996,71804141,,0,,1,30,3,0,UP,12,1,0,559,137,45394,255774,,1,2,1,,1948,,2,0,,2,L7OK,200,109,0,5912,181,11,29,0,0,,,,501,0,", + "foo-https-farm,preprod-bg,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,4,4,2453494,4518368,,1,2,2,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", + "foo-https-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5017532,5017532,,1,2,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,"] + @info_response = ["Name: HAProxy", "Version: 1.5-dev11", "Release_date: 2012/06/04", "Nbproc: 1", "Process_num: 1", "Pid: 4084", "Uptime: 58d 3h50m53s", "Uptime_sec: 5025053", "Memmax_MB: 0", "Ulimit-n: 40029", "Maxsock: 40029", "Maxconn: 20000", "Hard_maxconn: 20000", "Maxpipes: 0", "CurrConns: 0", "PipesUsed: 0", "PipesFree: 0", "ConnRate: 0", "ConnRateLimit: 0", "MaxConnRate: 69", "Tasks: 10", "Run_queue: 1", "Idle_pct: 100", "node: some machine on ec3", "description: Our awesome load balancer"] + @allstats = ["# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,", + "foo-farm,FRONTEND,,,0,150,2000,165893,38619996,3233457381,0,0,6504,,,,,OPEN,,,,,,,,,1,1,0,,,,0,0,0,69,,,,0,136147,2128,6654,20955,9,,0,144,165893,,,", + "foo-farm,preprod-app,0,9,0,60,60,139066,34839081,3222850212,,0,,3,725,0,0,UP,10,1,0,583,148,10893,257935,,1,1,1,,114847,,2,0,,88,L7OK,200,150,0,135827,2128,147,230,0,0,,,,20,11,", + "foo-farm,preprod-bg,0,0,0,3,30,31,14333,380028,,0,,0,9,4,2,DOWN,5,1,0,4,10,2538799,4603702,,1,1,2,,6,,2,0,,2,L4CON,,0,0,16,0,0,0,0,0,,,,1,0,", + "foo-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5102839,5102839,,1,1,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", + "foo-farm,BACKEND,0,84,0,150,200,159082,38619996,3233457381,0,0,,19991,734,4,2,UP,10,1,0,,148,10893,300268,,1,1,0,,114853,,1,0,,144,,,,0,135843,2128,147,20955,9,,,,,21,11,", + "foo-https-farm,FRONTEND,,,0,3,2000,6545,2675933,71871179,0,0,76,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,3,,,,0,5912,181,82,313,57,,0,3,6545,,,", + "foo-https-farm,preprod-app,0,0,0,3,60,6219,2577996,71804141,,0,,1,30,3,0,UP,12,1,0,580,142,10893,257923,,1,2,1,,1948,,2,0,,2,L7OK,200,70,0,5912,181,11,29,0,0,,,,501,0,", + "foo-https-farm,preprod-bg,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,4,4,2538799,4603673,,1,2,2,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", + "foo-https-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5102837,5102837,,1,2,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", + "foo-https-farm,BACKEND,0,0,0,3,200,6469,2675933,71871179,0,0,,254,30,3,0,UP,12,1,0,,142,10893,300288,,1,2,0,,1948,,1,0,,2,,,,0,5912,181,11,313,52,,,,,501,0,"] + @info_422 = ["Name: HAProxy", "Version: 1.4.22", "Release_date: 2012/08/09", "Nbproc: 1", "Process_num: 1", + "Pid: 3803", "Uptime: 0d 2h46m58s", "Uptime_sec: 10018", "Memmax_MB: 0", "Ulimit-n: 536", + "Maxsock: 536", "Maxconn: 256", "Maxpipes: 0", "CurrConns: 1", "PipesUsed: 0", "PipesFree: 0", + "Tasks: 12", "Run_queue: 1", "node: haproxy1.company.com", "description:"] + + + + end + + before :each do + HAProxyManager::HAPSocket.any_instance.stubs(:execute).returns(@allstats) + + instance = HAProxyManager::Instance.new("foo") + @print_response = Proc.new {|response| puts response} + @backend = instance.backend_instances.to_a.first.last + @server = @backend.servers['preprod-app'] + end + + it 'should create a server object' do + @server.should be_instance_of HAProxyManager::Server + end + + it 'should have list of backends' do + @server.backends.should be_instance_of(Array) + end + + it 'should return true if server is part of backend' do + @server.has_backend?('foo-farm').should be_true + end + + it 'server should be in multiple backends' do + @server.backends.length.should eq(2) + end + + it 'should be in backends foo-farm and foo-https-farm' do + @server.backends.should include('foo-farm') + @server.backends.should include('foo-https-farm') + end + + it 'should disable itself from all backends' do + HAPSocket.any_instance.expects(:execute).with('disable server foo-farm/preprod-app') + HAPSocket.any_instance.expects(:execute).with('disable server foo-https-farm/preprod-app') + @server.disable + end + + it 'should enable itself from all backends' do + HAPSocket.any_instance.expects(:execute).with('enable server foo-farm/preprod-app') + HAPSocket.any_instance.expects(:execute).with('enable server foo-https-farm/preprod-app') + @server.enable + end + + it 'should disable itself from foo-farm backend' do + HAPSocket.any_instance.expects(:execute).with('disable server foo-farm/preprod-app') + @server.disable('foo-farm') + end + + it 'should enable itself from foo-farm backend' do + HAPSocket.any_instance.expects(:execute).with('enable server foo-farm/preprod-app') + @server.enable('foo-farm') + end + + it 'should disable itself from foo-farm backend' do + HAPSocket.any_instance.expects(:execute).times(0).with('disable server foo-farm/preprod-app') + @server.disable('foo-farm1') + end + + it 'should enable itself from foo-farm backend' do + HAPSocket.any_instance.expects(:execute).times(0).with('enable server foo-farm/preprod-app') + @server.enable('foo-farm1') + end + it 'should return UP when no backend is specified' do + @server.status.should eq('UP') + end + + it 'should return UP when a backend is specified' do + @server.status('foo-farm').should eq('UP') + end + + it 'should return true when up when no backend is provided' do + @server.up?.should be_true + end + it 'should return true when up when a backend is provided' do + @server.up?('foo-farm').should be_true + end + it 'should return false when down and no backend is provided' do + @server.down?.should be_false + end + + it 'should return false when down and a backend is provided' do + @server.down?.should be_false + end + it 'should return the current weight of the server for the specific backend' do + @server.weight('foo-farm').should eq([10]) + end + + it 'should return the current weight of the server for the specific backend' do + @server.weight.should eq([10, 12]) + end + + it 'should set the weight of the server for the specific backend as a fixnum' do + HAPSocket.any_instance.expects(:execute).times(1).with('set weight foo-farm/preprod-app 20') + @server.set_weight(20, 'foo-farm') + end + + it 'should set the weight of the server for all backends as a fixnum' do + HAPSocket.any_instance.expects(:execute).times(1).with('set weight foo-farm/preprod-app 20') + HAPSocket.any_instance.expects(:execute).times(1).with('set weight foo-https-farm/preprod-app 20') + @server.set_weight(20) + end + + it 'should set the weight of the server for the specific backend as a string' do + HAPSocket.any_instance.expects(:execute).times(1).with('set weight foo-farm/preprod-app 20') + @server.set_weight('20', 'foo-farm') + end + + it 'should set the weight of the server for all backends as a string' do + HAPSocket.any_instance.expects(:execute).times(1).with('set weight foo-farm/preprod-app 20') + HAPSocket.any_instance.expects(:execute).times(1).with('set weight foo-https-farm/preprod-app 20') + @server.set_weight('20') + end + + it 'should return an array of stat names' do + @server.stat_names.should be_instance_of(Array) + @server.stat_names.should include('pxname', 'status', 'svname') + end + + it 'method missing should return method not found if method is not a stat name' do + expect{ @server.foo }.to raise_error(NoMethodError) + end + + it 'method missing should return a hash of items with the total' do + hash = @server.send('bin') + hash.has_key?('total').should be_true + hash['total'].should > 0 + end + + it 'method missing should return for every item in stat_names' do + @server.stat_names.each do |name| + hash = @server.send(name) + hash.should_not be_nil + end + end + + it 'method missing should not error out if stat is a non integer value' do + @server.send('pxname').should eq({"foo-farm/pxname"=>"foo-farm", "foo-https-farm/pxname"=>"foo-https-farm", "total"=>0}) + end + + it 'method missing should be able to convert strings to integers easily' do + @server.send('bin').should eq({"foo-farm/bin"=>"34839081", "foo-https-farm/bin"=>"2577996", "total"=>37417077}) + end + + + + end + +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 34e3cf5..a458e35 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,4 @@ +require 'json' require 'rspec' require File.expand_path('../../lib/haproxy_manager', __FILE__) From f99375f5d63c2cd83ddd14cd4e96fc57e884f7e2 Mon Sep 17 00:00:00 2001 From: Corey Osman Date: Mon, 2 Dec 2013 21:57:21 -0800 Subject: [PATCH 2/8] refactored instance to work with backend and server objects --- lib/haproxy_manager.rb | 3 +- lib/haproxy_manager/hapsocket.rb | 22 ++++++++++ lib/haproxy_manager/instance.rb | 69 ++++++++++++-------------------- spec/instance_spec.rb | 20 +++++---- 4 files changed, 62 insertions(+), 52 deletions(-) create mode 100644 lib/haproxy_manager/hapsocket.rb diff --git a/lib/haproxy_manager.rb b/lib/haproxy_manager.rb index e9212c6..ca38c9b 100644 --- a/lib/haproxy_manager.rb +++ b/lib/haproxy_manager.rb @@ -1,3 +1,4 @@ require 'haproxy_manager/instance' require 'haproxy_manager/backend' -require 'haproxy_manager/server' \ No newline at end of file +require 'haproxy_manager/server' +require 'haproxy_manager/hapsocket' \ No newline at end of file diff --git a/lib/haproxy_manager/hapsocket.rb b/lib/haproxy_manager/hapsocket.rb new file mode 100644 index 0000000..d9ea2c7 --- /dev/null +++ b/lib/haproxy_manager/hapsocket.rb @@ -0,0 +1,22 @@ +require 'socket' +module HAProxyManager + + class HAPSocket + def initialize(file) + @file = file + end + + def execute(cmd, &block) + socket = UNIXSocket.new(@file) + socket.write("#{cmd};") + response = [] + socket.each do |line| + data = line.strip + next if data.empty? + response << data + end + yield response if block_given? + response + end + end +end diff --git a/lib/haproxy_manager/instance.rb b/lib/haproxy_manager/instance.rb index 285c35a..89b23a0 100644 --- a/lib/haproxy_manager/instance.rb +++ b/lib/haproxy_manager/instance.rb @@ -1,7 +1,7 @@ -require 'socket' + module HAProxyManager class Instance - attr_reader :backends, :backend_instances + attr_reader :backends, :backend_instances, :server_instances def initialize(socket) @socket = HAPSocket.new(socket) @@ -27,19 +27,18 @@ def self.create_instances(sockets=[]) # If backend is not specified then all the backends in which the serverid exists are disabled. # A disabled server shows up as in Maintance mode. def disable(serverid, backend = nil) - all_servers(serverid, backend).each do |item| - @socket.execute "disable server #{item[0]}/#{item[1]}", &@print_response - end + server = server_instances[serverid] + server.disable(backend) end # Enables a server in the server in a backend. # If backend is not specified then all the backends in which the serverid exists are enabled. def enable(serverid, backend = nil) - all_servers(serverid, backend).each do |item| - @socket.execute "enable server #{item[0]}/#{item[1]}", &@print_response - end + server = server_instances[serverid] + server.enable(backend) end + # returns a hash of backend objects # rereads the backend data each time when refresh is true def backend_instances(refresh=false) if @backend_instances.nil? or refresh @@ -103,22 +102,34 @@ def stats end end - # returns an array of servers + # returns a hash of server instances objects that contain unique server names + def server_instances(refresh=false) + if @server_instances.nil? or refresh + @server_instances = {} + backend_instances.each do |key, backend| + @server_instances = @server_instances.merge(backend.servers) + end + end + @server_instances + end + + # returns an array of servers that are uqniue in case the same server exists in def servers(backend = nil) - servers = [] + my_servers = [] if backend.nil? # return all servers backend_instances.each_value do | backend| - servers << backend.servers + my_servers << backend.server_names end else begin - servers = backend_instances[backend].servers + my_servers = backend_instances[backend].server_names rescue KeyError => e "The backend #{backend} is not a valid argument" end end - return servers.flatten + # return a list of serv + my_servers.flatten.uniq end # resets Haproxy counters. If no option is specified backend and frontend counters are cleared, but @@ -129,38 +140,8 @@ def reset_counters(option = "") @socket.execute "clear counters {option}", &@print_response end - private - # returns array will all backends the server belongs to - def all_servers(serverid, backend) - # return all backends the serverid belongs to - if backend.nil? or ! backend_instances[backend].has_server?(serverid) - items = [] - backend_instances.each do |name, backend| - next if ! backend.has_server?(serverid) - items << [name, serverid] - end - else - items = [[backend, serverid]] - end - end + end - class HAPSocket - def initialize(file) - @file = file - end - def execute(cmd, &block) - socket = UNIXSocket.new(@file) - socket.write("#{cmd};") - response = [] - socket.each do |line| - data = line.strip - next if data.empty? - response << data - end - yield response if block_given? - response - end - end end \ No newline at end of file diff --git a/spec/instance_spec.rb b/spec/instance_spec.rb index 5f1bae5..3e88985 100644 --- a/spec/instance_spec.rb +++ b/spec/instance_spec.rb @@ -31,7 +31,7 @@ module HAProxyManager describe 'multiple instances' do before(:each) do - HAPSocket.any_instance.expects(:execute).times(3).returns(@stat_response) + HAPSocket.any_instance.stubs(:execute).returns(@stat_response) end # tests that we can create three instances given a array of sockets @@ -50,7 +50,7 @@ module HAProxyManager describe "creation" do before(:each) do - HAPSocket.any_instance.expects(:execute).once.returns(@stat_response) + HAPSocket.any_instance.stubs(:execute).returns(@stat_response) @instance = Instance.new("foo") end @@ -71,16 +71,21 @@ module HAProxyManager @instance.servers('foo-farm').size.should == 3 end it "understands servers without backend are all servers" do - @instance.servers.size.should == 6 + @instance.servers.size.should == 3 @instance.servers.should include "preprod-bg" @instance.servers.should include "preprod-test" end + it 'should return a hash of server instances from all backends that are unqiue' do + @instance.server_instances.should be_instance_of(Hash) + @instance.server_instances.length.should eq(3) + + end end describe "enables/disables servers" do before(:each) do - HAPSocket.any_instance.expects(:execute).once.returns(@stat_response) + HAPSocket.any_instance.stubs(:execute).returns(@stat_response) @instance = Instance.new("foo") end @@ -108,7 +113,7 @@ module HAProxyManager end describe "weights" do before(:each) do - HAPSocket.any_instance.expects(:execute).once.returns(@stat_response) + HAPSocket.any_instance.stubs(:execute).returns(@stat_response) @instance = Instance.new("foo") end @@ -124,10 +129,11 @@ module HAProxyManager weights = @instance.weights "preprod-bg","foo-farm", 20 end end + describe "stats" do before(:each) do - HAPSocket.any_instance.expects(:execute).once.returns(@stat_response) + HAPSocket.any_instance.stubs(:execute).returns(@stat_response) @instance = Instance.new("foo") end @@ -152,7 +158,7 @@ module HAProxyManager describe "info about haproxy" do before(:each) do - HAPSocket.any_instance.expects(:execute).once.returns(@stat_response) + HAPSocket.any_instance.stubs(:execute).returns(@stat_response) @instance = Instance.new("foo") end From 00393d28f287fced17df776a3c4da4b3ff3039ed Mon Sep 17 00:00:00 2001 From: Corey Osman Date: Mon, 2 Dec 2013 22:44:20 -0800 Subject: [PATCH 3/8] added myself as another author --- haproxy_manager.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/haproxy_manager.gemspec b/haproxy_manager.gemspec index d6f3725..70855d0 100644 --- a/haproxy_manager.gemspec +++ b/haproxy_manager.gemspec @@ -4,7 +4,7 @@ Gem::Specification.new do |s| s.name = "haproxy_manager" s.version = HAProxyManager::VERSION s.platform = Gem::Platform::RUBY - s.authors = ["Sreekanth(sreeix)", "Smita Bhat(sbhat)"] + s.authors = ["Corey Osman(logicminds)", "Sreekanth(sreeix)", "Smita Bhat(sbhat)"] s.email = ["gabbar@activesphere.com", "sbhat@altheasystems.com"] s.homepage = "https://github.com/althea/haproxy-manager" s.summary = 'HAproxy manager for controlling haproxy' From 65ebc78feeb915220731e9c85b3a4b4d495915f8 Mon Sep 17 00:00:00 2001 From: Corey Osman Date: Mon, 2 Dec 2013 22:45:35 -0800 Subject: [PATCH 4/8] version bump --- lib/haproxy_manager/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/haproxy_manager/version.rb b/lib/haproxy_manager/version.rb index 6e61dfd..d8dc16c 100644 --- a/lib/haproxy_manager/version.rb +++ b/lib/haproxy_manager/version.rb @@ -1,3 +1,3 @@ module HAProxyManager - VERSION = "0.1.3" + VERSION = "0.2.0" end From b2bb2ca50b9077cff63672420c5e56aabd7843e3 Mon Sep 17 00:00:00 2001 From: Corey Osman Date: Tue, 3 Dec 2013 21:04:51 -0800 Subject: [PATCH 5/8] added error checking for invalid server id entries --- lib/haproxy_manager/instance.rb | 16 ++++++++++++---- spec/instance_spec.rb | 10 ++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/haproxy_manager/instance.rb b/lib/haproxy_manager/instance.rb index 89b23a0..4adf509 100644 --- a/lib/haproxy_manager/instance.rb +++ b/lib/haproxy_manager/instance.rb @@ -27,15 +27,23 @@ def self.create_instances(sockets=[]) # If backend is not specified then all the backends in which the serverid exists are disabled. # A disabled server shows up as in Maintance mode. def disable(serverid, backend = nil) - server = server_instances[serverid] - server.disable(backend) + if server_instances.has_key?(serverid) + server = server_instances[serverid] + server.disable(backend) + else + raise "InvalidServerId" + end end # Enables a server in the server in a backend. # If backend is not specified then all the backends in which the serverid exists are enabled. def enable(serverid, backend = nil) - server = server_instances[serverid] - server.enable(backend) + if server_instances.has_key?(serverid) + server = server_instances[serverid] + server.enable(backend) + else + raise "InvalidServerId" + end end # returns a hash of backend objects diff --git a/spec/instance_spec.rb b/spec/instance_spec.rb index 3e88985..60414d3 100644 --- a/spec/instance_spec.rb +++ b/spec/instance_spec.rb @@ -100,6 +100,16 @@ module HAProxyManager @instance.enable("preprod-bg") end + it 'when enabling it raises an error when the serverid does not exist' do + expect{ @instance.enable('foo') }.to raise_error(RuntimeError) + + end + + it 'when disabling it raises and error when the serverid does not exist' do + expect{ @instance.disable('foo') }.to raise_error(RuntimeError) + + end + it "disables a server" do HAPSocket.any_instance.expects(:execute).with('disable server foo-farm/preprod-bg') @instance.disable("preprod-bg", "foo-farm") From eb100cbd35390dd2812fe005db4f52ccaea7335e Mon Sep 17 00:00:00 2001 From: Corey Osman Date: Wed, 4 Dec 2013 13:24:01 -0800 Subject: [PATCH 6/8] -- updated the status command to only work with a single backend -- added several new methods to backend class to find all the servers with a paticular status easily --- lib/haproxy_manager/backend.rb | 41 +++++++++++++++++++++++++++++++++- lib/haproxy_manager/server.rb | 28 +++++++++++++---------- spec/backend_spec.rb | 34 ++++++++++++++++++++++++++-- spec/server_spec.rb | 31 ++++++++++++++++--------- 4 files changed, 108 insertions(+), 26 deletions(-) diff --git a/lib/haproxy_manager/backend.rb b/lib/haproxy_manager/backend.rb index 66f72f6..75df4c1 100644 --- a/lib/haproxy_manager/backend.rb +++ b/lib/haproxy_manager/backend.rb @@ -1,3 +1,4 @@ +require 'json' module HAProxyManager class Backend attr_reader :name, :servers, :socket @@ -13,6 +14,7 @@ def server_names servers.keys end + # returns the number of servers in the pool no matter what status they have def count servers.length end @@ -21,7 +23,7 @@ def servers if @servers.length < 1 srv_names = stats.keys - ['FRONTEND', 'BACKEND'] srv_names.each do | srv| - @servers[srv] = Server.new(srv, socket) + @servers[srv] = HAProxyManager::Server.new(srv, socket) end end @servers @@ -102,5 +104,42 @@ def status def stats_names stats.keys end + + # returns the number of servers that have status of up + def up_count + servers_up.length + end + + # returns the number of servers that have status of down + def down_count + servers_down.length + end + + # returns the number of servers that have status of maint + def maint_count + servers_maint.length + end + + # returns an array of the servers that are currently up + def servers_up + servers.values.find_all do |server| + server.up?(name) + end + end + + # returns an array of the servers that are currently down + def servers_down + servers.values.find_all do |server| + server.down?(name) + end + end + + # returns an array of the servers that are currently in maint mode + def servers_maint + servers.values.find_all do |server| + server.maint?(name) + end + end + end end diff --git a/lib/haproxy_manager/server.rb b/lib/haproxy_manager/server.rb index c6b2939..ff98422 100644 --- a/lib/haproxy_manager/server.rb +++ b/lib/haproxy_manager/server.rb @@ -1,3 +1,4 @@ +require 'json' module HAProxyManager class Server attr_reader :backends, :name, :status, :socket @@ -62,34 +63,32 @@ def enable(backend = nil) end end - # get the status of the server for all backends or just one backend - # If any backend has marked the server as down, the status will be down, otherwise up - def status(backend=nil) + # get the status of the server for just one backend + # If all backends marked the server as down, the status will be down, otherwise up + # Note: this does not account for servers in maint mode + def status(backend) if backend.nil? or ! has_backend?(backend) - if stats.detect { |key, value| value['status'] == 'DOWN' } - 'DOWN' - else - 'UP' - end + raise 'InvalidBackendName' else stats["#{backend}/#{name}"].fetch('status') end - end # returns boolean if the server is up in all backends. Passing a backend will return boolean if up is in # the specified backend - def up?(backend=nil) + def up?(backend) status(backend) == 'UP' end # returns boolean if the server is down in all backends. Passing a backend will return boolean if down is in # the specified backend - def down?(backend=nil) + def down?(backend) status(backend) == 'DOWN' end - + def maint?(backend) + status(backend) == 'MAINT' + end def method_missing(method, *args, &block) if not stat_names.include?(method.to_s) @@ -173,6 +172,11 @@ def stats end mystats end + + def stats_to_json + JSON.pretty_generate(stats) + end + private def total(hash) diff --git a/spec/backend_spec.rb b/spec/backend_spec.rb index 2f892a4..a25f511 100644 --- a/spec/backend_spec.rb +++ b/spec/backend_spec.rb @@ -14,7 +14,7 @@ module HAProxyManager "foo-farm,FRONTEND,,,0,150,2000,165893,38619996,3233457381,0,0,6504,,,,,OPEN,,,,,,,,,1,1,0,,,,0,0,0,69,,,,0,136147,2128,6654,20955,9,,0,144,165893,,,", "foo-farm,preprod-app,0,9,0,60,60,139066,34839081,3222850212,,0,,3,725,0,0,UP,10,1,0,583,148,10893,257935,,1,1,1,,114847,,2,0,,88,L7OK,200,150,0,135827,2128,147,230,0,0,,,,20,11,", "foo-farm,preprod-bg,0,0,0,3,30,31,14333,380028,,0,,0,9,4,2,DOWN,5,1,0,4,10,2538799,4603702,,1,1,2,,6,,2,0,,2,L4CON,,0,0,16,0,0,0,0,0,,,,1,0,", - "foo-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5102839,5102839,,1,1,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", + "foo-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,MAINT,5,1,0,0,1,5102839,5102839,,1,1,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", "foo-farm,BACKEND,0,84,0,150,200,159082,38619996,3233457381,0,0,,19991,734,4,2,UP,10,1,0,,148,10893,300268,,1,1,0,,114853,,1,0,,144,,,,0,135843,2128,147,20955,9,,,,,21,11,", "foo-https-farm,FRONTEND,,,0,3,2000,6545,2675933,71871179,0,0,76,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,3,,,,0,5912,181,82,313,57,,0,3,6545,,,", "foo-https-farm,preprod-app,0,0,0,3,60,6219,2577996,71804141,,0,,1,30,3,0,UP,12,1,0,580,142,10893,257923,,1,2,1,,1948,,2,0,,2,L7OK,200,70,0,5912,181,11,29,0,0,,,,501,0,", @@ -34,7 +34,7 @@ module HAProxyManager HAProxyManager::HAPSocket.any_instance.stubs(:execute).returns(@allstats) instance = HAProxyManager::Instance.new("foo") - @backend = instance.backend_instances.to_a.first.last + @backend = instance.backend_instances['foo-farm'] end it 'should create a backend instance' do @@ -107,7 +107,33 @@ module HAProxyManager it 'should disable backend servers' do @backend.disable.should be_true + end + + it 'should return up count of 1' do + @backend.up_count.should eq(1) + end + + it 'should return down count of 1' do + @backend.down_count.should eq(1) + end + + it 'should return maint count of 1' do + @backend.maint_count.should eq(1) + end + it 'should return an array of servers that are up' do + @backend.servers_up.should be_instance_of(Array) + @backend.servers_up.length.should eq(1) + end + + it 'should return an array of servers that are down' do + @backend.servers_down.should be_instance_of(Array) + @backend.servers_down.length.should eq(1) + end + + it 'should return an array of servers that are maint' do + @backend.servers_maint.should be_instance_of(Array) + @backend.servers_maint.length.should eq(1) end it 'should enable backend servers' do @@ -118,5 +144,9 @@ module HAProxyManager @backend.count.should eq(3) end + it 'should return json listing' do + expect{@backend.stats_to_json}.to_not raise_error + end + end end \ No newline at end of file diff --git a/spec/server_spec.rb b/spec/server_spec.rb index 647b12b..d88b2c1 100644 --- a/spec/server_spec.rb +++ b/spec/server_spec.rb @@ -6,7 +6,7 @@ module HAProxyManager @stat_response = ["# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,", "foo-farm,preprod-app,0,9,0,60,60,137789,34510620,3221358490,,0,,3,720,0,0,UP,12,1,0,562,143,45394,255790,,1,1,1,,113890,,2,0,,88,L7OK,200,20,0,134660,2028,147,230,0,0,,,,20,6,", "foo-farm,preprod-bg,0,0,0,3,30,31,14333,380028,,0,,0,9,4,2,DOWN,5,1,0,4,10,2453494,4518397,,1,1,2,,6,,2,0,,2,L4CON,,0,0,16,0,0,0,0,0,,,,1,0,", - "foo-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5017534,5017534,,1,1,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", + "foo-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,MAINT,5,1,0,0,1,5017534,5017534,,1,1,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", "foo-https-farm,preprod-app,0,0,0,3,60,6219,2577996,71804141,,0,,1,30,3,0,UP,12,1,0,559,137,45394,255774,,1,2,1,,1948,,2,0,,2,L7OK,200,109,0,5912,181,11,29,0,0,,,,501,0,", "foo-https-farm,preprod-bg,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,4,4,2453494,4518368,,1,2,2,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", "foo-https-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5017532,5017532,,1,2,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,"] @@ -15,7 +15,7 @@ module HAProxyManager "foo-farm,FRONTEND,,,0,150,2000,165893,38619996,3233457381,0,0,6504,,,,,OPEN,,,,,,,,,1,1,0,,,,0,0,0,69,,,,0,136147,2128,6654,20955,9,,0,144,165893,,,", "foo-farm,preprod-app,0,9,0,60,60,139066,34839081,3222850212,,0,,3,725,0,0,UP,10,1,0,583,148,10893,257935,,1,1,1,,114847,,2,0,,88,L7OK,200,150,0,135827,2128,147,230,0,0,,,,20,11,", "foo-farm,preprod-bg,0,0,0,3,30,31,14333,380028,,0,,0,9,4,2,DOWN,5,1,0,4,10,2538799,4603702,,1,1,2,,6,,2,0,,2,L4CON,,0,0,16,0,0,0,0,0,,,,1,0,", - "foo-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,DOWN,5,1,0,0,1,5102839,5102839,,1,1,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", + "foo-farm,preprod-test,0,0,0,0,30,0,0,0,,0,,0,0,0,0,MAINT,5,1,0,0,1,5102839,5102839,,1,1,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,", "foo-farm,BACKEND,0,84,0,150,200,159082,38619996,3233457381,0,0,,19991,734,4,2,UP,10,1,0,,148,10893,300268,,1,1,0,,114853,,1,0,,144,,,,0,135843,2128,147,20955,9,,,,,21,11,", "foo-https-farm,FRONTEND,,,0,3,2000,6545,2675933,71871179,0,0,76,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,3,,,,0,5912,181,82,313,57,,0,3,6545,,,", "foo-https-farm,preprod-app,0,0,0,3,60,6219,2577996,71804141,,0,,1,30,3,0,UP,12,1,0,580,142,10893,257923,,1,2,1,,1948,,2,0,,2,L7OK,200,70,0,5912,181,11,29,0,0,,,,501,0,", @@ -36,10 +36,14 @@ module HAProxyManager instance = HAProxyManager::Instance.new("foo") @print_response = Proc.new {|response| puts response} - @backend = instance.backend_instances.to_a.first.last + @backend = instance.backend_instances['foo-farm'] @server = @backend.servers['preprod-app'] end + it 'should return json listing' do + expect{@server.stats_to_json}.to_not raise_error + end + it 'should create a server object' do @server.should be_instance_of HAProxyManager::Server end @@ -92,26 +96,30 @@ module HAProxyManager HAPSocket.any_instance.expects(:execute).times(0).with('enable server foo-farm/preprod-app') @server.enable('foo-farm1') end - it 'should return UP when no backend is specified' do - @server.status.should eq('UP') + it 'should raise error when no backend is specified' do + expect{@server.status}.to raise_exception end it 'should return UP when a backend is specified' do @server.status('foo-farm').should eq('UP') end - it 'should return true when up when no backend is provided' do - @server.up?.should be_true + it 'should return MAINT when a backend is specified' do + maintserver = @backend.servers['preprod-test'] + maintserver.status('foo-farm').should eq('MAINT') + end + + it 'should return true when the server is under maint mode' do + maintserver = @backend.servers['preprod-test'] + maintserver.maint?('foo-farm').should be_true end + it 'should return true when up when a backend is provided' do @server.up?('foo-farm').should be_true end - it 'should return false when down and no backend is provided' do - @server.down?.should be_false - end it 'should return false when down and a backend is provided' do - @server.down?.should be_false + @server.down?('foo-farm').should be_false end it 'should return the current weight of the server for the specific backend' do @server.weight('foo-farm').should eq([10]) @@ -160,6 +168,7 @@ module HAProxyManager it 'method missing should return for every item in stat_names' do @server.stat_names.each do |name| + next if name == 'status' hash = @server.send(name) hash.should_not be_nil end From 0fc31e965e7026e241937043e036691fc7e7e75e Mon Sep 17 00:00:00 2001 From: Corey Osman Date: Fri, 6 Dec 2013 20:17:18 -0800 Subject: [PATCH 7/8] added ability to determine if socket is available --- lib/haproxy_manager/hapsocket.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/haproxy_manager/hapsocket.rb b/lib/haproxy_manager/hapsocket.rb index d9ea2c7..e225fe4 100644 --- a/lib/haproxy_manager/hapsocket.rb +++ b/lib/haproxy_manager/hapsocket.rb @@ -18,5 +18,21 @@ def execute(cmd, &block) yield response if block_given? response end + + # returns bool if socket is available + def self.available?(file) + HAPSocket.new(file).available? + end + + # returns bool if socket is available + def available? + begin + execute('show info') + true + rescue Exception => e + false + end + end + end end From 3015522fa834561f796dd74bf22784b88c5e4b85 Mon Sep 17 00:00:00 2001 From: Corey Osman Date: Thu, 26 Dec 2013 14:25:22 -0800 Subject: [PATCH 8/8] updated readme --- Readme.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/Readme.md b/Readme.md index 9594a1f..87e6c62 100644 --- a/Readme.md +++ b/Readme.md @@ -33,12 +33,61 @@ Installation Add the following where approporiate(eg. deploy.rb) require "haproxy_manager" +Release Notes +============= +Version 0.2 opens up a bunch of new possibilities by allowing the user to interact with the backend or server object +directly. Additionally the API is now more object oriented. Example: + +```Ruby + +serverobj.disable('foo-farm') + +# get the stats for the server +serverobj.stats + +# use any stat name and get value for all backends and calculate total across all backends +serverobj.smax => {"foo-farm/bin"=>"34839081", "foo-https-farm/bin"=>"2577996", "total"=>37417077} + +``` + API ====== ```Ruby haproxy = HAProxyManager::Instance.new ('path to haproxy socket') + + +# New API Commands 0.2 API +haproxy.backend_instances # Returns a hash of backend instances where the backend name is the key +haproxy.server_instances # Returns a hash of server instances where the server name is the key + +# New Backend API (See class for more info) +foo_farm = haproxy.backend_instances('foo-farm') +foo_farm_servers = foo_farm.servers +foo_farm.up? # returns the status of the backend +foo_farm.up_count # returns the number of servers up in the backend +foo_farm.disable # disable the entire backend +foo_farm.smax # returns the value of smax for the backend (uses method missing) +foo_farm.bin # returns the value of bytes in for the backend (uses method missing) + +# New Server API (See class for more info) +# because the server can be used in multiple backends many methods require the backend name to be passed in +all_servers = haproxy.server_instances +foo_farm_servers = haproxy.backend_instances('foo-farm').servers +foo_farm_servers.each do | name, server| + puts "Info for #{server.name} = #{server.status('foo-farm')}" + puts "Backends: #{server.backends.join("\n")}" + puts "#{server.weight('foo-farm')" + puts "#{server.smax}" +end + +# New Hapsocket API 0.2 API +# The socket class has been moved to its own file and a new Class method has been added to determine if the socket is +# available should you need to determine if the socket can be opened +HAPSocket.available?('/home/haproxy/sockets/socketname') # returns boolean + +# below is a list of API calls that were used primarly in the 0.1 API but are still valid in 0.2 API haproxy.backends # Lists all the backends available haproxy.servers("foo-farm") # List all the servers available in the given backend