haproxy-cluster 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -15,7 +15,7 @@ While there are already a handfull of [HA Proxy](http://haproxy.1wt.edu) abstrac
15
15
 
16
16
  `haproxy_cluster` provides a shell scripting interface for `HAProxyCluster`. Exit codes are meaningful and intended to be useful from Nagios.
17
17
 
18
- Do you deploy new code using a sequential restart of application servers? Using this common pattern carelessly can result in too many servers being down at the same time, and cutomers seeing errors. `haproxy_cluster` can prevent this by ensuring that every load balancer agrees that the application is up at each stage in the deployment. In the example below, we will deploy a new WAR to three Tomcat instances which are fronted by two HA Proxy instances. HA Proxy has been configured with `option httpchk /check`, a path which only returns an affirmative status code when the application is ready to serve requests.
18
+ Do you deploy new code using a sequential restart of application servers? Using this common pattern carelessly can result in too many servers being down at the same time, and cutomers seeing errors. `haproxy_cluster` can prevent this by ensuring that every load balancer agrees that all (or enough) servers are up at each stage in the deployment. In the example below, we will deploy a new WAR to three Tomcat instances which are fronted by two HA Proxy instances. HA Proxy has been configured with `option httpchk /check`, a path which only returns an affirmative status code when the application is ready to serve requests.
19
19
 
20
20
  ```bash
21
21
  #!bin/bash
@@ -24,29 +24,31 @@ servers="server1.example.com server2.example.com server3.example.com"
24
24
  load_balancers="https://lb1.example.com:8888 http://lb2.example.com:8888"
25
25
 
26
26
  for server in $servers ; do
27
- haproxy_cluster --timeout=300 --eval "wait_until(true){ myapp.rolling_restartable? }" $load_balancers
27
+ haproxy_cluster --eval "wait_until(:condition => true){ myapp.rolling_restartable? }" $load_balancers
28
28
  scp myapp.war $server:/opt/tomcat/webapps
29
29
  done
30
30
  ```
31
31
 
32
- The code block passed to `--eval` will not return until every load balancer reports that at least 80% of the backend servers defined for "myapp" are ready to serve requests. If this takes more than 5 minutes (300 seconds), the whole deployment is halted.
32
+ The code block passed to `--eval` will not return until every load balancer reports that at least 80% of the backend servers defined for "myapp" are ready to serve requests (or all of them are down). This condition must pass 3 times in a row for it to be considered valid. If this takes more than 5 minutes (300 seconds), the whole deployment is halted.
33
33
 
34
34
  Maybe you'd like to know how many transactions per second your whole cluster is processing.
35
35
 
36
- $ haproxy_cluster --eval 'poll{ puts members.map{|m|m.myapp.rate}.inject(:+) }' $load_balancers
36
+ ```bash
37
+ haproxy_cluster --eval 'poll{ puts members.map{|m|m.myapp.rate}.inject(:+) }' $load_balancers
38
+ ```
37
39
 
38
40
  Installation
39
41
  ------------
40
42
 
41
43
  `gem install haproxy-cluster`
42
44
 
43
- Requires Ruby 1.9.2 and depends on RestClient.
45
+ Requires Ruby 1.9.3 and depends on RestClient.
44
46
 
45
47
  Non-Features
46
48
  ------------
47
49
 
48
50
  * Doesn't try to modify configuration files. Use [haproxy-tools](https://github.com/subakva/haproxy-tools), [rhaproxy](https://github.com/jjuliano/rhaproxy), [haproxy_join](https://github.com/joewilliams/haproxy_join), or better yet, [Chef](http://www.opscode.com/chef) for that.
49
- * Doesn't talk to sockets, yet. Use [haproxy-ruby](https://github.com/inkel/haproxy-ruby) for now if you need this. I intend to add support for this using `Net::SSH` and `socat(1)` but for now HTTP is enough for my needs.
51
+ * Doesn't talk to sockets, yet. Use [haproxy-ruby](https://github.com/inkel/haproxy-ruby) for now if you need this. I intend to add support for this using `Net::SSH` and `socat(1)` but for now HTTP is enough for my needs. Please comment on Issue \#6 if you care about this feature.
50
52
 
51
53
  ProTip
52
54
  ------
@@ -56,3 +58,7 @@ HA Proxy's awesome creator Willy Tarrreau loves [big text files](http://haproxy.
56
58
  * http://code.google.com/p/haproxy-docs/
57
59
  * http://cbonte.github.com/haproxy-dconv/configuration-1.5.html
58
60
 
61
+
62
+ <hr/>
63
+ [![Build Status](https://secure.travis-ci.org/jelder/haproxy_cluster.png)](http://travis-ci.org/jelder/haproxy_cluster)
64
+
@@ -1,7 +1,12 @@
1
1
  require 'haproxy_cluster/member'
2
+ require 'timeout'
2
3
  require 'thread'
3
4
 
4
5
  class HAProxyCluster
6
+
7
+ Inf = +1.0/0.0
8
+ NegInf = -1.0/0.0
9
+
5
10
  def initialize(members = [])
6
11
  @members = []
7
12
  threads = []
@@ -21,51 +26,59 @@ class HAProxyCluster
21
26
  first = true
22
27
  loop do
23
28
  start = Time.now
24
- map { poll! } unless first
29
+ each_member { poll! } unless first
25
30
  first = false
26
31
  yield
27
32
  sleep interval - (Time.now - start)
28
33
  end
29
34
  end
30
35
 
31
- # Poll the entire cluster using exponential backoff until the the given
32
- # block's return value always matches the condition (expressed as boolean or
33
- # range).
36
+ # Poll the entire cluster until the the given block's return value always
37
+ # matches the condition (expressed as boolean or range).
34
38
  #
35
- # A common form of this is:
39
+ # This block would not return until every member of the cluster is available
40
+ # to serve requests.
36
41
  #
37
- # wait_for(true) do
42
+ # wait_for(:condition => true) do
38
43
  # api.servers.map{|s|s.ok?}
39
44
  # end
40
45
  #
41
- # This block would not return until every member of the cluster is available
42
- # to serve requests.
43
- #
44
- # wait_until(1!=1){false} #=> true
45
- # wait_until(1==1){true} #=> true
46
- # wait_until(1..3){2} #=> true
47
- # wait_until(true){sleep} #=> Timeout
48
- def wait_until (condition, &code)
49
- results = map(&code)
50
- delay = 1.5
51
- loop do
52
- if reduce(condition, results.values.flatten)
53
- return true
54
- end
55
- if delay > 60
56
- puts "Too many timeouts, giving up"
57
- return false
46
+ # The constants `Inf` and `NegInf` (representing infinity and negative
47
+ # infinity respectively) are available, which enables less than/greater than
48
+ # expressions in the form of `Range`s.
49
+ #
50
+ # Parameters:
51
+ #
52
+ # * :condition, anything accepted by `check_condition`
53
+ # * :interval, check interval (default 2 seconds, same as HA Proxy)
54
+ # * :timeout, give up after this number of seconds
55
+ # * :min_checks, require :condtion to pass this many times in a row
56
+ #
57
+ def wait_until (options = {}, &code)
58
+ opts = {
59
+ :condition => true,
60
+ :interval => 2.0,
61
+ :timeout => 300,
62
+ :min_checks => 3
63
+ }.merge options
64
+ Timeout::timeout(opts[:timeout]) do
65
+ history = []
66
+ loop do
67
+ results = each_member(&code)
68
+
69
+ # Break out as soon as we reach :min_checks in a row
70
+ history << check_condition(opts[:condition], results.values.flatten)
71
+ return true if history.last(opts[:min_checks]) == [true] * opts[:min_checks]
72
+
73
+ sleep opts[:interval]
74
+ each_member { poll! }
58
75
  end
59
- delay *= 2
60
- sleep delay
61
- map { poll! }
62
- results = map(&code)
63
76
  end
64
77
  end
65
78
 
66
79
  # Run the specified code against every memeber of the cluster. Results are
67
80
  # returned as a Hash, with member.to_s being the key.
68
- def map (&code)
81
+ def each_member (&code)
69
82
  threads = []
70
83
  results = {}
71
84
  @members.each do |member|
@@ -80,7 +93,12 @@ class HAProxyCluster
80
93
  # Return true or false depending on the relationship between `condition` and `values`.
81
94
  # `condition` may be specified as true, false, or a Range object.
82
95
  # `values` is an Array of whatever type is appropriate for the condition.
83
- def reduce (condition, values)
96
+ #
97
+ # check_condition(0!=1, [true]) #=> true
98
+ # check_condition(1==1, [true]) #=> true
99
+ # check_condition(3..Inf, [2,2]) #=> false
100
+ # check_condition(true, [true,false]) #=> raise Timeout::timeout
101
+ def check_condition (condition, values)
84
102
  case condition.class.to_s
85
103
  when "Range"
86
104
  values.each{ |v| return false unless condition.cover? v }
@@ -44,7 +44,7 @@ if options.code_string
44
44
 
45
45
  case result.class.to_s
46
46
  when "TrueClass","FalseClass"
47
- exit result == true ? 0 : 1
47
+ exit result == true ? 0 : 2
48
48
  when "Hash"
49
49
  pp result
50
50
  when "Array"
@@ -20,13 +20,13 @@ class HAProxyCluster
20
20
  end
21
21
 
22
22
  def poll!
23
- case @type
23
+ csv = case @type
24
24
  when :url
25
- csv = RestClient.get(@source + ';csv').gsub(/^# /,'').gsub(/,$/,'')
25
+ RestClient.get(@source + ';csv')
26
26
  when :file
27
27
  File.read(@source)
28
28
  end
29
- CSV.parse(csv, { :headers => :first_row, :converters => :all, :header_converters => [:downcase,:symbol] } ) do |row|
29
+ CSV.parse(csv.gsub(/^# /,'').gsub(/,$/,''), { :headers => :first_row, :converters => :all, :header_converters => [:downcase,:symbol] } ) do |row|
30
30
  case row[:type]
31
31
  when BACKEND
32
32
  @backends[ row[:pxname].to_sym ].stats.merge! row.to_hash
@@ -1,3 +1,3 @@
1
1
  class HAProxyCluster
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: haproxy-cluster
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-24 00:00:00.000000000 Z
12
+ date: 2012-07-31 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rest-client
16
- requirement: &70257308044560 !ruby/object:Gem::Requirement
16
+ requirement: &70230919355820 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,8 +21,30 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70257308044560
25
- description: ! " Ruby Gem and command line tool for quickly answering questions like,
24
+ version_requirements: *70230919355820
25
+ - !ruby/object:Gem::Dependency
26
+ name: test-unit
27
+ requirement: &70230919354840 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70230919354840
36
+ - !ruby/object:Gem::Dependency
37
+ name: webmock
38
+ requirement: &70230919353540 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70230919353540
47
+ description: ! " Gem and command line tool for quickly answering questions like,
26
48
  \"Can we\n survive a rolling restart?\", \"How many transactions per second am
27
49
  I seeing?\",\n \"What's my session backlog?\". Intended for use within continuous
28
50
  deployment\n and monitoring solutions.\n"
@@ -66,6 +88,6 @@ rubyforge_project:
66
88
  rubygems_version: 1.8.11
67
89
  signing_key:
68
90
  specification_version: 3
69
- summary: Inspect and manipulate collections of HA Proxy instances
91
+ summary: A DSL for inspecting and manipulating groups of HA Proxy instances.
70
92
  test_files: []
71
93
  has_rdoc: