haproxy-cluster 0.0.4 → 0.0.5

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.
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: