cloudlb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ *.gem
data/COPYING ADDED
@@ -0,0 +1,12 @@
1
+ Unless otherwise noted, all files are released under the MIT license, exceptions contain licensing information in them.
2
+
3
+ Copyright (C) 2011 Rackspace US, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10
+
11
+ Except as contained in this notice, the name of Rackspace US, Inc. shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Rackspace US, Inc.
12
+
@@ -0,0 +1,66 @@
1
+ = Rackspace Cloud Load Balancers
2
+
3
+ == Description
4
+
5
+ This is a Ruby interface into the Rackspace[http://rackspace.com/] {Cloud Load Balancers}[http://www.rackspace.com/cloud/blog/2011/02/24/rackspace-cloud-load-balancers-beta-now-available-for-all-cloud-customers/] service. Mission-critical web-based applications and workloads require high availability. Load balancing
6
+ distributes workloads across two or more servers, network links, and other resources to maximize
7
+ throughput, minimize response time and avoid overload. Rackspace Cloud Load Balancers allow you
8
+ to quickly load balance multiple Cloud Servers for optimal resource utilization.
9
+
10
+ == Installation
11
+
12
+ This source is available on Github[http://github.com/rackspace/ruby-cloudlb/] and the gem is available on RubyGems[http://rubygems.org/]. To install it, do
13
+
14
+ sudo gem install cloudlb
15
+
16
+ To use it in Bundler, add the following statement to your Gemfile
17
+
18
+ gem "cloudlb"
19
+
20
+ == RDOC Documentation
21
+
22
+ Find the latest RDoc documentation for this library at http://rdoc.info/github/rackspace/ruby-cloudlb/master/frames
23
+
24
+ == API Documentation
25
+
26
+ This binding attempts to conform to the latest API specifications. For current API documentation, visit http://docs.rackspacecloud.com/api/
27
+
28
+ == Examples
29
+
30
+ See the class definitions for documentation on specific methods and operations.
31
+
32
+ require 'rubygems'
33
+ require 'cloudlb'
34
+
35
+ # Authenticate to the Rackspace Cloud, and choose to manage load balancers in the Dallas/Ft. Worth datacenter
36
+ lb = CloudLB::Connection.new(:username => "MY_USERNAME", :api_key => "MY_API_KEY", :region => :dfw)
37
+
38
+ # Show active load balancers
39
+ lb.list_load_balancers
40
+ => [
41
+ {:status=>"ACTIVE", :port=>80, :updated=>{:time=>"2011-02-25T03:14:49+0000"}, :created=>{:time=>"2010-12-02T20:30:49+0000"}, :protocol=>"HTTP", :algorithm=>"RANDOM", :virtualIps=>[{:type=>"PUBLIC", :id=>21, :ipVersion=>"IPV4", :address=>"174.143.139.1"}], :name=>"stage-rax-lb", :id=>1},
42
+ {:status=>"ACTIVE", :port=>80, :updated=>{:time=>"2011-02-25T15:31:16+0000"}, :created=>{:time=>"2011-02-25T01:11:31+0000"}, :protocol=>"HTTP", :algorithm=>"ROUND_ROBIN", :virtualIps=>[{:type=>"PUBLIC", :id=>38, :ipVersion=>"IPV4", :address=>"174.143.139.2"}], :name=>"Renamed LB", :id=>73}
43
+ ]
44
+
45
+ # Select a load balancer
46
+ balancer = lb.get_load_balancer(1)
47
+ => #<CloudLB::Balancer:0x101d7ebf8 @status="ACTIVE", @port=80, @name="stage-rax-lb", ...>
48
+
49
+ # Change the algorithm to round-robin
50
+ balancer.algorithm="ROUND_ROBIN"
51
+ => "ROUND_ROBIN"
52
+
53
+ # Add a new node to the load balancer
54
+ node = balancer.create_node(:address => '192.168.0.1', :port => 80)
55
+ => #<CloudLB::Node:0x101d48c88 @status="ONLINE", @port=80, ...>
56
+
57
+ == Authors
58
+
59
+ {H. Wade Minter}[https://github.com/minter/] <minter@lunenburg.org>
60
+
61
+ == License
62
+
63
+ See COPYING for license information.
64
+ Copyright (c) 2011, Rackspace US, Inc.
65
+
66
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib/', __FILE__)
3
+ $:.unshift lib unless $:.include?(lib)
4
+
5
+ require 'cloudlb'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "cloudlb"
9
+ s.version = CloudLB::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ["H. Wade Minter"]
12
+ s.email = ["minter@lunenburg.org"]
13
+ s.homepage = "http://github.com/rackspace/ruby-cloudlb"
14
+ s.summary = "Ruby API into the Rackspace Cloud Load Balancers product"
15
+ s.description = "A Ruby API to manage the Rackspace Cloud Load Balancers product"
16
+
17
+ s.required_rubygems_version = ">= 1.3.6"
18
+
19
+ s.add_runtime_dependency "typhoeus"
20
+ s.add_runtime_dependency "json"
21
+
22
+ s.files = [
23
+ "VERSION",
24
+ "COPYING",
25
+ ".gitignore",
26
+ "README.rdoc",
27
+ "cloudlb.gemspec",
28
+ "lib/cloudlb.rb",
29
+ "lib/cloudlb/authentication.rb",
30
+ "lib/cloudlb/balancer.rb",
31
+ "lib/cloudlb/connection.rb",
32
+ "lib/cloudlb/exception.rb",
33
+ "lib/cloudlb/node.rb",
34
+ "lib/cloudlb/health_monitor.rb",
35
+ "lib/cloudlb/connection_throttle.rb"
36
+ ]
37
+ s.require_path = 'lib'
38
+ end
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # == Cloud Load Balancers API
4
+ # ==== Connects Ruby Applications to Rackspace's {Cloud Load Balancers service}[http://www.rackspacecloud.com/cloud_hosting_products/servers]
5
+ # By H. Wade Minter <minter@lunenburg.org>
6
+ #
7
+ # See COPYING for license information.
8
+ # Copyright (c) 2011, Rackspace US, Inc.
9
+ # ----
10
+ #
11
+ # === Documentation & Examples
12
+ # To begin reviewing the available methods and examples, peruse the README.rodc file, or begin by looking at documentation for the
13
+ # CloudLB::Connection class.
14
+ #
15
+ # The CloudLB class is the base class. Not much of note aside from housekeeping happens here.
16
+ # To create a new CloudLB connection, use the CloudLB::Connection.new method.
17
+
18
+ module CloudLB
19
+
20
+ AUTH_USA = "https://auth.api.rackspacecloud.com/v1.0"
21
+ AUTH_UK = "https://lon.auth.api.rackspacecloud.com/v1.0"
22
+
23
+ VERSION = IO.read(File.dirname(__FILE__) + '/../VERSION').chomp
24
+ require 'uri'
25
+ require 'rubygems'
26
+ require 'json'
27
+ require 'date'
28
+ require 'typhoeus'
29
+
30
+ unless "".respond_to? :each_char
31
+ require "jcode"
32
+ $KCODE = 'u'
33
+ end
34
+
35
+ $:.unshift(File.dirname(__FILE__))
36
+ require 'cloudlb/exception'
37
+ require 'cloudlb/authentication'
38
+ require 'cloudlb/connection'
39
+ require 'cloudlb/balancer'
40
+ require 'cloudlb/node'
41
+ require 'cloudlb/health_monitor'
42
+ require 'cloudlb/connection_throttle'
43
+
44
+ # Helper method to recursively symbolize hash keys.
45
+ def self.symbolize_keys(obj)
46
+ case obj
47
+ when Array
48
+ obj.inject([]){|res, val|
49
+ res << case val
50
+ when Hash, Array
51
+ symbolize_keys(val)
52
+ else
53
+ val
54
+ end
55
+ res
56
+ }
57
+ when Hash
58
+ obj.inject({}){|res, (key, val)|
59
+ nkey = case key
60
+ when String
61
+ key.to_sym
62
+ else
63
+ key
64
+ end
65
+ nval = case val
66
+ when Hash, Array
67
+ symbolize_keys(val)
68
+ else
69
+ val
70
+ end
71
+ res[nkey] = nval
72
+ res
73
+ }
74
+ else
75
+ obj
76
+ end
77
+ end
78
+
79
+ def self.hydra
80
+ @@hydra ||= Typhoeus::Hydra.new
81
+ end
82
+
83
+ # CGI.escape, but without special treatment on spaces
84
+ def self.escape(str,extra_exclude_chars = '')
85
+ str.gsub(/([^a-zA-Z0-9_.-#{extra_exclude_chars}]+)/) do
86
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
87
+ end
88
+ end
89
+
90
+ def self.paginate(options = {})
91
+ path_args = []
92
+ path_args.push(URI.encode("limit=#{options[:limit]}")) if options[:limit]
93
+ path_args.push(URI.encode("offset=#{options[:offset]}")) if options[:offset]
94
+ path_args.join("&")
95
+ end
96
+
97
+
98
+ end
@@ -0,0 +1,44 @@
1
+ module CloudLB
2
+ class Authentication
3
+
4
+ # Performs an authentication to the Rackspace Cloud authorization servers. Opens a new HTTP connection to the API server,
5
+ # sends the credentials, and looks for a successful authentication. If it succeeds, it sets the svrmgmthost,
6
+ # svrmgtpath, svrmgmtport, svrmgmtscheme, authtoken, and authok variables on the connection. If it fails, it raises
7
+ # an exception.
8
+ #
9
+ # Should probably never be called directly.
10
+ def initialize(connection)
11
+ request = Typhoeus::Request.new(connection.auth_url,
12
+ :method => :get,
13
+ :headers => { "X-Auth-User" => connection.authuser, "X-Auth-Key" => connection.authkey },
14
+ :disable_ssl_peer_verification => true,
15
+ :verbose => ENV['LOADBALANCERS_VERBOSE'] ? true : false)
16
+ CloudLB.hydra.queue(request)
17
+ CloudLB.hydra.run
18
+ response = request.response
19
+ headers = response.headers_hash
20
+ if (response.code.to_s == "204")
21
+ connection.authtoken = headers["x-auth-token"]
22
+ user_id = headers["x-server-management-url"].match(/.*\/(\d+)$/)[1]
23
+ headers["x-server-management-url"] = case connection.region
24
+ when :ord
25
+ "https://ord.loadbalancers.api.rackspacecloud.com/v1.0/#{user_id}"
26
+ when :dfw
27
+ "https://dfw.loadbalancers.api.rackspacecloud.com/v1.0/#{user_id}"
28
+ else
29
+ raise
30
+ end
31
+ connection.lbmgmthost = URI.parse(headers["x-server-management-url"]).host
32
+ connection.lbmgmtpath = URI.parse(headers["x-server-management-url"]).path.chomp
33
+ # Force the path into the v1.0 URL space
34
+ connection.lbmgmtpath.sub!(/\/.*?\//, '/v1.0/')
35
+ connection.lbmgmtport = URI.parse(headers["x-server-management-url"]).port
36
+ connection.lbmgmtscheme = URI.parse(headers["x-server-management-url"]).scheme
37
+ connection.authok = true
38
+ else
39
+ connection.authtoken = false
40
+ raise CloudLB::Exception::Authentication, "Authentication failed with response code #{response.code}"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,233 @@
1
+ module CloudLB
2
+ class Balancer
3
+
4
+ attr_reader :id
5
+ attr_reader :name
6
+ attr_reader :protocol
7
+ attr_reader :port
8
+ attr_reader :connection
9
+ attr_reader :algorithm
10
+ attr_reader :connection_logging
11
+ attr_reader :status
12
+
13
+ # Creates a new CloudLB::Balancer object representing a Load Balancer instance.
14
+ def initialize(connection,id)
15
+ @connection = connection
16
+ @id = id
17
+ @lbmgmthost = connection.lbmgmthost
18
+ @lbmgmtpath = connection.lbmgmtpath
19
+ @lbmgmtport = connection.lbmgmtport
20
+ @lbmgmtscheme = connection.lbmgmtscheme
21
+ populate
22
+ return self
23
+ end
24
+
25
+ # Updates the information about the current Balancer object by making an API call.
26
+ def populate
27
+ response = @connection.lbreq("GET",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}",@lbmgmtport,@lbmgmtscheme)
28
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
29
+ data = JSON.parse(response.body)['loadBalancer']
30
+ @id = data["id"]
31
+ @name = data["name"]
32
+ @protocol = data["protocol"]
33
+ @port = data["port"]
34
+ @algorithm = data["algorithm"]
35
+ @connection_logging = data["connectionLogging"]
36
+ @status = data["status"]
37
+ true
38
+ end
39
+ alias :refresh :populate
40
+
41
+ # Lists the virtual IP addresses associated with this Balancer
42
+ #
43
+ # >> b.list_virtual_ips
44
+ # => [{:type=>"PUBLIC", :address=>"174.143.139.191", :ipVersion=>"IPV4", :id=>38}]
45
+ def list_virtual_ips
46
+ response = @connection.lbreq("GET", @lbmgmthost, "#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}/virtualips",@lbmgmtport,@lbmgmtscheme)
47
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
48
+ CloudLB.symbolize_keys(JSON.parse(response.body)["virtualIps"])
49
+ end
50
+ alias :virtual_ips :list_virtual_ips
51
+
52
+ # Lists the backend nodes that this Balancer sends traffic to.
53
+ #
54
+ # >> b.list_nodes
55
+ # => [{:status=>"ONLINE", :port=>80, :address=>"173.203.218.1", :condition=>"ENABLED", :id=>1046}]
56
+ def list_nodes
57
+ response = @connection.lbreq("GET", @lbmgmthost, "#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}/nodes",@lbmgmtport,@lbmgmtscheme)
58
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
59
+ CloudLB.symbolize_keys(JSON.parse(response.body)["nodes"])
60
+ end
61
+ alias :nodes :list_nodes
62
+
63
+ # Returns a CloudLB::Node object for the given node id.
64
+ def get_node(id)
65
+ CloudLB::Node.new(self,id)
66
+ end
67
+ alias :node :get_node
68
+
69
+ # Creates a brand new backend node and associates it with the current load balancer. Returns the new Node object.
70
+ #
71
+ # Options include:
72
+ # * :address - The IP address of the backend node *required*
73
+ # * :port - The TCP port that the backend node listens on. *required*
74
+ # * :condition - Can be "ENABLED" (default), "DISABLED", or "DRAINING"
75
+ # * :weight - A weighting for the WEIGHTED_ balancing algorithms. Defaults to 1.
76
+ def create_node(options={})
77
+ (raise CloudLB::Exception::MissingArgument, "Must provide a node IP address") if options[:address].to_s.empty?
78
+ (raise CloudLB::Exception::MissingArgument, "Must provide a node TCP port") if options[:port].to_s.empty?
79
+ options[:condition] ||= "ENABLED"
80
+ body = {:nodes => [options]}.to_json
81
+ response = @connection.lbreq("POST", @lbmgmthost, "#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}/nodes",@lbmgmtport,@lbmgmtscheme,{},body)
82
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
83
+ body = JSON.parse(response.body)['nodes'][0]
84
+ return get_node(body["id"])
85
+ end
86
+
87
+ # Deletes the current load balancer object. Returns true if successful, raises an exception otherwise.
88
+ def destroy!
89
+ response = @connection.lbreq("DELETE", @lbmgmthost, "#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}",@lbmgmtport,@lbmgmtscheme)
90
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^202$/)
91
+ true
92
+ end
93
+
94
+ # TODO: Figure out formats for startTime and endTime
95
+ def usage
96
+ response = @connection.lbreq("GET",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}/usage",@lbmgmtport,@lbmgmtscheme,{})
97
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
98
+ CloudLB.symbolize_keys(JSON.parse(response.body)["loadBalancerUsageRecords"])
99
+ end
100
+
101
+ # Returns either true or false if connection logging is enabled for this load balancer.
102
+ #
103
+ # >> balancer.connection_logging?
104
+ # => false
105
+ def connection_logging?
106
+ response = @connection.lbreq("GET",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}/connectionlogging",@lbmgmtport,@lbmgmtscheme,{})
107
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
108
+ JSON.parse(response.body)["connectionLogging"]["enabled"]
109
+ end
110
+
111
+ # Pass either true or false in to enable or disable connection logging for this load balancer. Returns true if successful,
112
+ # raises execption otherwise.
113
+ #
114
+ # >> balancer.connection_logging = true
115
+ # => true
116
+ def connection_logging=(state)
117
+ (raise CloudLB::Exception::MissingArgument, "Must provide either true or false") unless [true,false].include?(state)
118
+ body = {'connectionLogging' => {:enabled => state}}.to_json
119
+ response = @connection.lbreq("PUT",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}/connectionlogging",@lbmgmtport,@lbmgmtscheme,{}, body)
120
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
121
+ true
122
+ end
123
+
124
+ # Sets a new name for the current load balancer. Name must be 128 characters or less.
125
+ def name=(new_name="")
126
+ (raise CloudLB::Exception::Syntax, "Load balancer name must be 128 characters or less") if new_name.size > 128
127
+ (raise CloudLB::Exception::MissingArgument, "Must provide a new name") if new_name.to_s.empty?
128
+ body = {"name" => new_name}
129
+ update(body)
130
+ end
131
+
132
+ # Sets a new balancer algorithm for the current load balancer. Must be a valid algorithm as returned by the
133
+ # CloudLB::Connection.list_algorithms call.
134
+ def algorithm=(new_algorithm="")
135
+ (raise CloudLB::Exception::MissingArgument, "Must provide a new name") if new_algorithm.to_s.empty?
136
+ body = {"algorithm" => new_algorithm.to_s.upcase}
137
+ update(body)
138
+ end
139
+
140
+ # Sets a new port for the current load balancer to listen upon.
141
+ def port=(new_port="")
142
+ (raise CloudLB::Exception::MissingArgument, "Must provide a new port") if new_port.to_s.empty?
143
+ (raise CloudLB::Exception::Syntax, "Port must be numeric") unless new_port.to_s =~ /^\d+$/
144
+ body = {"port" => new_port.to_s}
145
+ update(body)
146
+ end
147
+
148
+ # Sets a new protocol for the current load balancer to manage. Must be a valid protocol as returned by the
149
+ # CloudLB::Connection.list_protocols call.
150
+ def protocol=(new_protocol="")
151
+ (raise CloudLB::Exception::MissingArgument, "Must provide a new protocol") if new_protocol.to_s.empty?
152
+ body = {"protocol" => new_protocol}
153
+ update(body)
154
+ end
155
+
156
+ # Checks to see whether or not the load balancer is using HTTP cookie session persistence. Returns true if it is, false otherwise.
157
+ def session_persistence?
158
+ response = @connection.lbreq("GET",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}/sessionpersistence",@lbmgmtport,@lbmgmtscheme,{})
159
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
160
+ JSON.parse(response.body)["sessionPersistence"]["persistenceType"] == "HTTP_COOKIE" ? true : false
161
+ end
162
+
163
+ # Allows toggling of HTTP cookie session persistence. Valid values are true and false to enable or disable, respectively.
164
+ # FIXME - Trying to set the persistence to true is currently returning an undocumented 405 error.
165
+ def session_persistence=(value)
166
+ (raise CloudLB::Exception::MissingArgument, "value must be true or false") unless [true,false].include?(value)
167
+ if value == true
168
+ body = {'sessionPersistence' => {'persistenceType' => 'HTTP_COOKIE'}}
169
+ response = @connection.lbreq("POST", @lbmgmthost, "#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}/sessionpersistence",@lbmgmtport,@lbmgmtscheme,{},body.to_json)
170
+ elsif value == false
171
+ response = @connection.lbreq("DELETE", @lbmgmthost, "#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}/sessionpersistence",@lbmgmtport,@lbmgmtscheme)
172
+ end
173
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
174
+ true
175
+ end
176
+
177
+ # Returns the current access control list for the load balancer.
178
+ def list_access_list
179
+ response = @connection.lbreq("GET",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}/accesslist",@lbmgmtport,@lbmgmtscheme,{})
180
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
181
+ JSON.parse(response.body)["accessList"]
182
+ end
183
+ alias :access_list :list_access_list
184
+
185
+ # FIXME: Does not work (JSON error)
186
+ def add_to_access_list(options={})
187
+ (raise CloudLB::Exception::MissingArgument, "Must supply address and type") unless (options[:address] && options[:type])
188
+ body = {'networkItems' => [ { :address => options[:address], :type => options[:type].upcase}]}.to_json
189
+ response = @connection.lbreq("POST",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}/accesslist",@lbmgmtport,@lbmgmtscheme,{},body)
190
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
191
+ true
192
+ end
193
+
194
+ # Deletes the entire access list for this load balancer, removing all entries. Returns true if successful, raises
195
+ # an exception otherwise.
196
+ #
197
+ # >> balancer.delete_access_list
198
+ # => true
199
+ def delete_access_list
200
+ response = @connection.lbreq("DELETE",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}/accesslist",@lbmgmtport,@lbmgmtscheme,{})
201
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
202
+ true
203
+ end
204
+
205
+ # TODO
206
+ def delete_access_list_member(id)
207
+ response = @connection.lbreq("DELETE",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}/accesslist/#{CloudLB.escape(id.to_s)}",@lbmgmtport,@lbmgmtscheme,{})
208
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
209
+ true
210
+ end
211
+
212
+ def get_health_monitor
213
+ CloudLB::HealthMonitor.new(self)
214
+ end
215
+ alias :health_monitor :get_health_monitor
216
+
217
+ def get_connection_throttle
218
+ CloudLB::ConnectionThrottle.new(self)
219
+ end
220
+ alias :connection_throttle :get_connection_throttle
221
+
222
+ private
223
+
224
+ def update(body)
225
+ response = @connection.lbreq("PUT", @lbmgmthost, "#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@id.to_s)}",@lbmgmtport,@lbmgmtscheme,{},body.to_json)
226
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
227
+ populate
228
+ true
229
+ end
230
+
231
+
232
+ end
233
+ end
@@ -0,0 +1,199 @@
1
+ module CloudLB
2
+ class Connection
3
+
4
+ attr_reader :authuser
5
+ attr_reader :authkey
6
+ attr_accessor :authtoken
7
+ attr_accessor :authok
8
+ attr_accessor :lbmgmthost
9
+ attr_accessor :lbmgmtpath
10
+ attr_accessor :lbmgmtport
11
+ attr_accessor :lbmgmtscheme
12
+ attr_reader :auth_url
13
+ attr_reader :region
14
+
15
+ # Creates a new CloudLB::Connection object. Uses CloudLB::Authentication to perform the login for the connection.
16
+ #
17
+ # Setting the retry_auth option to false will cause an exception to be thrown if your authorization token expires.
18
+ # Otherwise, it will attempt to reauthenticate.
19
+ #
20
+ # This will likely be the base class for most operations.
21
+ #
22
+ # The constructor takes a hash of options, including:
23
+ #
24
+ # :username - Your Rackspace Cloud username *required*
25
+ # :api_key - Your Rackspace Cloud API key *required*
26
+ # :region - The region in which to manage load balancers. Current options are :dfw (Rackspace Dallas/Ft. Worth Datacenter)
27
+ # and :ord (Rackspace Chicago Datacenter). *required*
28
+ # :auth_url - The URL to use for authentication. (defaults to Rackspace USA).
29
+ # :retry_auth - Whether to retry if your auth token expires (defaults to true)
30
+ #
31
+ # lb = CloudLB::Connection.new(:username => 'YOUR_USERNAME', :api_key => 'YOUR_API_KEY', :region => :dfw)
32
+ def initialize(options = {:retry_auth => true})
33
+ @authuser = options[:username] || (raise CloudLB::Exception::Authentication, "Must supply a :username")
34
+ @authkey = options[:api_key] || (raise CloudLB::Exception::Authentication, "Must supply an :api_key")
35
+ @region = options[:region] || (raise CloudLB::Exception::Authentication, "Must supply a :region")
36
+ @retry_auth = options[:retry_auth]
37
+ @auth_url = options[:auth_url] || CloudLB::AUTH_USA
38
+ @snet = ENV['RACKSPACE_SERVICENET'] || options[:snet]
39
+ @authok = false
40
+ @http = {}
41
+ CloudLB::Authentication.new(self)
42
+ end
43
+
44
+ # Returns true if the authentication was successful and returns false otherwise.
45
+ #
46
+ # lb.authok?
47
+ # => true
48
+ def authok?
49
+ @authok
50
+ end
51
+
52
+ # Returns the list of available load balancers. By default, it hides deleted load balancers (which hang around unusable
53
+ # for 90 days). To show all load balancers, including deleted ones, pass in :show_deleted => true.
54
+ #
55
+ # Information returned includes:
56
+ # * :id - The numeric ID of this load balancer
57
+ # * :name - The name of the load balancer
58
+ # * :status - The current state of the load balancer (ACTIVE, BUILD, PENDING_UPDATE, PENDING_DELETE, SUSPENDED, ERROR)
59
+ # * :port - The TCP port that the load balancer listens on
60
+ # * :protocol - The network protocol being balanced.
61
+ # * :algorithm - The balancing algorithm used by this load balancer
62
+ # * :created[:time] - The time that the load balancer was created
63
+ # * :updated[:time] - The most recent time that the load balancer was modified
64
+ # * :virutalIps - Information about the Virtual IPs providing service to this load balancer.
65
+ def list_load_balancers(options={})
66
+ response = lbreq("GET",lbmgmthost,"#{lbmgmtpath}/loadbalancers",lbmgmtport,lbmgmtscheme)
67
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
68
+ balancers = CloudLB.symbolize_keys(JSON.parse(response.body)["loadBalancers"])
69
+ return options[:show_deleted] == true ? balancers : balancers.select{|lb| lb[:status] != "DELETED"}
70
+ end
71
+ alias :load_balancers :list_load_balancers
72
+
73
+ # Returns a CloudLB::Balancer object for the given load balancer ID number.
74
+ #
75
+ # >> lb.get_load_balancer(2)
76
+ def get_load_balancer(id)
77
+ CloudLB::Balancer.new(self,id)
78
+ end
79
+ alias :load_balancer :get_load_balancer
80
+
81
+ # Creates a brand new load balancer under your account.
82
+ #
83
+ # A minimal request must pass in :name, :protocol, :nodes, and either :virtual_ip_ids or :virtual_ip_types.
84
+ #
85
+ # Options:
86
+ # :name - the name of the load balancer. Limted to 128 characters or less.
87
+ # :protocol - the protocol to balance. Must be a valid protocol. Get the list of available protocols with the list_protocols
88
+ # command. Supported in the latest docs are: HTTP, HTTPS, FTP, IMAPv4, IMAPS, POP3, POP3S, LDAP, LDAPS, SMTP
89
+ # :nodes - An array of hashes for each node to be balanced. The hash should contain the address of the target server,
90
+ # the port on the target server, and the condition ("ENABLED", "DISABLED", "DRAINING"). Optionally supply a :weight for use
91
+ # in the WEIGHTED_ algorithms.
92
+ # {:address => "1.2.3.4", :port => "80", :condition => "ENABLED", :weight => 1}
93
+ # :port - the port that the load balancer listens on. Defaults to the standard port for the protocol.
94
+ # :algorithm - A valid balancing algorithm. Use the algorithms method to get the list. Valid in the current documentation
95
+ # are RANDOM (default), LEAST_CONNECTIONS, ROUND_ROBIN, WEIGHTED_LEAST_CONNECTIONS, WEIGHTED_ROUND_ROBIN
96
+ # :virtual_ip_id - If you have existing virtual IPs and would like to reuse them in a different balancer (for example, to
97
+ # load balance both HTTP and HTTPS on the same IP), you can pass in an array of the ID numbers for those
98
+ # virtual IPs
99
+ # :virtual_ip_type - Alternately, you can get new IP addresses by passing in the type of addresses that you
100
+ # want to obtain. Values are "PUBLIC" or "PRIVATE".
101
+ def create_load_balancer(options = {})
102
+ body = Hash.new
103
+ body[:name] = options[:name] or raise CloudLB::Exception::MissingArgument, "Must provide a name to create a load balancer"
104
+ (raise CloudLB::Exception::Syntax, "Load balancer name must be 128 characters or less") if options[:name].size > 128
105
+ (raise CloudLB::Exception::Syntax, "Must provide at least one node in the :nodes array") if (!options[:nodes].is_a?(Array) || options[:nodes].size < 1)
106
+ body[:protocol] = options[:protocol] or raise CloudLB::Exception::MissingArgument, "Must provide a protocol to create a load balancer"
107
+ body[:port] = options[:port] if options[:port]
108
+ body[:nodes] = options[:nodes]
109
+ body[:protocol].upcase! if body[:protocol]
110
+ body[:algorithm].upcase! if body[:algorithm]
111
+ if options[:virtual_ip_id]
112
+ body['virtualIps'] = [{:id => options[:virtual_ip_id]}]
113
+ elsif options[:virtual_ip_type]
114
+ body['virtualIps'] = [{:type => options[:virtual_ip_type]}]
115
+ end
116
+ response = lbreq("POST",lbmgmthost,"#{lbmgmtpath}/loadbalancers",lbmgmtport,lbmgmtscheme,{},body.to_json)
117
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
118
+ body = JSON.parse(response.body)['loadBalancer']
119
+ return get_load_balancer(body["id"])
120
+ end
121
+
122
+ # Returns a list of protocols that are currently supported by the Cloud Load Balancer product.
123
+ #
124
+ # >> lb.list_protocols
125
+ # => [{:port=>21, :name=>"FTP"}, {:port=>80, :name=>"HTTP"}, {:port=>443, :name=>"HTTPS"}, {:port=>993, :name=>"IMAPS"}, {:port=>143, :name=>"IMAPv4"}, {:port=>389, :name=>"LDAP"}, {:port=>636, :name=>"LDAPS"}, {:port=>110, :name=>"POP3"}, {:port=>995, :name=>"POP3S"}, {:port=>25, :name=>"SMTP"}]
126
+ def list_protocols
127
+ response = lbreq("GET",lbmgmthost,"#{lbmgmtpath}/loadbalancers/protocols",lbmgmtport,lbmgmtscheme,{})
128
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
129
+ CloudLB.symbolize_keys(JSON.parse(response.body)["protocols"])
130
+ end
131
+ alias :protocols :list_protocols
132
+
133
+ # Returns a list of balancer algorithms that are currently supported by the Cloud Load Balancer product.
134
+ #
135
+ # >> lb.list_algorithms
136
+ # => [{:name=>"LEAST_CONNECTIONS"}, {:name=>"RANDOM"}, {:name=>"ROUND_ROBIN"}, {:name=>"WEIGHTED_LEAST_CONNECTIONS"}, {:name=>"WEIGHTED_ROUND_ROBIN"}]
137
+ def list_algorithms
138
+ response = lbreq("GET",lbmgmthost,"#{lbmgmtpath}/loadbalancers/algorithms",lbmgmtport,lbmgmtscheme,{})
139
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
140
+ CloudLB.symbolize_keys(JSON.parse(response.body)["algorithms"])
141
+ end
142
+ alias :algorithms :list_algorithms
143
+
144
+
145
+ # This method actually makes the HTTP REST calls out to the server. Relies on the thread-safe typhoeus
146
+ # gem to do the heavy lifting. Never called directly.
147
+ def lbreq(method,server,path,port,scheme,headers = {},data = nil,attempts = 0) # :nodoc:
148
+ if data
149
+ unless data.is_a?(IO)
150
+ headers['Content-Length'] = data.respond_to?(:lstat) ? data.stat.size : data.size
151
+ end
152
+ else
153
+ headers['Content-Length'] = 0
154
+ end
155
+ hdrhash = headerprep(headers)
156
+ url = "#{scheme}://#{server}#{path}"
157
+ print "DEBUG: Data is #{data}\n" if (data && ENV['LOADBALANCERS_VERBOSE'])
158
+ request = Typhoeus::Request.new(url,
159
+ :body => data,
160
+ :method => method.downcase.to_sym,
161
+ :headers => hdrhash,
162
+ # :user_agent => "CloudLB Ruby API #{VERSION}",
163
+ :verbose => ENV['LOADBALANCERS_VERBOSE'] ? true : false)
164
+ CloudLB.hydra.queue(request)
165
+ CloudLB.hydra.run
166
+
167
+ response = request.response
168
+ print "DEBUG: Body is #{response.body}\n" if ENV['LOADBALANCERS_VERBOSE']
169
+ raise CloudLB::Exception::ExpiredAuthToken if response.code.to_s == "401"
170
+ response
171
+ rescue Errno::EPIPE, Errno::EINVAL, EOFError
172
+ # Server closed the connection, retry
173
+ raise CloudLB::Exception::Connection, "Unable to reconnect to #{server} after #{attempts} attempts" if attempts >= 5
174
+ attempts += 1
175
+ @http[server].finish if @http[server].started?
176
+ start_http(server,path,port,scheme,headers)
177
+ retry
178
+ rescue CloudLB::Exception::ExpiredAuthToken
179
+ raise CloudLB::Exception::Connection, "Authentication token expired and you have requested not to retry" if @retry_auth == false
180
+ CloudLB::Authentication.new(self)
181
+ retry
182
+ end
183
+
184
+
185
+ private
186
+
187
+ # Sets up standard HTTP headers
188
+ def headerprep(headers = {}) # :nodoc:
189
+ default_headers = {}
190
+ default_headers["X-Auth-Token"] = @authtoken if (authok? && @account.nil?)
191
+ default_headers["X-Storage-Token"] = @authtoken if (authok? && !@account.nil?)
192
+ default_headers["Connection"] = "Keep-Alive"
193
+ default_headers["Accept"] = "application/json"
194
+ default_headers["Content-Type"] = "application/json"
195
+ default_headers.merge(headers)
196
+ end
197
+
198
+ end
199
+ end
@@ -0,0 +1,105 @@
1
+ module CloudLB
2
+ class ConnectionThrottle
3
+
4
+ attr_reader :min_connections
5
+ attr_reader :max_connections
6
+ attr_reader :max_connection_rate
7
+ attr_reader :rate_interval
8
+
9
+ # Initializes a new CloudLB::ConnectionThrottle object with the current values. If there is no connection
10
+ # throttle currently defined, the enabled? method returns false.
11
+ def initialize(load_balancer)
12
+ @connection = load_balancer.connection
13
+ @load_balancer = load_balancer
14
+ @lbmgmthost = @connection.lbmgmthost
15
+ @lbmgmtpath = @connection.lbmgmtpath
16
+ @lbmgmtport = @connection.lbmgmtport
17
+ @lbmgmtscheme = @connection.lbmgmtscheme
18
+ populate
19
+ return self
20
+ end
21
+
22
+ def populate
23
+ response = @connection.lbreq("GET",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@load_balancer.id.to_s)}/connectionthrottle",@lbmgmtport,@lbmgmtscheme)
24
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
25
+ data = JSON.parse(response.body)['connectionThrottle']
26
+ @enabled = data == {} ? false : true
27
+ return nil unless @enabled
28
+ @min_connections = data["minConnections"]
29
+ @max_connections = data["maxConnections"]
30
+ @max_connection_rate = data["maxConnectionRate"]
31
+ @rate_interval = data["rateInterval"]
32
+ true
33
+ end
34
+ alias :refresh :populate
35
+
36
+ # Returns true if the connection throttle is defined and has data, returns false if not.
37
+ def enabled?
38
+ @enabled
39
+ end
40
+
41
+ # Updates (or creates) the connection throttle with the supplied arguments
42
+ #
43
+ # To create a health monitor for the first time, you must supply all *required* options. However, to modify an
44
+ # existing monitor, you need only supply the the value that you want to change.
45
+ #
46
+ # Options include:
47
+ #
48
+ # * :max_connections - Maximum number of connection to allow for a single IP address. *required*
49
+ # * :min_connections - Allow at least this number of connections per IP address before applying throttling restrictions. *required*
50
+ # * :max_connection_rate - Maximum number of connections allowed from a single IP address in the defined :rate_interval. *required*
51
+ # * :rate_interval - Frequency (in seconds) at which the maxConnectionRate is assessed. For example, a :max_connection_rate of 30
52
+ # with a :rate_interval of 60 would allow a maximum of 30 connections per minute for a single IP address. *required*
53
+ def update(options={})
54
+ data = Hash.new
55
+ data['maxConnections'] = options[:max_connections] if options[:max_connections]
56
+ data['minConnections'] = options[:min_connections] if options[:min_connections]
57
+ data['maxConnectionRate'] = options[:max_connection_rate] if options[:max_connection_rate]
58
+ data['rateInterval'] = options[:rate_interval] if options[:rate_interval]
59
+
60
+ response = @connection.lbreq("PUT",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@load_balancer.id.to_s)}/connectionthrottle",@lbmgmtport,@lbmgmtscheme,{},data.to_json)
61
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
62
+ populate
63
+ true
64
+ end
65
+
66
+ # Convenience method to update the max_connections value. Returns false if the connection throttle is not enabled,
67
+ # the new value if it succeeds, and raises an exception otherwise.
68
+ def max_connections=(value)
69
+ return false unless @enabled
70
+ update(:max_connections => value)
71
+ end
72
+
73
+ # Convenience method to update the min_connections value. Returns false if the connection throttle is not enabled,
74
+ # the new value if it succeeds, and raises an exception otherwise.
75
+ def min_connections=(value)
76
+ return false unless @enabled
77
+ update(:min_connections => value)
78
+ end
79
+
80
+ # Convenience method to update the max_connection_rate value. Returns false if the connection throttle is not enabled,
81
+ # the new value if it succeeds, and raises an exception otherwise.
82
+ def max_connection_rate=(value)
83
+ return false unless @enabled
84
+ update(:max_connection_rate => value)
85
+ end
86
+
87
+ # Convenience method to update the rate_interval value. Returns false if the connection throttle is not enabled,
88
+ # the new value if it succeeds, and raises an exception otherwise.
89
+ def rate_interval=(value)
90
+ return false unless @enabled
91
+ update(:rate_interval => value)
92
+ end
93
+
94
+ # Removes the current health monitor. Returns true if successful, exception otherwise.
95
+ #
96
+ # >> monitor.destroy!
97
+ # => true
98
+ def destroy!
99
+ response = @connection.lbreq("DELETE",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@load_balancer.id.to_s)}/connectionthrottle",@lbmgmtport,@lbmgmtscheme)
100
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
101
+ @enabled = false
102
+ true
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,65 @@
1
+ module CloudLB
2
+ class Exception
3
+
4
+ class CloudLBError < StandardError
5
+
6
+ attr_reader :response_body
7
+ attr_reader :response_code
8
+
9
+ def initialize(message, code, response_body)
10
+ @response_code=code
11
+ @response_body=response_body
12
+ super(message)
13
+ end
14
+
15
+ end
16
+
17
+ class LoadBalancerFault < CloudLBError # :nodoc:
18
+ end
19
+ class ServiceUnavailable < CloudLBError # :nodoc:
20
+ end
21
+ class Unauthorized < CloudLBError # :nodoc:
22
+ end
23
+ class BadRequest < CloudLBError # :nodoc:
24
+ end
25
+ class OverLimit < CloudLBError # :nodoc:
26
+ end
27
+ class Other < CloudLBError # :nodoc:
28
+ end
29
+
30
+ # Plus some others that we define here
31
+
32
+ class ExpiredAuthToken < StandardError # :nodoc:
33
+ end
34
+ class MissingArgument < StandardError # :nodoc:
35
+ end
36
+ class Authentication < StandardError # :nodoc:
37
+ end
38
+ class Connection < StandardError # :nodoc:
39
+ end
40
+ class Syntax < StandardError # :nodoc:
41
+ end
42
+
43
+
44
+ # In the event of a non-200 HTTP status code, this method takes the HTTP response, parses
45
+ # the JSON from the body to get more information about the exception, then raises the
46
+ # proper error. Note that all exceptions are scoped in the CloudLB::Exception namespace.
47
+ def self.raise_exception(response)
48
+ return if response.code =~ /^20.$/
49
+ begin
50
+ fault = nil
51
+ info = nil
52
+ JSON.parse(response.body).each_pair do |key, val|
53
+ fault=key
54
+ info=val
55
+ end
56
+ exception_class = self.const_get(fault[0,1].capitalize+fault[1,fault.length])
57
+ raise exception_class.new(info["message"], response.code, response.body)
58
+ rescue NameError, JSON::ParserError
59
+ raise CloudLB::Exception::Other.new("The server returned status #{response.code} with body #{response.body}", response.code, response.body)
60
+ end
61
+ end
62
+
63
+ end
64
+ end
65
+
@@ -0,0 +1,135 @@
1
+ module CloudLB
2
+ class HealthMonitor
3
+
4
+ attr_reader :type
5
+ attr_reader :delay
6
+ attr_reader :timeout
7
+ attr_reader :attempts_before_deactivation
8
+ attr_reader :path
9
+ attr_reader :status_regex
10
+ attr_reader :body_regex
11
+
12
+ # Initializes a new CloudLB::ConnectionThrottle object
13
+ def initialize(load_balancer)
14
+ @connection = load_balancer.connection
15
+ @load_balancer = load_balancer
16
+ @lbmgmthost = @connection.lbmgmthost
17
+ @lbmgmtpath = @connection.lbmgmtpath
18
+ @lbmgmtport = @connection.lbmgmtport
19
+ @lbmgmtscheme = @connection.lbmgmtscheme
20
+ populate
21
+ return self
22
+ end
23
+
24
+ def populate
25
+ response = @connection.lbreq("GET",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@load_balancer.id.to_s)}/healthmonitor",@lbmgmtport,@lbmgmtscheme)
26
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
27
+ data = JSON.parse(response.body)['healthMonitor']
28
+ @enabled = data == {} ? false : true
29
+ return nil unless @enabled
30
+ @type = data["type"]
31
+ @delay = data["delay"]
32
+ @timeout = data["timeout"]
33
+ @attempts_before_deactivation = data["attemptsBeforeDeactivation"]
34
+ @path = data["path"]
35
+ @status_regex = data["statusRegex"]
36
+ @body_regex = data["bodyRegex"]
37
+ true
38
+ end
39
+ alias :refresh :populate
40
+
41
+ # Returns true if the health monitor is defined and has data, returns false if not.
42
+ def enabled?
43
+ @enabled
44
+ end
45
+
46
+ # Updates (or creates) the health monitor with the supplied arguments
47
+ #
48
+ # To create a health monitor for the first time, you must supply all *required* options. However, to modify an
49
+ # existing monitor, you need only supply the :type and whatever it is that you want to change.
50
+ #
51
+ # Options include:
52
+ #
53
+ # * :type - The type of health monitor. Can be CONNECT (simple TCP connections), HTTP, or HTTPS. The HTTP and HTTPS
54
+ # monitors require additional options. *required*
55
+ # * :delay - The minimum number of seconds to wait before executing the health monitor. *required*
56
+ # * :timeout - Maximum number of seconds to wait for a connection to be established before timing out. *required*
57
+ # * :attempts_before_deactivation - Number of permissible monitor failures before removing a node from rotation. *required*
58
+ #
59
+ # For HTTP and HTTPS monitors, there are additional options. You must supply the :path and either the :status_regex or :body_regex
60
+ #
61
+ # * :path - The HTTP path that will be used in the sample request. *required*
62
+ # * :status_regex - A regular expression that will be used to evaluate the HTTP status code returned in the response
63
+ # * :body_regex - A regular expression that will be used to evaluate the contents of the body of the response.
64
+ def update(options={})
65
+ data = Hash.new
66
+ data[:type] = options[:type].upcase if options[:type]
67
+ data[:delay] = options[:delay] if options[:delay]
68
+ data[:timeout] = options[:timeout] if options[:timeout]
69
+ data['attemptsBeforeDeactivation'] = options[:attempts_before_deactivation] if options[:attempts_before_deactivation]
70
+ data[:type].upcase! if data[:type]
71
+ if ['HTTP','HTTPS'].include?(data[:type])
72
+ data[:path] = options[:path] if options[:path]
73
+ data['statusRegex'] = options[:status_regex] if options[:status_regex]
74
+ data['bodyRegex'] = options[:body_regex] if options[:body_regex]
75
+ end
76
+ response = @connection.lbreq("PUT",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@load_balancer.id.to_s)}/healthmonitor",@lbmgmtport,@lbmgmtscheme,{},data.to_json)
77
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
78
+ populate
79
+ true
80
+ end
81
+
82
+ # Convenience method to update the delay value for the current type. Returns false if the health monitor is not enabled,
83
+ # the new value if it succeeds, and raises an exception otherwise.
84
+ def delay=(value)
85
+ return false unless @enabled
86
+ update(:type => self.type, :delay => value)
87
+ end
88
+
89
+ # Convenience method to update the timeout value for the current type. Returns false if the health monitor is not enabled,
90
+ # the new value if it succeeds, and raises an exception otherwise.
91
+ def timeout=(value)
92
+ return false unless @enabled
93
+ update(:type => self.type, :timeout => value)
94
+ end
95
+
96
+ # Convenience method to update the attempts before deactivation value for the current type. Returns false if the health monitor is not enabled,
97
+ # the new value if it succeeds, and raises an exception otherwise.
98
+ def attempts_before_deactivation=(value)
99
+ return false unless @enabled
100
+ update(:type => self.type, :attempts_before_deactivation => value)
101
+ end
102
+
103
+ # Convenience method to update the path value for the current type. Returns false if the health monitor is not enabled
104
+ # or not an http monitor, the new value if it succeeds, and raises an exception otherwise.
105
+ def path=(value)
106
+ return false unless @enabled && ['HTTP','HTTPS'].include?(self.type)
107
+ update(:type => self.type, :path => value)
108
+ end
109
+
110
+ # Convenience method to update the delay value for the current type. Returns false if the health monitor is not enabled
111
+ # or is not an http monitor, the new value if it succeeds, and raises an exception otherwise.
112
+ def status_regex=(value)
113
+ return false unless @enabled && ['HTTP','HTTPS'].include?(self.type)
114
+ update(:type => self.type, :status_regex => value)
115
+ end
116
+
117
+ # Convenience method to update the delay value for the current type. Returns false if the health monitor is not enabled
118
+ # or is not an http monitor, the new value if it succeeds, and raises an exception otherwise.
119
+ def body_regex=(value)
120
+ return false unless @enabled && ['HTTP','HTTPS'].include?(self.type)
121
+ update(:type => self.type, :body_regex => value)
122
+ end
123
+
124
+ # Removes the current health monitor. Returns true if successful, exception otherwise.
125
+ #
126
+ # >> monitor.destroy!
127
+ # => true
128
+ def destroy!
129
+ response = @connection.lbreq("DELETE",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@load_balancer.id.to_s)}/healthmonitor",@lbmgmtport,@lbmgmtscheme)
130
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
131
+ @enabled = false
132
+ true
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,64 @@
1
+ module CloudLB
2
+ class Node
3
+
4
+ attr_reader :id
5
+ attr_reader :address
6
+ attr_reader :condition
7
+ attr_reader :port
8
+ attr_reader :weight
9
+ attr_reader :status
10
+
11
+ # Initializes a new CloudLB::Node object
12
+ def initialize(load_balancer,id)
13
+ @connection = load_balancer.connection
14
+ @load_balancer = load_balancer
15
+ @id = id
16
+ @lbmgmthost = @connection.lbmgmthost
17
+ @lbmgmtpath = @connection.lbmgmtpath
18
+ @lbmgmtport = @connection.lbmgmtport
19
+ @lbmgmtscheme = @connection.lbmgmtscheme
20
+ populate
21
+ return self
22
+ end
23
+
24
+ # Updates the information about this CloudLB::Node object by making an API call.
25
+ def populate
26
+ response = @connection.lbreq("GET",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@load_balancer.id.to_s)}/nodes/#{CloudLB.escape(@id.to_s)}",@lbmgmtport,@lbmgmtscheme)
27
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
28
+ data = JSON.parse(response.body)['node']
29
+ @id = data["id"]
30
+ @address = data["address"]
31
+ @condition = data["condition"]
32
+ @port = data["port"]
33
+ @weight = data["weight"]
34
+ @status = data["status"]
35
+ true
36
+ end
37
+ alias :refresh :populate
38
+
39
+ # Allows you to change the condition of the current Node. Values should be either "ENABLED", "DISABLED", or "DRAINING"
40
+ def condition=(data)
41
+ (raise CloudLB::Exception::MissingArgument, "Must provide a new node condition") if data.to_s.empty?
42
+ body = {"condition" => data.to_s.upcase}
43
+ update(body)
44
+ end
45
+
46
+ # Deletes the current Node object and removes it from the load balancer. Returns true if successful, raises an exception if not.
47
+ def destroy!
48
+ response = @connection.lbreq("DELETE",@lbmgmthost,"#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@load_balancer.id.to_s)}/nodes/#{CloudLB.escape(@id.to_s)}",@lbmgmtport,@lbmgmtscheme)
49
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
50
+ true
51
+ end
52
+
53
+ private
54
+
55
+ def update(data)
56
+ body = {:node => data}
57
+ response = @connection.lbreq("PUT", @lbmgmthost, "#{@lbmgmtpath}/loadbalancers/#{CloudLB.escape(@load_balancer.id.to_s)}/nodes/#{CloudLB.escape(@id.to_s)}",@lbmgmtport,@lbmgmtscheme,{},body.to_json)
58
+ CloudLB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
59
+ populate
60
+ true
61
+ end
62
+
63
+ end
64
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloudlb
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - H. Wade Minter
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-25 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: typhoeus
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: json
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ description: A Ruby API to manage the Rackspace Cloud Load Balancers product
50
+ email:
51
+ - minter@lunenburg.org
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - VERSION
60
+ - COPYING
61
+ - .gitignore
62
+ - README.rdoc
63
+ - cloudlb.gemspec
64
+ - lib/cloudlb.rb
65
+ - lib/cloudlb/authentication.rb
66
+ - lib/cloudlb/balancer.rb
67
+ - lib/cloudlb/connection.rb
68
+ - lib/cloudlb/exception.rb
69
+ - lib/cloudlb/node.rb
70
+ - lib/cloudlb/health_monitor.rb
71
+ - lib/cloudlb/connection_throttle.rb
72
+ has_rdoc: true
73
+ homepage: http://github.com/rackspace/ruby-cloudlb
74
+ licenses: []
75
+
76
+ post_install_message:
77
+ rdoc_options: []
78
+
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ hash: 3
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ hash: 23
96
+ segments:
97
+ - 1
98
+ - 3
99
+ - 6
100
+ version: 1.3.6
101
+ requirements: []
102
+
103
+ rubyforge_project:
104
+ rubygems_version: 1.5.0
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: Ruby API into the Rackspace Cloud Load Balancers product
108
+ test_files: []
109
+